ruby/set.c
2026-07-27 14:00:41 -07:00

2602 lines
68 KiB
C

/* This implements sets using the same hash table implementation as in
st.c, but without a value for each hash entry. This results in the
same basic performance characteristics as when using an st table,
but uses 1/3 less memory.
*/
#include "id.h"
#include "internal.h"
#include "internal/bits.h"
#include "internal/error.h"
#include "internal/hash.h"
#include "internal/object.h"
#include "internal/proc.h"
#include "internal/sanitizers.h"
#include "internal/set_table.h"
#include "internal/symbol.h"
#include "internal/variable.h"
#include "ruby_assert.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <string.h>
#ifndef SET_DEBUG
#define SET_DEBUG 0
#endif
#if SET_DEBUG
#include "internal/gc.h"
#endif
static st_index_t
dbl_to_index(double d)
{
union {double d; st_index_t i;} u;
u.d = d;
return u.i;
}
static const uint64_t prime1 = ((uint64_t)0x2e0bb864 << 32) | 0xe9ea7df5;
static const uint32_t prime2 = 0x830fcab9;
static inline uint64_t
mult_and_mix(uint64_t m1, uint64_t m2)
{
#if defined HAVE_UINT128_T
uint128_t r = (uint128_t) m1 * (uint128_t) m2;
return (uint64_t) (r >> 64) ^ (uint64_t) r;
#else
uint64_t hm1 = m1 >> 32, hm2 = m2 >> 32;
uint64_t lm1 = m1, lm2 = m2;
uint64_t v64_128 = hm1 * hm2;
uint64_t v32_96 = hm1 * lm2 + lm1 * hm2;
uint64_t v1_32 = lm1 * lm2;
return (v64_128 + (v32_96 >> 32)) ^ ((v32_96 << 32) + v1_32);
#endif
}
static inline uint64_t
key64_hash(uint64_t key, uint32_t seed)
{
return mult_and_mix(key + seed, prime1);
}
/* Should cast down the result for each purpose */
#define set_index_hash(index) key64_hash(rb_hash_start(index), prime2)
static st_index_t
set_ident_hash(st_data_t n)
{
#ifdef USE_FLONUM /* RUBY */
/*
* - flonum (on 64-bit) is pathologically bad, mix the actual
* float value in, but do not use the float value as-is since
* many integers get interpreted as 2.0 or -2.0 [Bug #10761]
*/
if (FLONUM_P(n)) {
n ^= dbl_to_index(rb_float_value(n));
}
#endif
return (st_index_t)set_index_hash((st_index_t)n);
}
static const struct st_hash_type identhash = {
rb_st_numcmp,
set_ident_hash,
};
static const struct st_hash_type objhash = {
rb_any_cmp,
rb_any_hash,
};
VALUE rb_cSet;
#define id_each idEach
static ID id_each_entry;
static ID id_any_p;
static ID id_new;
static ID id_i_hash;
static ID id_set_iter_lev;
static ID id_subclass_compatible;
static ID id_class_methods;
#define RSET_INITIALIZED FL_USER1
#define RSET_LEV_MASK (FL_USER13 | FL_USER14 | FL_USER15 | /* FL 13..19 */ \
FL_USER16 | FL_USER17 | FL_USER18 | FL_USER19)
#define RSET_LEV_SHIFT (FL_USHIFT + 13)
#define RSET_LEV_MAX 127 /* 7 bits */
#define SET_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(SET_DEBUG, expr, #expr)
#define RSET_SIZE(set) set_table_size(RSET_TABLE(set))
#define RSET_EMPTY(set) (RSET_SIZE(set) == 0)
#define RSET_SIZE_NUM(set) SIZET2NUM(RSET_SIZE(set))
#define RSET_IS_MEMBER(set, item) set_table_lookup(RSET_TABLE(set), (st_data_t)(item))
#define RSET_COMPARE_BY_IDENTITY(set) (RSET_TABLE(set)->type == &identhash)
struct set_object {
set_table table;
};
static int
mark_and_pin_key(st_data_t key, st_data_t data)
{
rb_gc_mark((VALUE)key);
return ST_CONTINUE;
}
static int
mark_key(st_data_t key, st_data_t data)
{
rb_gc_mark_movable((VALUE)key);
return ST_CONTINUE;
}
static void
set_mark(void *ptr)
{
struct set_object *sobj = ptr;
if (sobj->table.entries) {
if (sobj->table.type == &identhash) {
set_table_foreach(&sobj->table, mark_and_pin_key, 0);
}
else {
set_table_foreach(&sobj->table, mark_key, 0);
}
}
}
static void
set_free(void *ptr)
{
struct set_object *sobj = ptr;
set_free_embedded_table(&sobj->table);
}
static size_t
set_size(const void *ptr)
{
const struct set_object *sobj = ptr;
/* Do not count the table size twice, as it is embedded */
return (unsigned long)set_memsize(&sobj->table) - sizeof(sobj->table);
}
static int
set_foreach_replace(st_data_t key, st_data_t argp, int error)
{
if (rb_gc_location((VALUE)key) != (VALUE)key) {
return ST_REPLACE;
}
return ST_CONTINUE;
}
static int
set_replace_ref(st_data_t *key, st_data_t argp, int existing)
{
rb_gc_mark_and_move((VALUE *)key);
return ST_CONTINUE;
}
static void
set_update_references(void *ptr)
{
struct set_object *sobj = ptr;
set_foreach_with_replace(&sobj->table, set_foreach_replace, set_replace_ref, 0);
}
static const rb_data_type_t set_data_type = {
.wrap_struct_name = "set",
.function = {
.dmark = set_mark,
.dfree = set_free,
.dsize = set_size,
.dcompact = set_update_references,
},
.flags = RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
};
static inline set_table *
RSET_TABLE(VALUE set)
{
struct set_object *sobj;
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
return &sobj->table;
}
static unsigned long
iter_lev_in_ivar(VALUE set)
{
VALUE levval = rb_ivar_get(set, id_set_iter_lev);
SET_ASSERT(FIXNUM_P(levval));
long lev = FIX2LONG(levval);
SET_ASSERT(lev >= 0);
return (unsigned long)lev;
}
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
static void
iter_lev_in_ivar_set(VALUE set, unsigned long lev)
{
SET_ASSERT(lev >= RSET_LEV_MAX);
SET_ASSERT(POSFIXABLE(lev)); /* POSFIXABLE means fitting to long */
rb_ivar_set_internal(set, id_set_iter_lev, LONG2FIX((long)lev));
}
static inline unsigned long
iter_lev_in_flags(VALUE set)
{
return (unsigned long)((RBASIC(set)->flags >> RSET_LEV_SHIFT) & RSET_LEV_MAX);
}
static inline void
iter_lev_in_flags_set(VALUE set, unsigned long lev)
{
SET_ASSERT(lev <= RSET_LEV_MAX);
RBASIC(set)->flags = ((RBASIC(set)->flags & ~RSET_LEV_MASK) | ((VALUE)lev << RSET_LEV_SHIFT));
}
static inline bool
set_iterating_p(VALUE set)
{
return iter_lev_in_flags(set) > 0;
}
static void
set_iter_lev_inc(VALUE set)
{
unsigned long lev = iter_lev_in_flags(set);
if (lev == RSET_LEV_MAX) {
lev = iter_lev_in_ivar(set) + 1;
if (!POSFIXABLE(lev)) { /* paranoiac check */
rb_raise(rb_eRuntimeError, "too much nested iterations");
}
}
else {
lev += 1;
iter_lev_in_flags_set(set, lev);
if (lev < RSET_LEV_MAX) return;
}
iter_lev_in_ivar_set(set, lev);
}
static void
set_iter_lev_dec(VALUE set)
{
unsigned long lev = iter_lev_in_flags(set);
if (lev == RSET_LEV_MAX) {
lev = iter_lev_in_ivar(set);
if (lev > RSET_LEV_MAX) {
iter_lev_in_ivar_set(set, lev-1);
return;
}
rb_attr_delete(set, id_set_iter_lev);
}
else if (lev == 0) {
rb_raise(rb_eRuntimeError, "iteration level underflow");
}
iter_lev_in_flags_set(set, lev - 1);
}
static VALUE
set_foreach_ensure(VALUE set)
{
set_iter_lev_dec(set);
return 0;
}
typedef int set_foreach_func(VALUE, VALUE);
struct set_foreach_arg {
VALUE set;
set_foreach_func *func;
VALUE arg;
};
static int
set_iter_status_check(int status)
{
if (status == ST_CONTINUE) {
return ST_CHECK;
}
return status;
}
static int
set_foreach_iter(st_data_t key, st_data_t argp, int error)
{
struct set_foreach_arg *arg = (struct set_foreach_arg *)argp;
if (error) return ST_STOP;
set_table *tbl = RSET_TABLE(arg->set);
int status = (*arg->func)((VALUE)key, arg->arg);
if (RSET_TABLE(arg->set) != tbl) {
rb_raise(rb_eRuntimeError, "reset occurred during iteration");
}
return set_iter_status_check(status);
}
static VALUE
set_foreach_call(VALUE arg)
{
VALUE set = ((struct set_foreach_arg *)arg)->set;
int ret = 0;
ret = set_foreach_check(RSET_TABLE(set), set_foreach_iter,
(st_data_t)arg, (st_data_t)Qundef);
if (ret) {
rb_raise(rb_eRuntimeError, "ret: %d, set modified during iteration", ret);
}
return Qnil;
}
static void
set_iter(VALUE set, set_foreach_func *func, VALUE farg)
{
struct set_foreach_arg arg;
if (RSET_EMPTY(set))
return;
arg.set = set;
arg.func = func;
arg.arg = farg;
if (RB_OBJ_FROZEN(set)) {
set_foreach_call((VALUE)&arg);
}
else {
set_iter_lev_inc(set);
rb_ensure(set_foreach_call, (VALUE)&arg, set_foreach_ensure, set);
}
}
NORETURN(static void no_new_item(void));
static void
no_new_item(void)
{
rb_raise(rb_eRuntimeError, "can't add a new item into set during iteration");
}
static void
set_compact_after_delete(VALUE set)
{
if (!set_iterating_p(set)) {
set_compact_table(RSET_TABLE(set));
}
}
static int
set_table_insert_wb(set_table *tab, VALUE set, VALUE key)
{
if (tab->type != &identhash && rb_obj_class(key) == rb_cString && !RB_OBJ_FROZEN(key)) {
key = rb_hash_key_str(key);
}
int ret = set_insert(tab, (st_data_t)key);
if (ret == 0) RB_OBJ_WRITTEN(set, Qundef, key);
return ret;
}
static int
set_insert_wb(VALUE set, VALUE key)
{
return set_table_insert_wb(RSET_TABLE(set), set, key);
}
static VALUE
set_alloc_with_size(VALUE klass, st_index_t size)
{
VALUE set;
struct set_object *sobj;
set = TypedData_Make_Struct(klass, struct set_object, &set_data_type, sobj);
set_init_table_with_size(&sobj->table, &objhash, size);
return set;
}
static VALUE
set_s_alloc(VALUE klass)
{
return set_alloc_with_size(klass, 0);
}
/*
* call-seq:
* Set[*objects] -> new_set
*
* Returns a new set populated with the given +objects+:
*
* Set[1, 'one', :one, 1.0, %w[a b c], {foo: 0, bar: 1}]
* # => Set[1, "one", :one, 1.0, ["a", "b", "c"], {foo: 0, bar: 1}]
* Set[Set[0, 1, 2], Set[%w[a b c]]]
* # => Set[Set[0, 1, 2], Set[["a", "b", "c"]]]
* Set[] # => Set[]
*
* Related: see {Methods for Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set].
*
*/
static VALUE
set_s_create(int argc, VALUE *argv, VALUE klass)
{
VALUE set = set_alloc_with_size(klass, argc);
set_table *table = RSET_TABLE(set);
int i;
for (i=0; i < argc; i++) {
set_table_insert_wb(table, set, argv[i]);
}
return set;
}
static VALUE
set_s_inherited(VALUE klass, VALUE subclass)
{
if (klass == rb_cSet) {
// When subclassing directly from Set, include the compatibility layer
rb_require("set/subclass_compatible.rb");
VALUE subclass_compatible = rb_const_get(klass, id_subclass_compatible);
rb_include_module(subclass, subclass_compatible);
rb_extend_object(subclass, rb_const_get(subclass_compatible, id_class_methods));
}
return Qnil;
}
static void
check_set(VALUE arg)
{
if (!rb_obj_is_kind_of(arg, rb_cSet)) {
rb_raise(rb_eArgError, "value must be a set");
}
}
static ID
enum_method_id(VALUE other)
{
if (rb_respond_to(other, id_each_entry)) {
return id_each_entry;
}
else if (rb_respond_to(other, id_each)) {
return id_each;
}
else {
rb_raise(rb_eArgError, "value must be enumerable");
}
}
static VALUE
set_enum_size(VALUE set, VALUE args, VALUE eobj)
{
return RSET_SIZE_NUM(set);
}
static VALUE
set_initialize_without_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
{
VALUE element = i;
set_insert_wb(set, element);
return element;
}
static VALUE
set_initialize_with_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
{
VALUE element = rb_yield(i);
set_insert_wb(set, element);
return element;
}
/*
* call-seq:
* Set.new(object = nil) -> new_set
* Set.new(object = nil) {|element| ... } -> new_set
*
* Returns a new set based on the given +object+,
* which must be an Enumerable or +nil+.
*
* With argument +object+ given as +nil+,
* returns a new empty set:
*
* Set.new # => Set[]
* Set.new { fail 'Cannot happen' } # => Set[] # Block not called.
*
* With no block given and enumerable argument +object+ given,
* populates the new set with the elements of +object+:
*
* Set.new(%w[ a b c ]) # => Set["a", "b", "c"]
* Set.new({foo: 0, bar: 1}) # => Set[[:foo, 0], [:bar, 1]]
* Set.new(4..10) # => Set[4, 5, 6, 7, 8, 9, 10]
* Set.new(Dir.new('lib')).take(5)
* # => [".", "..", "bundled_gems.rb", "bundler", "bundler.rb"]
* Set.new(File.new('doc/NEWS/NEWS-4.0.0.md')).take(3)
* # => ["# NEWS for Ruby 4.0.0\n", "\n", "This document is a list of user-visible feature changes\n"]
*
* With a block given and enumerable argument +object+ given,
* calls the block with each element of +object+;
* adds the block's return value to the new set:
*
* Set.new(4..10) {|i| i * 2 } # => Set[8, 10, 12, 14, 16, 18, 20]
*
* Related: see {Methods for Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set].
*
*/
static VALUE
set_i_initialize(int argc, VALUE *argv, VALUE set)
{
if (RBASIC(set)->flags & RSET_INITIALIZED) {
rb_raise(rb_eRuntimeError, "cannot reinitialize set");
}
RBASIC(set)->flags |= RSET_INITIALIZED;
VALUE other;
rb_check_arity(argc, 0, 1);
if (argc > 0 && (other = argv[0]) != Qnil) {
if (RB_TYPE_P(other, T_ARRAY)) {
long i;
int block_given = rb_block_given_p();
set_table *into = RSET_TABLE(set);
for (i=0; i<RARRAY_LEN(other); i++) {
VALUE key = RARRAY_AREF(other, i);
if (block_given) key = rb_yield(key);
set_table_insert_wb(into, set, key);
}
}
else {
rb_block_call(other, enum_method_id(other), 0, 0,
rb_block_given_p() ? set_initialize_with_block : set_initialize_without_block,
set);
}
}
return set;
}
/* :nodoc: */
static VALUE
set_i_initialize_copy(VALUE set, VALUE other)
{
if (set == other) return set;
if (set_iterating_p(set)) {
rb_raise(rb_eRuntimeError, "cannot replace set during iteration");
}
struct set_object *sobj;
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
set_free_embedded_table(&sobj->table);
set_copy(&sobj->table, RSET_TABLE(other));
rb_gc_writebarrier_remember(set);
return set;
}
static int
set_inspect_i(st_data_t key, st_data_t arg)
{
VALUE *args = (VALUE*)arg;
VALUE str = args[0];
if (args[1] == Qtrue) {
rb_str_buf_cat_ascii(str, ", ");
}
else {
args[1] = Qtrue;
}
rb_str_buf_append(str, rb_inspect((VALUE)key));
return ST_CONTINUE;
}
static VALUE
set_inspect(VALUE set, VALUE dummy, int recur)
{
VALUE str;
VALUE klass_name = rb_class_path(CLASS_OF(set));
if (recur) {
str = rb_sprintf("%"PRIsVALUE"[...]", klass_name);
return rb_str_export_to_enc(str, rb_usascii_encoding());
}
str = rb_sprintf("%"PRIsVALUE"[", klass_name);
VALUE args[2] = {str, Qfalse};
set_iter(set, set_inspect_i, (st_data_t)args);
rb_str_buf_cat2(str, "]");
return str;
}
/*
* call-seq:
* inspect -> string
*
* Returns a string representation of +self+:
*
* Set[*%w[foo bar], {foo: 0, bar: 1}].inspect
* # => "Set[\"foo\", \"bar\", {foo: 0, bar: 1}]"
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_inspect(VALUE set)
{
return rb_exec_recursive(set_inspect, set, 0);
}
static int
set_to_a_i(st_data_t key, st_data_t arg)
{
rb_ary_push((VALUE)arg, (VALUE)key);
return ST_CONTINUE;
}
/*
* call-seq:
* to_a -> array
*
* Returns an array containing the elements of +self+:
*
* Set[1, 2].to_a # => [1, 2]
* Set[1, 'c', :s].to_a # => [1, "c", :s]
*
* Related: {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_to_a(VALUE set)
{
st_index_t size = RSET_SIZE(set);
VALUE ary = rb_ary_new_capa(size);
if (size == 0) return ary;
if (ST_DATA_COMPATIBLE_P(VALUE)) {
RARRAY_PTR_USE(ary, ptr, {
size = set_keys(RSET_TABLE(set), ptr, size);
});
rb_gc_writebarrier_remember(ary);
rb_ary_set_len(ary, size);
}
else {
set_iter(set, set_to_a_i, (st_data_t)ary);
}
return ary;
}
/*
* call-seq:
* to_set {|element| ... } -> new_set
* to_set -> self or new_set
*
* With a block given, creates and returns a new set;
* calls the block with each element of +self+,
* and adds the block's returns value to the new set:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.to_set {|i| i * 2 } # => Set[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
*
* With no block given, when +self+ is an instance of +Set+,
* returns +self+:
*
* set = Set[*0..9]
* set.to_set
* set.to_set.equal?(set) # => true
*
* With no block given, when +self+ is an instance of a subclass of +Set+,
* returns a set containing the elements of +self+:
*
* class MySet < Set; end
* my_set = MySet[*0..9] # => #<MySet: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}>
* set = my_set.to_set # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_to_set(VALUE set)
{
if (rb_obj_is_instance_of(set, rb_cSet) && !rb_block_given_p()) {
return set;
}
return rb_funcall_passing_block(rb_cSet, id_new, 1, &set);
}
/*
* call-seq:
* join(separator = $,) -> string
*
* Returns the string formed by joining the string-converted elements of +self+
* with the given +separator+ (defaults to <tt>$,</tt>):
*
* $, # => nil
* Set[*%w[foo bar baz]].join
* # => "foobarbaz"
* Set[*%w[foo bar baz]].join(', ')
* # => "foo, bar, baz"
*
* Flattens nested arrays:
*
* Set[[:foo, [:bar, [:baz, :bat]]]].join
* # => "foobarbazbat"
*
* Does not flatten nested sets:
*
* Set[Set[:foo, Set[:bar, Set[:baz, :bat]]]].join
* # => "Set[:foo, Set[:bar, Set[:baz, :bat]]]"
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_join(int argc, VALUE *argv, VALUE set)
{
rb_check_arity(argc, 0, 1);
return rb_ary_join(set_i_to_a(set), argc == 0 ? Qnil : argv[0]);
}
/*
* call-seq:
* add(object) -> self
*
* Adds the given +object+ to +self+; returns +self+:
*
* set = Set[0, 1, 2]
* set.add(%w[a b c]) # => Set[0, 1, 2, ["a", "b", "c"]]
* set.add(0) # => Set[0, 1, 2, ["a", "b", "c"]]
*
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
*/
static VALUE
set_i_add(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_iterating_p(set)) {
if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
no_new_item();
}
}
else {
set_insert_wb(set, item);
}
return set;
}
/*
* call-seq:
* add?(object) -> self or nil
*
* Like #add, but returns +nil+ if the given +object+ is already in +self+:
*
* set = Set[0, 1, 2]
* set.add?(:foo) # => Set[0, 1, 2, :foo]
* set.add?(0..9) # => Set[0, 1, 2, :foo, 0..9]
* set.add?(2) # => nil
*
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
*/
static VALUE
set_i_add_p(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_iterating_p(set)) {
if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
no_new_item();
}
return Qnil;
}
else {
return set_insert_wb(set, item) ? Qnil : set;
}
}
/*
* call-seq:
* delete(object) -> self
*
* Removes the given +object+ from +self+ if +self+ includes the object;
* returns +self+:
*
* set = Set[0, 'zero', :zero]
* set.delete(0) # => Set["zero", :zero]
* set.delete(:nosuch) # => Set["zero", :zero]
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_delete(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
set_compact_after_delete(set);
}
return set;
}
/*
* call-seq:
* delete?(object) -> self or nil
*
* Like #delete, but returns +nil+ if the object is not in +self+:
*
* set = Set[0, 'zero', :zero]
* set.delete?(0) # => Set["zero", :zero]
* set.delete?(0) # => nil
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_delete_p(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
set_compact_after_delete(set);
return set;
}
return Qnil;
}
static int
set_delete_if_i(st_data_t key, st_data_t dummy)
{
return RTEST(rb_yield((VALUE)key)) ? ST_DELETE : ST_CONTINUE;
}
/*
* call-seq:
* delete_if {|element| ... } -> self
* delete_if -> enumerator
*
* With a block given, calls the block with each element in +self+;
* removes the element if the block returns a truthy value:
*
* set = Set[*0..9]
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.delete_if {|element| element.even? }
* # => Set[1, 3, 5, 7, 9]
*
* With no block given, returns an Enumerator.
*
* Related: {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_delete_if(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
rb_check_frozen(set);
set_iter(set, set_delete_if_i, 0);
set_compact_after_delete(set);
return set;
}
/*
* call-seq:
* reject! {|element| ... } -> self or nil
* reject! -> enumerator
*
* With a block given, like #delete_if, but returns +nil+ if no changes were made:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.reject! {|element| element.even? } # => Set[1, 3, 5, 7, 9]
* set.reject! {|element| element.even? } # => nil
* set.reject! {|element| element.odd? } # => Set[]
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_reject(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
rb_check_frozen(set);
set_table *table = RSET_TABLE(set);
size_t n = set_table_size(table);
set_iter(set, set_delete_if_i, 0);
if (n == set_table_size(table)) return Qnil;
set_compact_after_delete(set);
return set;
}
static int
set_classify_i(st_data_t key, st_data_t tmp)
{
VALUE* args = (VALUE*)tmp;
VALUE hash = args[0];
VALUE hash_key = rb_yield(key);
VALUE set = rb_hash_lookup2(hash, hash_key, Qundef);
if (set == Qundef) {
set = set_s_alloc(args[1]);
rb_hash_aset(hash, hash_key, set);
}
set_i_add(set, key);
return ST_CONTINUE;
}
/*
* call-seq:
* classify {|element| ... } -> hash
* classify -> enumerator
*
* With a block given, calls the block with each element of +self+;
* returns a hash whose keys are the block's return values.
* The value for each key is a set containing the elements
* for which the block returned that key.
*
* This example classifies elements by their classes:
*
* set = Set[*(5..7), *%w[foo bar]] # => Set[5, 6, 7, "foo", "bar"]
* set.classify {|element| element.class }
* # => {Integer => Set[5, 6, 7], String => Set["foo", "bar"]}
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_classify(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
VALUE args[2];
args[0] = rb_hash_new();
args[1] = rb_obj_class(set);
set_iter(set, set_classify_i, (st_data_t)args);
return args[0];
}
// Union-find with path compression
static long
set_divide_union_find_root(long *uf_parents, long index, long *tmp_array)
{
long root = uf_parents[index];
long update_size = 0;
while (root != index) {
tmp_array[update_size++] = index;
index = root;
root = uf_parents[index];
}
for (long j = 0; j < update_size; j++) {
long idx = tmp_array[j];
uf_parents[idx] = root;
}
return root;
}
static void
set_divide_union_find_merge(long *uf_parents, long i, long j, long *tmp_array)
{
long root_i = set_divide_union_find_root(uf_parents, i, tmp_array);
long root_j = set_divide_union_find_root(uf_parents, j, tmp_array);
if (root_i != root_j) uf_parents[root_j] = root_i;
}
static VALUE
set_divide_arity2(VALUE set)
{
VALUE tmp, uf;
long size, *uf_parents, *tmp_array;
VALUE set_class = rb_obj_class(set);
VALUE items = set_i_to_a(set);
rb_ary_freeze(items);
size = RARRAY_LEN(items);
tmp_array = ALLOCV_N(long, tmp, size);
uf_parents = ALLOCV_N(long, uf, size);
for (long i = 0; i < size; i++) {
uf_parents[i] = i;
}
for (long i = 0; i < size - 1; i++) {
VALUE item1 = RARRAY_AREF(items, i);
for (long j = i + 1; j < size; j++) {
VALUE item2 = RARRAY_AREF(items, j);
if (RTEST(rb_yield_values(2, item1, item2)) &&
RTEST(rb_yield_values(2, item2, item1))) {
set_divide_union_find_merge(uf_parents, i, j, tmp_array);
}
}
}
VALUE final_set = set_s_create(0, 0, rb_cSet);
VALUE hash = rb_hash_new();
for (long i = 0; i < size; i++) {
VALUE v = RARRAY_AREF(items, i);
long root = set_divide_union_find_root(uf_parents, i, tmp_array);
VALUE set = rb_hash_aref(hash, LONG2FIX(root));
if (set == Qnil) {
set = set_s_create(0, 0, set_class);
rb_hash_aset(hash, LONG2FIX(root), set);
set_i_add(final_set, set);
}
set_i_add(set, v);
}
ALLOCV_END(tmp);
ALLOCV_END(uf);
return final_set;
}
static void set_merge_enum_into(VALUE set, VALUE arg);
/*
* call-seq:
* divide {|ele| ... } -> new_set
* divide {|ele0, ele1| ... } -> new_set
* divide -> enumerator
*
* With a block given, returns a set of sets.
*
* For a block that accepts one argument,
* calls the block with each element;
* creates a set for each distinct block return value:
*
* set = Set[*0..9]
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* # Divide into mod 3 sets.
* set.divide {|ele| ele % 3 }
* # => Set[Set[0, 3, 6, 9], Set[1, 4, 7], Set[2, 5, 8]]
* # Divide into mod 5 sets.
* set.divide {|ele| ele % 5 }
* # => Set[Set[0, 5], Set[1, 6], Set[2, 7], Set[3, 8], Set[4, 9]]
*
* Set[0].divide {|ele| anything } # => Set[Set[0]]
* Set[].divide {|ele| not called } # => Set[]
*
* For a block that accepts two arguments,
* divides +self+ into connected components based on the binary
* relation defined by the block, calling the block with each 2-element
* permutation of the elements of +self+:
*
* set = Set[*0..9]
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* # Divide into mod 2 sets.
* set.divide {|i, j| (i - j) % 2 == 0 }
* # => Set[Set[0, 2, 4, 6, 8], Set[1, 3, 5, 7, 9]]
* # Divide into mod 3 sets.
* set.divide {|i, j| (i - j) % 3 == 0 }
* # => Set[Set[0, 3, 6, 9], Set[1, 4, 7], Set[2, 5, 8]]
*
* Set[0].divide {|i, j| not called } # => Set[Set[0]]
* Set[].divide {|i, j| not called } # => Set[]
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_divide(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
if (rb_block_arity() == 2) {
return set_divide_arity2(set);
}
VALUE values = rb_hash_values(set_i_classify(set));
set = set_alloc_with_size(rb_cSet, RARRAY_LEN(values));
set_merge_enum_into(set, values);
return set;
}
static int
set_clear_i(st_data_t key, st_data_t dummy)
{
return ST_DELETE;
}
/*
* call-seq:
* clear -> self
*
* Returns +self+ with all elements removed:
*
* Set[1, :one, 'one', 1.0].clear # => Set[]
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_clear(VALUE set)
{
rb_check_frozen(set);
if (RSET_SIZE(set) == 0) return set;
if (set_iterating_p(set)) {
set_iter(set, set_clear_i, 0);
}
else {
set_table_clear(RSET_TABLE(set));
set_compact_after_delete(set);
}
return set;
}
struct set_intersection_data {
VALUE set;
set_table *into;
set_table *other;
};
static int
set_intersection_i(st_data_t key, st_data_t tmp)
{
struct set_intersection_data *data = (struct set_intersection_data *)tmp;
if (set_table_lookup(data->other, key)) {
set_table_insert_wb(data->into, data->set, key);
}
return ST_CONTINUE;
}
static VALUE
set_intersection_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, data))
{
set_intersection_i((st_data_t)i, (st_data_t)data);
return i;
}
/*
* call-seq:
* self & enumerable -> new_set
*
* Returns a new set containing the {intersection}[https://en.wikipedia.org/wiki/Intersection_(set_theory)]
* of +self+ and +enumerable+;
* that is, containing all elements common to both, with no duplicates.
* Argument +enumerable+ must be an Enumerable object:
*
* set = Set[*(0..6), *%w[ a b c]] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
* set & ['c', 6, 8, 4] # => Set["c", 6, 4]
* set & [:foo, :bar] # => Set[] # No elements in common.
*
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
*/
static VALUE
set_i_intersection(VALUE set, VALUE other)
{
VALUE new_set = set_s_alloc(rb_obj_class(set));
set_table *stable = RSET_TABLE(set);
set_table *ntable = RSET_TABLE(new_set);
if (rb_obj_is_kind_of(other, rb_cSet)) {
set_table *otable = RSET_TABLE(other);
if (set_table_size(stable) >= set_table_size(otable)) {
/* Swap so we iterate over the smaller set */
otable = stable;
set = other;
}
struct set_intersection_data data = {
.set = new_set,
.into = ntable,
.other = otable
};
set_iter(set, set_intersection_i, (st_data_t)&data);
}
else {
struct set_intersection_data data = {
.set = new_set,
.into = ntable,
.other = stable
};
rb_block_call(other, enum_method_id(other), 0, 0, set_intersection_block, (VALUE)&data);
}
return new_set;
}
/*
* call-seq:
* include?(object) -> true or false
*
* Returns whether the given +object+ is an element of +self+:
*
* set = Set[0, :zero, '0']
* set.include?('0') # => true
* set.include?('zero') # => false
*
* Tests equality using `hash` and `eql?`.
*
* Aliased as #===, which means that sets may be used in +case+ expressions:
*
* case :apple
* when Set[:potato, :carrot]
* 'vegetable'
* when Set[:apple, :banana]
* 'fruit'
* else
* 'unknown'
* end
* # => "fruit"
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_include(VALUE set, VALUE item)
{
return RBOOL(RSET_IS_MEMBER(set, item));
}
struct set_merge_args {
VALUE set;
set_table *into;
};
static int
set_merge_i(st_data_t key, st_data_t data)
{
struct set_merge_args *args = (struct set_merge_args *)data;
set_table_insert_wb(args->into, args->set, key);
return ST_CONTINUE;
}
static VALUE
set_merge_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
{
VALUE element = key;
set_insert_wb(set, element);
return element;
}
static void
set_merge_enum_into(VALUE set, VALUE arg)
{
if (rb_obj_is_kind_of(arg, rb_cSet)) {
struct set_merge_args args = {
.set = set,
.into = RSET_TABLE(set)
};
set_iter(arg, set_merge_i, (st_data_t)&args);
}
else if (RB_TYPE_P(arg, T_ARRAY)) {
long i;
set_table *into = RSET_TABLE(set);
for (i=0; i<RARRAY_LEN(arg); i++) {
set_table_insert_wb(into, set, RARRAY_AREF(arg, i));
}
}
else {
rb_block_call(arg, enum_method_id(arg), 0, 0, set_merge_block, (VALUE)set);
}
}
/*
* call-seq:
* merge(*enumerables, **nil) -> self
*
* Adds each element of each of the given +enumerables+ to +self+;
* returns +self+:
*
* set = Set[*0..2] # => Set[0, 1, 2]
* set.merge('a'..'c', %w[foo bar]) # => Set[0, 1, 2, "a", "b", "c", "foo", "bar"]
* set.merge('a'..'c', %w[foo bar]) # => Set[0, 1, 2, "a", "b", "c", "foo", "bar"]
*
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
*
*/
static VALUE
set_i_merge(int argc, VALUE *argv, VALUE set)
{
if (rb_keyword_given_p()) {
rb_raise(rb_eArgError, "no keywords accepted");
}
if (set_iterating_p(set)) {
rb_raise(rb_eRuntimeError, "cannot add to set during iteration");
}
rb_check_frozen(set);
int i;
for (i=0; i < argc; i++) {
set_merge_enum_into(set, argv[i]);
}
return set;
}
static VALUE
set_reset_table_with_type(VALUE set, const struct st_hash_type *type)
{
rb_check_frozen(set);
struct set_object *sobj;
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
set_table *old = &sobj->table;
size_t size = set_table_size(old);
if (size > 0) {
set_table *new = set_init_table_with_size(NULL, type, size);
struct set_merge_args args = {
.set = set,
.into = new
};
set_iter(set, set_merge_i, (st_data_t)&args);
set_free_embedded_table(&sobj->table);
memcpy(&sobj->table, new, sizeof(*new));
SIZED_FREE(new);
}
else {
sobj->table.type = type;
}
return set;
}
/*
* call-seq:
* compare_by_identity -> self
*
* Sets +self+ to compare by object identity
* (rather than by object content, which is the initial setting);
* returns +self+:
*
* set = Set.new
* set.compare_by_identity
* str = +"foo"
* set.add(str)
* # => Set["foo"]
* set.include?(str)
* # => true
* set.add(str)
* # => Set["foo"])
* set.include?(+"foo")
* # => false
* set.add(+"foo")
* # => Set["foo", "foo"])
*
* Once set, the compare-by-identity property may not be unset.
*
* Related: #compare_by_identity?.
*/
static VALUE
set_i_compare_by_identity(VALUE set)
{
if (RSET_COMPARE_BY_IDENTITY(set)) return set;
if (set_iterating_p(set)) {
rb_raise(rb_eRuntimeError, "compare_by_identity during iteration");
}
return set_reset_table_with_type(set, &identhash);
}
/*
* call-seq:
* compare_by_identity? -> true or false
*
* Returns whether +self+ compares elements by object identity
* (rather than by content):
*
* set = Set[]
* set.compare_by_identity? # => false
* set.compare_by_identity
* set.compare_by_identity? # => true
*
* Related: #compare_by_identity;
* see also {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_compare_by_identity_p(VALUE set)
{
return RBOOL(RSET_COMPARE_BY_IDENTITY(set));
}
/*
* call-seq:
* size -> integer
*
* Returns the number of elements in +self+:
*
* Set[*0..9].size # => 10
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_size(VALUE set)
{
return RSET_SIZE_NUM(set);
}
/*
* call-seq:
* empty? -> true or false
*
* Returns whether +self+ contains no elements:
*
* Set[].empty? # => true
* Set[0].empty? # => false
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_empty(VALUE set)
{
return RBOOL(RSET_EMPTY(set));
}
static int
set_xor_i(st_data_t key, st_data_t data)
{
VALUE element = (VALUE)key;
VALUE set = (VALUE)data;
set_table *table = RSET_TABLE(set);
if (set_table_insert_wb(table, set, element)) {
set_table_delete(table, &element);
}
return ST_CONTINUE;
}
/*
* call-seq:
* self ^ enumerable -> new_set
*
* Returns a new set containing
* the {exclusive OR}[https://en.wikipedia.org/wiki/Exclusive_or]
* of +self+ and the given +enumerable+;
* that is, containing each element that is in either +self+ or +enumerable+,
* but not in both:
*
* set = Set[0, 1, 2]
* set ^ Set[1, 2, 3] # => Set[0, 3]
* set ^ Set[2, 1] # => Set[0]
* set ^ Set[2, *('a'..'c')] # => Set[0, 1, "a", "b", "c"]
* set ^ Set[2, 1, 0] # => Set[]
*
* For \Set +set+ and \Enumerable +enumerable+, these expressions are equivalent:
*
* set ^ enumerable
* ((set | enumerable) - (set & enumerable))
*
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
*/
static VALUE
set_i_xor(VALUE set, VALUE other)
{
VALUE new_set = rb_obj_dup(set);
if (rb_obj_is_kind_of(other, rb_cSet)) {
set_iter(other, set_xor_i, (st_data_t)new_set);
}
else {
VALUE tmp = set_s_alloc(rb_cSet);
set_merge_enum_into(tmp, other);
set_iter(tmp, set_xor_i, (st_data_t)new_set);
}
set_compact_after_delete(set);
return new_set;
}
/*
* call-seq:
* self | enumerable -> new_set
*
* Returns a new set containing
* the {union}[https://en.wikipedia.org/wiki/Union_(set_theory)]
* of +self+ and the given +enumerable+;
* that is, containing the elements of both +self+ and +enumerable+.
*
* set = Set[0, 1, 2]
* set | Set[2, 1, 'a'] # => Set[0, 1, 2, "a"]
* set | set # => Set[0, 1, 2]
*
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
*/
static VALUE
set_i_union(VALUE set, VALUE other)
{
set = rb_obj_dup(set);
set_merge_enum_into(set, other);
return set;
}
static int
set_remove_i(st_data_t key, st_data_t from)
{
set_table_delete((struct set_table *)from, (st_data_t *)&key);
return ST_CONTINUE;
}
static VALUE
set_remove_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
{
rb_check_frozen(set);
set_table_delete(RSET_TABLE(set), (st_data_t *)&key);
return key;
}
static void
set_remove_enum_from(VALUE set, VALUE arg)
{
if (rb_obj_is_kind_of(arg, rb_cSet)) {
set_iter(arg, set_remove_i, (st_data_t)RSET_TABLE(set));
}
else {
rb_block_call(arg, enum_method_id(arg), 0, 0, set_remove_block, (VALUE)set);
}
set_compact_after_delete(set);
}
/*
* call-seq:
* subtract(enumerable) -> self
*
* Deletes from +self+ every element found in the given +enumerable+;
* returns +self+:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.subtract(5..14) # => Set[0, 1, 2, 3, 4]
* set.subtract(Set[6, 2]) # => Set[0, 1, 3, 4]
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_subtract(VALUE set, VALUE other)
{
rb_check_frozen(set);
set_remove_enum_from(set, other);
return set;
}
/*
* call-seq:
* self - enumerable -> new_set
*
* Returns a new set containing the
* {difference}[https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement]
* of +self+ and argument +enumerable+;
* that is, containing all elements in +self+ that are not in +enumerable+.
*
*
* set = Set[*(0..6), *%w[ a b c]] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
* set - ['b', 6, 4, 1] # => Set[0, 2, 3, 5, "a", "c"]
* set - ['d', 7, 9] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
*
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
*/
static VALUE
set_i_difference(VALUE set, VALUE other)
{
return set_i_subtract(rb_obj_dup(set), other);
}
static int
set_each_i(st_data_t key, st_data_t dummy)
{
rb_yield(key);
return ST_CONTINUE;
}
/*
* call-seq:
* each {|element| ... } -> self
* each -> enumerator
*
* With a block given, calls the block once for each element in the set,
* passing the element as a parameter;
* returns +self+:
*
* sum = 0
* Set[1, 2, 3].each {|i| sum += i }
* sum # => 6
*
* With no block given, returns an Enumerator.
*/
static VALUE
set_i_each(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
set_iter(set, set_each_i, 0);
return set;
}
static int
set_collect_i(st_data_t key, st_data_t data)
{
set_insert_wb((VALUE)data, rb_yield((VALUE)key));
return ST_CONTINUE;
}
/*
* call-seq:
* collect! {|element| ... } -> self
* collect! -> enumerator
*
* With a block given, calls the block with each element in +self+;
* replaces the element with the block's return value:
*
* Set[1, :one, 'one', 1.0].collect! {|element| element.class }
* # => Set[Integer, Symbol, String, Float]
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_collect(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
rb_check_frozen(set);
VALUE new_set = set_s_alloc(rb_obj_class(set));
set_iter(set, set_collect_i, (st_data_t)new_set);
set_i_initialize_copy(set, new_set);
return set;
}
static int
set_keep_if_i(st_data_t key, st_data_t into)
{
if (!RTEST(rb_yield((VALUE)key))) {
set_table_delete((set_table *)into, &key);
}
return ST_CONTINUE;
}
/*
* call-seq:
* keep_if {|element| ... } -> self
* keep_if -> enumerator
*
* With a block given,
* calls the block with each element in +self+,
* deleting the element if the block returns +false+ or +nil+;
* returns +self+:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.keep_if {|i| i.even? } # => Set[0, 2, 4, 6, 8]
* set.keep_if {|i| i.odd? } # => Set[]
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_keep_if(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
rb_check_frozen(set);
set_iter(set, set_keep_if_i, (st_data_t)RSET_TABLE(set));
set_compact_after_delete(set);
return set;
}
/*
* call-seq:
* select! {|element| ... } -> self or nil
* select! -> enumerator
*
* With a block given, like #keep_if, but returns +nil+ if no changes were made:
*
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* set.select! {|i| i.even? } # => Set[0, 2, 4, 6, 8]
* set.select! {|i| i.even? } # => nil
* set.select! {|i| i.odd? } # => Set[]
*
* With no block given, returns an Enumerator.
*
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
*/
static VALUE
set_i_select(VALUE set)
{
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
rb_check_frozen(set);
set_table *table = RSET_TABLE(set);
size_t n = set_table_size(table);
set_iter(set, set_keep_if_i, (st_data_t)table);
set_compact_after_delete(set);
return (n == set_table_size(table)) ? Qnil : set;
}
/*
* call-seq:
* replace(enumerable) -> self
*
* Replaces the contents +self+ with the contents of the given +enumerable+;
* returns +self+:
*
* set = Set[1, 'c', :s] # => Set[1, "c", :s]
* set.replace([1, 2]) # => Set[1, 2]
*
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
*/
static VALUE
set_i_replace(VALUE set, VALUE other)
{
rb_check_frozen(set);
if (rb_obj_is_kind_of(other, rb_cSet)) {
set_i_initialize_copy(set, other);
}
else {
if (set_iterating_p(set)) {
rb_raise(rb_eRuntimeError, "cannot replace set during iteration");
}
// make sure enum is enumerable before calling clear
enum_method_id(other);
set_table_clear(RSET_TABLE(set));
set_merge_enum_into(set, other);
}
set_compact_after_delete(set);
return set;
}
/*
* call-seq:
* reset -> self
*
* Resets the internal state of +self+; returns +self+.
*
* A set relies on the #hash results of each element being consistent.
* Modifying an element in a way that changes the results of #hash
* may allow duplicate elements in the set:
*
* array = [1]
* set = Set[array] # => Set[[1]]
* array << 2
* set.add(array) # => Set[[1, 2], [1, 2]]
*
* Calling #reset will recalculate all of the hash values and remove
* duplicate elements:
*
* set.reset # => Set[[1, 2]]
*
*/
static VALUE
set_i_reset(VALUE set)
{
if (set_iterating_p(set)) {
rb_raise(rb_eRuntimeError, "reset during iteration");
}
return set_reset_table_with_type(set, RSET_TABLE(set)->type);
}
static void set_flatten_merge(VALUE set, VALUE from, VALUE seen);
static int
set_flatten_merge_i(st_data_t item, st_data_t arg)
{
VALUE *args = (VALUE *)arg;
VALUE set = args[0];
if (rb_obj_is_kind_of(item, rb_cSet)) {
VALUE e_id = rb_obj_id(item);
VALUE hash = args[2];
switch(rb_hash_aref(hash, e_id)) {
case Qfalse:
return ST_CONTINUE;
case Qtrue:
rb_raise(rb_eArgError, "tried to flatten recursive Set");
default:
break;
}
rb_hash_aset(hash, e_id, Qtrue);
set_flatten_merge(set, item, hash);
rb_hash_aset(hash, e_id, Qfalse);
}
else {
set_i_add(set, item);
}
return ST_CONTINUE;
}
static void
set_flatten_merge(VALUE set, VALUE from, VALUE hash)
{
VALUE args[3] = {set, from, hash};
set_iter(from, set_flatten_merge_i, (st_data_t)args);
}
/*
* call-seq:
* flatten -> new_set
*
* Returns a new set that is a copy of +self+,
* but with +self+ and its nested sets flattened;
* that is, their elements become elements of +self+:
*
* Set[Set[0, 1], Set[2, 3]].flatten
* # => Set[0, 1, 2, 3]
* Set[Set[0, 1], Set[Set[2, 3], Set[3, 4]]].flatten
* # => Set[0, 1, 2, 3, 4]
*
* Does not flatten nested arrays or hashes:
*
* Set[%w[foo bar]].flatten # => Set[["foo", "bar"]]
* Set[{foo: 0, bar: 1}].flatten # => Set[{foo: 0, bar: 1}]
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
static VALUE
set_i_flatten(VALUE set)
{
VALUE new_set = set_s_alloc(rb_obj_class(set));
set_flatten_merge(new_set, set, rb_hash_new());
return new_set;
}
static int
set_contains_set_i(st_data_t item, st_data_t arg)
{
if (rb_obj_is_kind_of(item, rb_cSet)) {
*(bool *)arg = true;
return ST_STOP;
}
return ST_CONTINUE;
}
/*
* call-seq:
* flatten! -> self or nil
*
* Like #flatten, but if any changes were made
* replaces +self+ with the result and returns +self+:
*
* Set[Set[0, 1], Set[2, 3]].flatten!
* # => Set[0, 1, 2, 3]
* Set[Set[0, 1], Set[Set[2, 3], Set[3, 4]]].flatten!
* # => Set[0, 1, 2, 3, 4]
*
* Returns +nil+ if no changes were made:
*
* Set[0, 1, 2].flatten! # => nil
*
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
*/
static VALUE
set_i_flatten_bang(VALUE set)
{
bool contains_set = false;
set_iter(set, set_contains_set_i, (st_data_t)&contains_set);
if (!contains_set) return Qnil;
rb_check_frozen(set);
return set_i_replace(set, set_i_flatten(set));
}
struct set_subset_data {
set_table *table;
VALUE result;
};
static int
set_le_i(st_data_t key, st_data_t arg)
{
struct set_subset_data *data = (struct set_subset_data *)arg;
if (set_table_lookup(data->table, key)) return ST_CONTINUE;
data->result = Qfalse;
return ST_STOP;
}
static VALUE
set_le(VALUE set, VALUE other)
{
struct set_subset_data data = {
.table = RSET_TABLE(other),
.result = Qtrue
};
set_iter(set, set_le_i, (st_data_t)&data);
return data.result;
}
/*
* call-seq:
* proper_subset?(other_set) -> true or false
*
* Returns whether +self+ is
* a {proper subset}[https://en.wikipedia.org/wiki/Subset]
* of the given +other_set+:
*
* set = Set[*'b'..'e']
* set.proper_subset?(set) # => false
* set.proper_subset?(Set[*'a'..'f']) # => true
*
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_proper_subset(VALUE set, VALUE other)
{
check_set(other);
if (RSET_SIZE(set) >= RSET_SIZE(other)) return Qfalse;
return set_le(set, other);
}
/*
* call-seq:
* subset?(other_set) -> true or false
*
* Returns whether +self+ is a {subset}[https://en.wikipedia.org/wiki/Subset]
* of the given +other_set+:
*
* set = Set[*'b'..'e']
* set.subset?(set) # => true
* set.subset?(Set[*'a'..'f']) # => true
* set.subset?(Set[*'c'..'e']) # => false
*
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_subset(VALUE set, VALUE other)
{
check_set(other);
if (RSET_SIZE(set) > RSET_SIZE(other)) return Qfalse;
return set_le(set, other);
}
/*
* call-seq:
* proper_superset?(other_set) -> true or false
*
* Returns whether +self+ is
* a {proper superset}[https://en.wikipedia.org/wiki/Subset]
* of the given +other_set+:
*
* set = Set[*'a'..'f']
* set.proper_superset?(set) # => false
* set.proper_superset?(Set[*'b'..'e']) # => true
*
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_proper_superset(VALUE set, VALUE other)
{
check_set(other);
if (RSET_SIZE(set) <= RSET_SIZE(other)) return Qfalse;
return set_le(other, set);
}
/*
* call-seq:
* superset?(other_set) -> true or false
*
* Returns whether +self+ is a {superset}[https://en.wikipedia.org/wiki/Subset]
* of the given +other_set+:
*
* set = Set[*'a'..'f'] # => Set["a", "b", "c", "d", "e", "f"]
* set.superset?(set) # => true
* set.superset?(Set[*'b'..'e']) # => true
* set.superset?(Set[*'b'..'x']) # => false
*
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_superset(VALUE set, VALUE other)
{
check_set(other);
if (RSET_SIZE(set) < RSET_SIZE(other)) return Qfalse;
return set_le(other, set);
}
static int
set_intersect_i(st_data_t key, st_data_t arg)
{
VALUE *args = (VALUE *)arg;
if (set_table_lookup((set_table *)args[0], key)) {
args[1] = Qtrue;
return ST_STOP;
}
return ST_CONTINUE;
}
/*
* call-seq:
* intersect?(enumerable) -> true or false
*
* Returns whether +self+ and +enumerable+ have any elements in common:
*
* set = Set[0, 'zero', :zero]
* set.intersect?([0, 1, 2]) # => true
* set.intersect?(%w[zero one two]) # => true
* set.intersect?(Set[3]) # => false
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_intersect(VALUE set, VALUE other)
{
if (rb_obj_is_kind_of(other, rb_cSet)) {
size_t set_size = RSET_SIZE(set);
size_t other_size = RSET_SIZE(other);
VALUE args[2];
args[1] = Qfalse;
VALUE iter_arg;
if (set_size < other_size) {
iter_arg = set;
args[0] = (VALUE)RSET_TABLE(other);
}
else {
iter_arg = other;
args[0] = (VALUE)RSET_TABLE(set);
}
set_iter(iter_arg, set_intersect_i, (st_data_t)args);
return args[1];
}
else if (rb_obj_is_kind_of(other, rb_mEnumerable)) {
return rb_funcall(other, id_any_p, 1, set);
}
else {
rb_raise(rb_eArgError, "value must be enumerable");
}
}
/*
* call-seq:
* disjoint?(enumerable) -> true or false
*
* Returns whether no element of +enumerable+ is present in +self+:
*
* set = Set[0, 'zero', :zero]
* set.disjoint?([1, 2, 3]) # => true
* set.disjoint?([0, 1, 2, 3]) # => false
*
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
*/
static VALUE
set_i_disjoint(VALUE set, VALUE other)
{
return RBOOL(!RTEST(set_i_intersect(set, other)));
}
/*
* call-seq:
* self <=> object -> -1, 0, 1, or nil
*
* Compares +self+ and +object+.
*
* If +object+ is another set, returns:
*
* - +-1+, if +self+ is a proper subset of +object+.
* - +0+, if +self+ and +object+ have the same elements.
* - +1+, if +self+ is a proper superset of +object+.
* - +nil+, if none of the above;
* that is, if +self+ and +object+ each have one or more elements
* not included in the other.
*
* Examples:
*
* set = Set[0, 1, 2]
* set <=> Set[3, 2, 1, 0] # => -1
* set <=> Set[2, 1, 0] # => 0
* set <=> Set[1, 0] # => 1
* set <=> Set[1, 0, 3] # => nil
*
* Returns +nil+ if +object+ is not a set:
*
* set <=> [2, 1, 0] # => nil # Array, not Set.
*
* Related: see {Methods for Comparing}[rdoc-ref:Set@Methods+for+Comparing].
*/
static VALUE
set_i_compare(VALUE set, VALUE other)
{
if (rb_obj_is_kind_of(other, rb_cSet)) {
size_t set_size = RSET_SIZE(set);
size_t other_size = RSET_SIZE(other);
if (set_size < other_size) {
if (set_le(set, other) == Qtrue) {
return INT2NUM(-1);
}
}
else if (set_size > other_size) {
if (set_le(other, set) == Qtrue) {
return INT2NUM(1);
}
}
else if (set_le(set, other) == Qtrue) {
return INT2NUM(0);
}
}
return Qnil;
}
struct set_equal_data {
VALUE result;
VALUE set;
};
static int
set_eql_i(st_data_t item, st_data_t arg)
{
struct set_equal_data *data = (struct set_equal_data *)arg;
if (!set_table_lookup(RSET_TABLE(data->set), item)) {
data->result = Qfalse;
return ST_STOP;
}
return ST_CONTINUE;
}
static VALUE
set_recursive_eql(VALUE set, VALUE dt, int recur)
{
if (recur) return Qtrue;
struct set_equal_data *data = (struct set_equal_data*)dt;
data->result = Qtrue;
set_iter(set, set_eql_i, dt);
return data->result;
}
/*
* call-seq:
* self == object -> true or false
*
* Returns whether +object+ is a set, and has the same elements as +self+:
*
* set = Set[0, 1, 2]
* set == Set[1, 2, 0] # => true
* set == [1, 2, 3] # => false
* set == Set[1, 2, '3'] # => false
*
* Related: see {Methods for Comparing}[rdoc-ref:Set@Methods+for+Comparing].
*/
static VALUE
set_i_eq(VALUE set, VALUE other)
{
if (!rb_obj_is_kind_of(other, rb_cSet)) return Qfalse;
if (set == other) return Qtrue;
set_table *stable = RSET_TABLE(set);
set_table *otable = RSET_TABLE(other);
size_t ssize = set_table_size(stable);
size_t osize = set_table_size(otable);
if (ssize != osize) return Qfalse;
if (ssize == 0 && osize == 0) return Qtrue;
if (stable->type != otable->type) return Qfalse;
struct set_equal_data data;
data.set = other;
return rb_exec_recursive_paired(set_recursive_eql, set, other, (VALUE)&data);
}
static int
set_hash_i(st_data_t item, st_data_t(arg))
{
st_index_t *hval = (st_index_t *)arg;
st_index_t ival = rb_hash(item);
*hval ^= rb_st_hash(&ival, sizeof(st_index_t), 0);
return ST_CONTINUE;
}
/*
* call-seq:
* hash -> integer
*
* Returns the integer hash value for +self+.
*
* Two sets with the same content have the same hash value.
*
* Set[0, 1].hash == Set[1, 0].hash # => true
* Set[0, 1].hash == Set[0].hash # => false
*/
static VALUE
set_i_hash(VALUE set)
{
st_index_t size = RSET_SIZE(set);
st_index_t hval = rb_st_hash_start(size);
hval = rb_hash_uint(hval, (st_index_t)set_i_hash);
if (size) {
set_iter(set, set_hash_i, (VALUE)&hval);
}
hval = rb_st_hash_end(hval);
return ST2FIX(hval);
}
/* :nodoc: */
static int
set_to_hash_i(st_data_t key, st_data_t arg)
{
rb_hash_aset((VALUE)arg, (VALUE)key, Qtrue);
return ST_CONTINUE;
}
static VALUE
set_i_to_h(VALUE set)
{
st_index_t size = RSET_SIZE(set);
VALUE hash;
if (RSET_COMPARE_BY_IDENTITY(set)) {
hash = rb_ident_hash_new_with_size(size);
}
else {
hash = rb_hash_new_with_size(size);
}
rb_hash_set_default(hash, Qfalse);
if (size == 0) return hash;
set_iter(set, set_to_hash_i, (st_data_t)hash);
return hash;
}
static VALUE
compat_dumper(VALUE set)
{
VALUE dumper = rb_class_allocate_instance_capa(rb_cObject, 1);
rb_ivar_set(dumper, id_i_hash, set_i_to_h(set));
return dumper;
}
static int
set_i_from_hash_i(st_data_t key, st_data_t val, st_data_t set)
{
if ((VALUE)val != Qtrue) {
rb_raise(rb_eRuntimeError, "expect true as Set value: %"PRIsVALUE, rb_obj_class((VALUE)val));
}
set_i_add((VALUE)set, (VALUE)key);
return ST_CONTINUE;
}
static VALUE
set_i_from_hash(VALUE set, VALUE hash)
{
Check_Type(hash, T_HASH);
if (rb_hash_compare_by_id_p(hash)) set_i_compare_by_identity(set);
rb_hash_stlike_foreach(hash, set_i_from_hash_i, (st_data_t)set);
return set;
}
static VALUE
compat_loader(VALUE self, VALUE a)
{
return set_i_from_hash(self, rb_ivar_get(a, id_i_hash));
}
/* C-API functions */
void
rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg)
{
set_iter(set, func, arg);
}
VALUE
rb_set_new(void)
{
return set_alloc_with_size(rb_cSet, 0);
}
VALUE
rb_set_new_capa(size_t capa)
{
return set_alloc_with_size(rb_cSet, (st_index_t)capa);
}
bool
rb_set_lookup(VALUE set, VALUE element)
{
return RSET_IS_MEMBER(set, element);
}
bool
rb_set_add(VALUE set, VALUE element)
{
return set_i_add_p(set, element) != Qnil;
}
VALUE
rb_set_clear(VALUE set)
{
return set_i_clear(set);
}
bool
rb_set_delete(VALUE set, VALUE element)
{
return set_i_delete_p(set, element) != Qnil;
}
size_t
rb_set_size(VALUE set)
{
return RSET_SIZE(set);
}
/*
* Document-class: Set
*
* An instance of class \Set contains a collection
* of objects (elements), with no duplicates.
*
* By default:
*
* - Set determines equality via Object#eql? and Object#hash,
* and assumes that these values do not change for a stored element.
* If these values do change, the set enters an unreliable state;
* see #reset.
* - A String instance added to a set is stored as a frozen copy of the string,
* unless it is already frozen.
*
* Calling #compare_by_identity causes:
*
* - All following determinations of equality
* to use object identity instead of the methods mentioned above.
* - A String added to a set is stored "as is", whether or not frozen.
*
* \Set includes module Enumerable, and is easy to use with other enumerable objects.
* Many of its methods accept enumerable objects as arguments;
* any enumerable object may be converted to a set via #to_set.
*
* == Contact
*
* - Akinori MUSHA <knu@iDaemons.org> (current maintainer)
*
* == Inheriting from \Set
*
* Before Ruby 4.0 (released in December, 2025),
* class \Set had a different, less efficient implementation.
* In Ruby 4.0, the class was reimplemented in C,
* and the behaviors of some methods were adjusted.
*
* When compatibility with the older implementation is needed,
* a \Set subclass should inherit directly from class +Set+;
* this automatically includes module +Set::SubclassCompatible+,
* which makes behaviors closer to those in the older implementation.
*
* A difference may be seen as follows:
*
* Set[[1, 2, 3]] # => Set[[1, 2, 3]]
* class MySet < Set; end
* MySet[[1, 2, 3]] # => #<MySet: {[1, 2, 3]}> # Same as in Ruby 3.4.
*
* When backward compatibility is not needed,
* a \Set subclass should inherit from +Set::CoreSet+,
* which avoids including the compatibility layer:
*
* class MyCoreSet < Set::CoreSet; end
* MyCoreSet[[1, 2, 3]] # => MyCoreSet[[1, 2, 3]]
*
* == What's Here
*
* First, what's elsewhere. \Class \Set:
*
* - Inherits from {class Object}[rdoc-ref:Object@Whats+Here].
* - Includes {module Enumerable}[rdoc-ref:Enumerable@Whats+Here],
* which provides dozens of additional methods.
*
* In particular, class \Set does not have many methods of its own
* for fetching or for iterating.
* Instead, it relies on those in \Enumerable.
*
* Here, class \Set provides methods that are useful for:
*
* - {Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set]
* - {Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations]
* - {Comparing}[rdoc-ref:Set@Methods+for+Comparing]
* - {Querying}[rdoc-ref:Set@Methods+for+Querying]
* - {Assigning}[rdoc-ref:Set@Methods+for+Assigning]
* - {Deleting}[rdoc-ref:Set@Methods+for+Deleting]
* - {Converting}[rdoc-ref:Set@Methods+for+Converting]
* - {And more....}[rdoc-ref:Set@Other+Methods]
*
* === Methods for Creating a \Set
*
* - ::[]:
* Returns a new set populated with the given objects.
* - ::new:
* Returns a new set based on the given object (if no block given),
* or on the return values from the called block (if a block given).
*
* === Methods for \Set Operations
*
* - #& (aliased as #intersection):
* Returns a new set containing the intersection of +self+ and the given enumerable.
* - #- (aliased as #difference):
* Returns a new set containing the difference of +self+ and the given enumerable.
* - #^: Returns a new set containing the exclusive OR of +self+ and the given enumerable.
* - #| (aliased as #union and #+):
* Returns a new set containing the union of +self+ and the given enumerable.
*
* === Methods for Comparing
*
* - #<=>: Returns -1, 0, or 1 as +self+ is less than, equal to,
* or greater than a given object.
* - #==: Returns whether +self+ and a given enumerable are equal,
* as determined by Object#eql?.
* - #compare_by_identity?:
* Returns whether +self+ considers only identity
* when comparing elements.
* - #proper_subset? (aliased as #<):
* Returns whether the given enumerable is a proper subset of +self+.
* - #proper_superset? (aliased as #>):
* Returns whether the given enumerable is a proper superset of +self+.
* - #subset? (aliased as #<=):
* Returns whether the given object is a subset of +self+.
* - #superset? (aliased as #>=):
* Returns whether the given enumerable is a superset of +self+.
*
* === Methods for Querying
*
* - #disjoint?:
* Returns whether no element of the given enumerable is present in +self+.
* - #empty?:
* Returns whether +self+ contains no elements.
* - #include? (aliased as #member? and #===):
* Returns whether the given object is an element of +self+.
* - #intersect?:
* Returns whether +self+ and the given enumerable have any elements in common.
* - #size (aliased as #length):
* Returns the number of elements in +self+.
*
* === Methods for Assigning
*
* - #add (aliased as #<<):
* Adds the given object to +self+; returns +self+.
* - #add?:
* Like #add, but returns +nil+ if the given object is already in +self+.
* - #merge:
* Adds each element of each of the given enumerables to +self+; returns +self+.
* - #replace:
* Replaces the contents of +self+ with the contents of the given enumerable;
* returns +self+.
*
* === Methods for Deleting
*
* - #clear:
* Removes all elements from +self+; returns +self+.
* - #delete:
* Removes the given object from +self+ if +self+ includes the object; returns +self+.
* - #delete?:
* Like #delete, but returns +nil+ if the object is not in +self+.
* - #delete_if:
* Calls the block with each element in +self+;
* removes the element if the block returns a truthy value.
* - #keep_if:
* Calls the block with each element in +self+,
* deleting the element if the block returns +false+ or +nil+; returns +self+.
* - #reject!
* Like #delete_if, but returns +nil+ if no changes were made.
* - #select! (aliased as #filter!):
* Like #keep_if, but returns +nil+ if no changes were made.
* - #subtract:
* Deletes from +self+ every element found in the given enumerable; returns +self+:
*
* === Methods for Converting
*
* - #classify:
* Returns a hash that partitions the elements,
* as determined by the given block.
* - #collect! (aliased as #map!):
* Replaces each element with a block return-value.
* - #divide:
* Returns a set of sets that partition the elements,
* as determined by the given block.
* - #flatten:
* Returns a new set that is a recursive flattening of +self+.
* - #flatten!: Like #flatten, but if any changes were made
* replaces +self+ with the result and returns +self+.
* - #inspect (aliased as #to_s):
* Returns a string representation of +self+.
* - #join:
* Returns the string formed by joining the string-converted elements of +self+
* with the given separator.
* - #to_a:
* Returns an array containing the elements of +self+.
* - #to_set:
* With a block given, creates and returns a new set;
* calls the block with each element of +self+,
* and adds the block's returns value to the new set.
*
* === Other Methods
*
* - #compare_by_identity:
* Sets +self+ to compare by object identity (rather than by object content).
* - #each:
* Calls the block with each successive element of +self+; returns +self+.
* - #reset:
* Resets the internal state of +self+; returns +self+.
* Useful if an element has been modified while an element in the set.
*
*/
void
Init_Set(void)
{
rb_cSet = rb_define_class("Set", rb_cObject);
rb_include_module(rb_cSet, rb_mEnumerable);
id_each_entry = rb_intern_const("each_entry");
id_any_p = rb_intern_const("any?");
id_new = rb_intern_const("new");
id_i_hash = rb_intern_const("@hash");
id_subclass_compatible = rb_intern_const("SubclassCompatible");
id_class_methods = rb_intern_const("ClassMethods");
id_set_iter_lev = rb_make_internal_id();
rb_define_alloc_func(rb_cSet, set_s_alloc);
rb_define_singleton_method(rb_cSet, "[]", set_s_create, -1);
rb_define_method(rb_cSet, "initialize", set_i_initialize, -1);
rb_define_method(rb_cSet, "initialize_copy", set_i_initialize_copy, 1);
rb_define_method(rb_cSet, "&", set_i_intersection, 1);
rb_define_alias(rb_cSet, "intersection", "&");
rb_define_method(rb_cSet, "-", set_i_difference, 1);
rb_define_alias(rb_cSet, "difference", "-");
rb_define_method(rb_cSet, "^", set_i_xor, 1);
rb_define_method(rb_cSet, "|", set_i_union, 1);
rb_define_alias(rb_cSet, "+", "|");
rb_define_alias(rb_cSet, "union", "|");
rb_define_method(rb_cSet, "<=>", set_i_compare, 1);
rb_define_method(rb_cSet, "==", set_i_eq, 1);
rb_define_alias(rb_cSet, "eql?", "==");
rb_define_method(rb_cSet, "add", set_i_add, 1);
rb_define_alias(rb_cSet, "<<", "add");
rb_define_method(rb_cSet, "add?", set_i_add_p, 1);
rb_define_method(rb_cSet, "classify", set_i_classify, 0);
rb_define_method(rb_cSet, "clear", set_i_clear, 0);
rb_define_method(rb_cSet, "collect!", set_i_collect, 0);
rb_define_alias(rb_cSet, "map!", "collect!");
rb_define_method(rb_cSet, "compare_by_identity", set_i_compare_by_identity, 0);
rb_define_method(rb_cSet, "compare_by_identity?", set_i_compare_by_identity_p, 0);
rb_define_method(rb_cSet, "delete", set_i_delete, 1);
rb_define_method(rb_cSet, "delete?", set_i_delete_p, 1);
rb_define_method(rb_cSet, "delete_if", set_i_delete_if, 0);
rb_define_method(rb_cSet, "disjoint?", set_i_disjoint, 1);
rb_define_method(rb_cSet, "divide", set_i_divide, 0);
rb_define_method(rb_cSet, "each", set_i_each, 0);
rb_define_method(rb_cSet, "empty?", set_i_empty, 0);
rb_define_method(rb_cSet, "flatten", set_i_flatten, 0);
rb_define_method(rb_cSet, "flatten!", set_i_flatten_bang, 0);
rb_define_method(rb_cSet, "hash", set_i_hash, 0);
rb_define_method(rb_cSet, "include?", set_i_include, 1);
rb_define_alias(rb_cSet, "member?", "include?");
rb_define_alias(rb_cSet, "===", "include?");
rb_define_method(rb_cSet, "inspect", set_i_inspect, 0);
rb_define_alias(rb_cSet, "to_s", "inspect");
rb_define_method(rb_cSet, "intersect?", set_i_intersect, 1);
rb_define_method(rb_cSet, "join", set_i_join, -1);
rb_define_method(rb_cSet, "keep_if", set_i_keep_if, 0);
rb_define_method(rb_cSet, "merge", set_i_merge, -1);
rb_define_method(rb_cSet, "proper_subset?", set_i_proper_subset, 1);
rb_define_alias(rb_cSet, "<", "proper_subset?");
rb_define_method(rb_cSet, "proper_superset?", set_i_proper_superset, 1);
rb_define_alias(rb_cSet, ">", "proper_superset?");
rb_define_method(rb_cSet, "reject!", set_i_reject, 0);
rb_define_method(rb_cSet, "replace", set_i_replace, 1);
rb_define_method(rb_cSet, "reset", set_i_reset, 0);
rb_define_method(rb_cSet, "size", set_i_size, 0);
rb_define_alias(rb_cSet, "length", "size");
rb_define_method(rb_cSet, "select!", set_i_select, 0);
rb_define_alias(rb_cSet, "filter!", "select!");
rb_define_method(rb_cSet, "subset?", set_i_subset, 1);
rb_define_alias(rb_cSet, "<=", "subset?");
rb_define_method(rb_cSet, "subtract", set_i_subtract, 1);
rb_define_method(rb_cSet, "superset?", set_i_superset, 1);
rb_define_alias(rb_cSet, ">=", "superset?");
rb_define_method(rb_cSet, "to_a", set_i_to_a, 0);
rb_define_method(rb_cSet, "to_set", set_i_to_set, 0);
/* :nodoc: */
VALUE compat = rb_define_class_under(rb_cSet, "compatible", rb_cObject);
rb_marshal_define_compat(rb_cSet, compat, compat_dumper, compat_loader);
// Create Set::CoreSet before defining inherited, so it does not include
// the backwards compatibility layer.
rb_define_class_under(rb_cSet, "CoreSet", rb_cSet);
rb_define_private_method(rb_singleton_class(rb_cSet), "inherited", set_s_inherited, 1);
rb_provide("set.rb");
}