set

package module
v0.0.0-...-1da624a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

go-ruby-set/set

set — go-ruby-set

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's Set — the unordered, unique collection with full set algebra from MRI 4.0.5's set standard library (autoloaded core in Ruby 4.0). It mirrors Set's observable behaviour — insertion-ordered iteration, add?/delete?, | & - ^, subset/superset predicates, classify / divide / group_by / flattenwithout any Ruby runtime.

It is the Set backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-yaml (the Psych emitter/loader) and go-ruby-bigdecimal.

MRI-faithful, not Composition-Oriented. This is the stdlib Set — the same philosophy distinction as go-ruby-bigdecimal vs go-composites/bigfloat. It is intentionally distinct from go-composites/set, which is a Composition-Oriented set with a different design. Reach for this one when you want Ruby Set semantics; reach for the composite when you want composition.

SortedSet. MRI 4.0 dropped SortedSet from core (it was removed from the set library), so this package does not ship a SortedSet type. The faithful equivalent of Ruby's Set#sort is SortedSlice, which returns the members ordered by a comparator without mutating the set.

Element identity — the host hash/eql plug

In MRI a Set keys its members by Ruby's hash / eql? protocol: two distinct String objects with the same bytes are the same member, while a Symbol of the same name is a different member. Go cannot know those semantics, so the host supplies them through a Hasher — a function mapping a member to the comparable Go key under which two members are considered equal:

type Hasher func(elem any) any

go-embedded-ruby plugs its own object hashing here, exactly as it does for Hash keys. A Set built with New (no Hasher) keys members by themselves — handy for plain Go data, but the members must be comparable. A Set built with NewWith(hasher, …) keys members through the host function and accepts any value. Iteration always preserves first-insertion order, as MRI does.

Install

go get github.com/go-ruby-set/set

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-set/set"
)

func main() {
	a := set.New(1, 2, 3, 4)
	b := set.New(3, 4, 5, 6)

	fmt.Println(a.Union(b))        // Set[1, 2, 3, 4, 5, 6]
	fmt.Println(a.Intersection(b)) // Set[3, 4]
	fmt.Println(a.Difference(b))   // Set[1, 2]
	fmt.Println(a.XorSym(b))       // Set[1, 2, 5, 6]
	fmt.Println(a.SubsetQ(set.New(1, 2, 3, 4, 5))) // true

	// classify: Hash{ block-value => Set }
	for _, g := range set.New(1, 2, 3, 4, 5, 6).
		Classify(func(e any) any { return e.(int) % 3 }).Groups() {
		fmt.Printf("%v => %v\n", g.Value, g.Set)
	}

	// A host (go-embedded-ruby) supplies Ruby hash/eql? via a Hasher:
	byContent := func(e any) any { return fmt.Sprintf("%v", e) }
	s := set.NewWith(byContent, "a", "a", "b") // the two "a"s coincide
	fmt.Println(s.Size())                       // 2
}

API

type Hasher func(elem any) any

func New(elems ...any) *Set                 // identity-keyed (comparable members)
func NewWith(h Hasher, elems ...any) *Set   // host hash/eql? semantics

// membership & mutation
func (s *Set) Add(elem any) *Set            // add / <<
func (s *Set) AddQ(elem any) bool           // add?  (true if newly inserted)
func (s *Set) Delete(elem any) *Set         // delete
func (s *Set) DeleteQ(elem any) bool        // delete? (true if was present)
func (s *Set) Include(elem any) bool        // include? / member? / ===
func (s *Set) Size() int                    // size / length / count
func (s *Set) Empty() bool                  // empty?
func (s *Set) Clear() *Set                  // clear
func (s *Set) Each(fn func(any) error) error
func (s *Set) ToSlice() []any               // to_a (insertion order)
func (s *Set) Dup() *Set                    // dup / clone
func (s *Set) Merge(others ...*Set) *Set    // merge
func (s *Set) MergeSlice(elems []any) *Set
func (s *Set) Subtract(other *Set) *Set     // subtract
func (s *Set) SubtractSlice(elems []any) *Set

// set algebra
func (s *Set) Union(other *Set) *Set        // | / + / union
func (s *Set) Intersection(other *Set) *Set // & / intersection
func (s *Set) Difference(other *Set) *Set   // - / difference
func (s *Set) XorSym(other *Set) *Set       // ^ (symmetric difference)
func (s *Set) SubsetQ(other *Set) bool      // subset? / <=
func (s *Set) ProperSubsetQ(other *Set) bool   // proper_subset? / <
func (s *Set) SupersetQ(other *Set) bool       // superset? / >=
func (s *Set) ProperSupersetQ(other *Set) bool // proper_superset? / >
func (s *Set) DisjointQ(other *Set) bool    // disjoint?
func (s *Set) IntersectQ(other *Set) bool   // intersect?
func (s *Set) EqualQ(other *Set) bool       // ==

// enumeration & higher-order
func (s *Set) Map(fn func(any) any) []any        // map / collect (-> Array)
func (s *Set) Select(fn func(any) bool) *Set     // select / filter
func (s *Set) Reject(fn func(any) bool) *Set     // reject
func (s *Set) CollectBang(fn func(any) any) *Set // collect! / map! (in place)
func (s *Set) Classify(fn func(any) any) *ClassifyResult // Hash{v => Set}
func (s *Set) GroupBy(fn func(any) any) *GroupByResult   // Hash{v => Array}
func (s *Set) Divide(fn func(any) any) []*Set            // divide {|x| ...}
func (s *Set) DivideRel(rel func(a, b any) bool) []*Set  // divide {|i,j| ...}
func (s *Set) FlattenSet() *Set                          // flatten (recursive)
func (s *Set) SortedSlice(less func(a, b any) bool) []any // sort -> Array
func (s *Set) Inspect(stringFn func(any) string) string  // "Set[1, 2, 3]"
func (s *Set) String() string

Map / Collect and SortedSlice return slices, matching MRI (Set#map and Set#sort return an Array, not a Set). Classify and GroupBy return insertion-ordered result objects (Groups() / Buckets()), matching MRI's Hash insertion ordering; Classify buckets hold Sets, GroupBy buckets hold slices, exactly as Ruby's Set#classify and Enumerable#group_by do. Divide takes the 1-argument block form (partition by block value); DivideRel takes the 2-argument relation form (transitive-closure connected components).

Tests & coverage

The suite pairs deterministic, ruby-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential MRI oracle: set algebra, subset/superset/disjoint predicates, classify, both divide forms, and the Set[…] inspect are computed here and checked against the system ruby. The oracle is gated on RUBY_VERSION >= "4.0" (where Set is core and renders as Set[…]), binmodes stdin/stdout so Windows text-mode never pollutes the bytes, and skips itself where ruby is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

CGO-free, dependency-free, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three OSes (Linux, macOS, Windows).

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-set/set authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package set is a pure-Go (no cgo) reimplementation of Ruby's MRI 4.0.5 Set — an unordered collection of unique members with full set algebra, mirroring the behaviour of the `set` standard library (autoloaded core in Ruby 4.0).

Element identity

In MRI a Set keys its members by Ruby's hash / eql? protocol, so two distinct String objects with the same bytes are the *same* member, while a Symbol of the same name is a *different* member. Go cannot know those semantics, so the host supplies them through a Hasher: a function mapping a member to a comparable Go key under which two members are considered equal. go-embedded-ruby plugs its own object hashing here, exactly as it does for Hash keys.

A Set built with New (no Hasher) keys members by themselves and therefore only accepts comparable members — convenient for plain Go programs. A Set built with NewWith(hasher) keys members through the host function and accepts any value.

Ordering

Like MRI, iteration (Each / ToSlice / Inspect) preserves first-insertion order. The set algebra keeps the receiver's order first, then the argument's, so the results match MRI's observable ordering.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultInspect

func DefaultInspect(elem any) string

DefaultInspect renders a member with Go's %#v, a reasonable default when the host supplies no Ruby inspect.

Types

type ClassGroup

type ClassGroup struct {
	Value any
	Set   *Set
}

ClassGroup is one bucket of a Classify / GroupBy result: the block value that labels the bucket and the members assigned to it.

type ClassifyResult

type ClassifyResult struct {
	// contains filtered or unexported fields
}

ClassifyResult is the insertion-ordered Hash{value => Set} a Classify returns.

func (*ClassifyResult) Get

func (r *ClassifyResult) Get(bv any, h Hasher) (*Set, bool)

Get returns the Set for block value bv and whether it exists.

func (*ClassifyResult) Groups

func (r *ClassifyResult) Groups() []*ClassGroup

Groups returns the buckets in first-seen order (Ruby Hash insertion order).

func (*ClassifyResult) Len

func (r *ClassifyResult) Len() int

Len returns the number of buckets.

type GroupByBucket

type GroupByBucket struct {
	Value   any
	Members []any
}

GroupByBucket is one bucket of a GroupBy result.

type GroupByResult

type GroupByResult struct {
	// contains filtered or unexported fields
}

GroupByResult is the insertion-ordered Hash{value => Array} a GroupBy returns.

func (*GroupByResult) Buckets

func (r *GroupByResult) Buckets() []*GroupByBucket

Buckets returns the buckets in first-seen order.

func (*GroupByResult) Len

func (r *GroupByResult) Len() int

Len returns the number of buckets.

type Hasher

type Hasher func(elem any) any

Hasher maps a member to the comparable key under which membership and equality are decided. Two members are the same iff their keys are ==. The host (e.g. go-embedded-ruby) supplies Ruby hash / eql? semantics here; a nil Hasher keys a member by itself (the member must then be comparable, or operations panic, just as a Go map does on an uncomparable key).

type Set

type Set struct {
	// contains filtered or unexported fields
}

Set is an MRI-faithful Ruby Set: an unordered collection of unique members with set algebra, iterated in first-insertion order. The zero value is not usable; construct one with New or NewWith.

The members are held as two parallel insertion-ordered slices — keys[i] is the identity key of members[i] — plus a membership index from key to presence. Iteration (Each / ToSlice / the algebra source loops) walks the slices directly, so a member is never re-fetched through a map lookup; the index is consulted only for membership tests. When the Hasher is nil a key is its own member, so the two slices hold the same values but are kept distinct to keep the hashed and unhashed paths uniform.

The membership index (see membership) keeps a concrete-typed map for the common homogeneous key types (all int, all int64, all string) and falls back to a generic map[any] only for mixed or other-typed sets, so the frequent case avoids interface-hashing every key while every Ruby-observable semantic — including the distinctness of int(1), int64(1), 1.0 and "1" — is preserved.

func New

func New(elems ...any) *Set

New returns a Set keyed by member identity (members must be comparable), seeded with the given members. It is the convenient form for plain Go data.

func NewWith

func NewWith(h Hasher, elems ...any) *Set

NewWith returns a Set whose membership and equality are decided by h, seeded with the given members. A nil h keys members by themselves (like New). This is the form a host with Ruby hash / eql? semantics uses. The internal tables are pre-grown for the seed count, so seeding a known number of members neither rehashes the index nor re-grows the order slices.

func (*Set) Add

func (s *Set) Add(elem any) *Set

Add inserts elem, returning the Set for chaining (Ruby Set#add / #<<). Adding a member already present is a no-op that preserves the original member.

func (*Set) AddQ

func (s *Set) AddQ(elem any) bool

AddQ adds elem and reports whether it was newly inserted (Ruby Set#add?: self when added, nil when already present — modelled here as a bool).

func (*Set) Classify

func (s *Set) Classify(fn func(elem any) any) *ClassifyResult

Classify groups the members by fn(member), returning a map from each block value's key to the Set of members that produced it (Ruby Set#classify returns a Hash{value => Set}). The returned map is keyed by the *key* of each block value (under the receiver's Hasher) so distinct-but-equal Ruby values coincide; the ClassifyResult also records the original block value for each group.

func (*Set) Clear

func (s *Set) Clear() *Set

Clear removes every member, returning the Set for chaining (Ruby Set#clear).

func (*Set) CollectBang

func (s *Set) CollectBang(fn func(elem any) any) *Set

CollectBang replaces every member with fn(member) in place, returning the receiver for chaining (Ruby Set#collect! / #map!). The set is rebuilt, so two members mapping to the same key collapse to one.

func (*Set) Delete

func (s *Set) Delete(elem any) *Set

Delete removes elem, returning the Set for chaining (Ruby Set#delete). Deleting an absent member is a no-op.

func (*Set) DeleteQ

func (s *Set) DeleteQ(elem any) bool

DeleteQ removes elem and reports whether it was present (Ruby Set#delete?: self when removed, nil when absent — modelled here as a bool).

func (*Set) Difference

func (s *Set) Difference(other *Set) *Set

Difference returns a new Set with the receiver's members not in other (Ruby Set#- / #difference). Order follows the receiver.

func (*Set) DisjointQ

func (s *Set) DisjointQ(other *Set) bool

DisjointQ reports whether the two sets share no member (Ruby Set#disjoint?).

func (*Set) Divide

func (s *Set) Divide(fn func(elem any) any) []*Set

Divide partitions the members into a Set of Sets by the single-argument relation fn: members sharing the same fn value land in the same block (Ruby Set#divide with an arity-1 block). For the transitive-closure (binary) form, use DivideRel.

func (*Set) DivideRel

func (s *Set) DivideRel(rel func(a, b any) bool) []*Set

DivideRel partitions the members into connected components of the graph whose edges are the pairs (a, b) for which rel(a, b) is true (Ruby Set#divide with an arity-2 block — a transitive-closure partition). rel is treated as symmetric: an edge a—b is added when rel(a, b) or rel(b, a) holds.

func (*Set) Dup

func (s *Set) Dup() *Set

Dup returns a shallow copy with the same members in the same order and the same Hasher (Ruby Set#dup / #clone).

func (*Set) Each

func (s *Set) Each(fn func(elem any) error) error

Each calls fn for every member in insertion order (Ruby Set#each). Returning a non-nil error from fn stops iteration and propagates it. It walks the member slice directly — no per-element map lookup.

func (*Set) EachPair

func (s *Set) EachPair(fn func(key, member any))

EachPair calls fn for every member in insertion order with both its identity key and the stored member value, in a single pass over the internal tables — no per-element membership re-lookup. A host that retains its own per-member data keyed by the identity key (as go-embedded-ruby does for the original Ruby value) can consume an algebra result with EachPair to rebuild its view in one pass, instead of re-deriving each member's key and re-testing Include. The members are exactly those yielded by Each / ToSlice, in the same order.

func (*Set) Empty

func (s *Set) Empty() bool

Empty reports whether the Set has no members (Ruby Set#empty?).

func (*Set) EqualQ

func (s *Set) EqualQ(other *Set) bool

EqualQ reports whether the two sets have the same members (Ruby Set#==).

func (*Set) FlattenSet

func (s *Set) FlattenSet() *Set

FlattenSet returns a new Set in which every member that is itself a *Set is replaced by its members, recursively (Ruby Set#flatten). The receiver is unchanged; the result uses the receiver's Hasher.

func (*Set) GroupBy

func (s *Set) GroupBy(fn func(elem any) any) *GroupByResult

GroupBy groups the members by fn(member) like Classify, but each bucket holds a slice in insertion order rather than a Set (Ruby Enumerable#group_by returns a Hash{value => Array}).

func (*Set) Include

func (s *Set) Include(elem any) bool

Include reports whether elem is a member (Ruby Set#include? / #member? / #===).

func (*Set) Inspect

func (s *Set) Inspect(stringFn func(elem any) string) string

Inspect renders the Set in MRI 4.0 form: "Set[1, 2, 3]" (an empty set is "Set[]"), members in insertion order via stringFn. stringFn produces each member's Ruby inspect; pass DefaultInspect for Go's %#v rendering.

func (*Set) IntersectQ

func (s *Set) IntersectQ(other *Set) bool

IntersectQ reports whether the two sets share at least one member (Ruby Set#intersect?), the negation of DisjointQ.

func (*Set) Intersection

func (s *Set) Intersection(other *Set) *Set

Intersection returns a new Set with the members in both (Ruby Set#& / #intersection). Order follows the receiver. It scans the smaller operand so the work is bounded by the smaller size, mapping back to the receiver's order when the receiver is the larger one.

func (*Set) Map

func (s *Set) Map(fn func(elem any) any) []any

Map applies fn to each member in insertion order and returns the results as a slice (Ruby Set#map / #collect returns an Array, not a Set).

func (*Set) Merge

func (s *Set) Merge(others ...*Set) *Set

Merge adds every member of each other Set, returning the receiver for chaining (Ruby Set#merge). The receiver is mutated.

func (*Set) MergeSlice

func (s *Set) MergeSlice(elems []any) *Set

MergeSlice adds every element of the slice, returning the receiver for chaining (Ruby Set#merge accepts any enumerable; this is the slice form).

func (*Set) ProperSubsetQ

func (s *Set) ProperSubsetQ(other *Set) bool

ProperSubsetQ reports whether the receiver is a subset of other and not equal to it (Ruby Set#proper_subset? / #<).

func (*Set) ProperSupersetQ

func (s *Set) ProperSupersetQ(other *Set) bool

ProperSupersetQ reports whether the receiver is a superset of other and not equal to it (Ruby Set#proper_superset? / #>).

func (*Set) Reject

func (s *Set) Reject(fn func(elem any) bool) *Set

Reject returns a new Set of the members for which fn is false (Ruby Set#reject).

func (*Set) Select

func (s *Set) Select(fn func(elem any) bool) *Set

Select returns a new Set of the members for which fn is true (Ruby Set#select / #filter).

func (*Set) Size

func (s *Set) Size() int

Size returns the number of members (Ruby Set#size / #length / #count).

func (*Set) SortedSlice

func (s *Set) SortedSlice(less func(a, b any) bool) []any

SortedSlice returns the members sorted by less, leaving the Set unchanged. MRI 4.0 dropped the SortedSet class from core (Set#sort returns an Array), so this is the faithful equivalent of Ruby's Set#sort.

func (*Set) String

func (s *Set) String() string

String renders the Set with DefaultInspect, satisfying fmt.Stringer.

func (*Set) SubsetQ

func (s *Set) SubsetQ(other *Set) bool

SubsetQ reports whether every member of the receiver is in other (Ruby Set#subset? / #<=).

func (*Set) Subtract

func (s *Set) Subtract(other *Set) *Set

Subtract removes every member of other, returning the receiver for chaining (Ruby Set#subtract). The receiver is mutated.

func (*Set) SubtractSlice

func (s *Set) SubtractSlice(elems []any) *Set

SubtractSlice removes every element of the slice, returning the receiver for chaining (Ruby Set#subtract accepts any enumerable; this is the slice form).

func (*Set) SupersetQ

func (s *Set) SupersetQ(other *Set) bool

SupersetQ reports whether every member of other is in the receiver (Ruby Set#superset? / #>=).

func (*Set) ToSlice

func (s *Set) ToSlice() []any

ToSlice returns the members as a slice in insertion order (Ruby Set#to_a).

func (*Set) Union

func (s *Set) Union(other *Set) *Set

Union returns a new Set with the members of the receiver and other (Ruby Set#| / #+ / #union). The receiver's order comes first, then other's. It fills in a single pass over each operand with a result map pre-grown to the worst-case size, so neither operand's members are re-hashed by an Add probe.

func (*Set) XorSym

func (s *Set) XorSym(other *Set) *Set

XorSym returns a new Set with the members in exactly one of the two (Ruby Set#^, the symmetric difference (s | other) - (s & other)).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL