summaryrefslogtreecommitdiff
path: root/set.c
diff options
context:
space:
mode:
Diffstat (limited to 'set.c')
-rw-r--r--set.c458
1 files changed, 276 insertions, 182 deletions
diff --git a/set.c b/set.c
index 8676c62cd3..4d8178ffc0 100644
--- a/set.c
+++ b/set.c
@@ -7,6 +7,7 @@
#include "id.h"
#include "internal.h"
#include "internal/bits.h"
+#include "internal/error.h"
#include "internal/hash.h"
#include "internal/proc.h"
#include "internal/sanitizers.h"
@@ -101,6 +102,8 @@ 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 */ \
@@ -113,7 +116,7 @@ static ID id_set_iter_lev;
#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(sobj, item) set_lookup(RSET_TABLE(set), (st_data_t)(item))
+#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 {
@@ -132,13 +135,12 @@ static void
set_mark(void *ptr)
{
struct set_object *sobj = ptr;
- if (sobj->table.entries) set_foreach(&sobj->table, mark_key, 0);
+ if (sobj->table.entries) set_table_foreach(&sobj->table, mark_key, 0);
}
static void
set_free_embedded(struct set_object *sobj)
{
- free((&sobj->table)->bins);
free((&sobj->table)->entries);
}
@@ -171,9 +173,7 @@ set_foreach_replace(st_data_t key, st_data_t argp, int error)
static int
set_replace_ref(st_data_t *key, st_data_t argp, int existing)
{
- if (rb_gc_location((VALUE)*key) != (VALUE)*key) {
- *key = rb_gc_location((VALUE)*key);
- }
+ rb_gc_mark_and_move((VALUE *)key);
return ST_CONTINUE;
}
@@ -405,6 +405,13 @@ set_s_alloc(VALUE klass)
return set_alloc_with_size(klass, 0);
}
+/*
+ * call-seq:
+ * Set[*objects] -> new_set
+ *
+ * Returns a new Set object populated with the given objects,
+ * See Set::new.
+ */
static VALUE
set_s_create(int argc, VALUE *argv, VALUE klass)
{
@@ -419,6 +426,19 @@ set_s_create(int argc, VALUE *argv, VALUE klass)
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)
{
@@ -475,11 +495,11 @@ set_initialize_with_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
* If a block is given, the elements of enum are preprocessed by the
* given block.
*
- * Set.new([1, 2]) #=> #<Set: {1, 2}>
- * Set.new([1, 2, 1]) #=> #<Set: {1, 2}>
- * Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}>
- * Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}>
- * Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}>
+ * Set.new([1, 2]) #=> Set[1, 2]
+ * Set.new([1, 2, 1]) #=> Set[1, 2]
+ * Set.new([1, 'c', :s]) #=> Set[1, "c", :s]
+ * Set.new(1..5) #=> Set[1, 2, 3, 4, 5]
+ * Set.new([1, 2, 3]) { |x| x * x } #=> Set[1, 4, 9]
*/
static VALUE
set_i_initialize(int argc, VALUE *argv, VALUE set)
@@ -513,6 +533,7 @@ set_i_initialize(int argc, VALUE *argv, VALUE set)
return set;
}
+/* :nodoc: */
static VALUE
set_i_initialize_copy(VALUE set, VALUE other)
{
@@ -527,6 +548,7 @@ set_i_initialize_copy(VALUE set, VALUE other)
set_free_embedded(sobj);
set_copy(&sobj->table, RSET_TABLE(other));
+ rb_gc_writebarrier_remember(set);
return set;
}
@@ -534,10 +556,14 @@ set_i_initialize_copy(VALUE set, VALUE other)
static int
set_inspect_i(st_data_t key, st_data_t arg)
{
- VALUE str = (VALUE)arg;
- if (RSTRING_LEN(str) > 8) {
+ 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;
@@ -547,11 +573,17 @@ static VALUE
set_inspect(VALUE set, VALUE dummy, int recur)
{
VALUE str;
+ VALUE klass_name = rb_class_path(CLASS_OF(set));
- if (recur) return rb_usascii_str_new2("#<Set: {...}>");
- str = rb_str_buf_new2("#<Set: {");
- set_iter(set, set_inspect_i, str);
- rb_str_buf_cat2(str, "}>");
+ 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;
}
@@ -563,11 +595,11 @@ set_inspect(VALUE set, VALUE dummy, int recur)
* Returns a new string containing the set entries:
*
* s = Set.new
- * s.inspect # => "#<Set: {}>"
+ * s.inspect # => "Set[]"
* s.add(1)
- * s.inspect # => "#<Set: {1}>"
+ * s.inspect # => "Set[1]"
* s.add(2)
- * s.inspect # => "#<Set: {1, 2}>"
+ * s.inspect # => "Set[1, 2]"
*
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
*/
@@ -616,35 +648,22 @@ set_i_to_a(VALUE set)
/*
* call-seq:
- * to_set(klass = Set, *args, &block) -> self or new_set
+ * to_set(&block) -> self or new_set
*
- * Returns self if receiver is an instance of +Set+ and no arguments or
- * block are given. Otherwise, converts the set to another with
- * <tt>klass.new(self, *args, &block)</tt>.
+ * Without a block, if +self+ is an instance of +Set+, returns +self+.
+ * Otherwise, calls <tt>Set.new(self, &block)</tt>.
*
- * In subclasses, returns `klass.new(self, *args, &block)` unless overridden.
+ * A form with arguments is _deprecated_. It converts the set to another
+ * with <tt>klass.new(self, *args, &block)</tt>.
*/
static VALUE
-set_i_to_set(int argc, VALUE *argv, VALUE set)
+set_i_to_set(VALUE set)
{
- VALUE klass;
-
- if (argc == 0) {
- klass = rb_cSet;
- argv = &set;
- argc = 1;
- }
- else {
- klass = argv[0];
- argv[0] = set;
- }
-
- if (klass == rb_cSet && rb_obj_is_instance_of(set, rb_cSet) &&
- argc == 1 && !rb_block_given_p()) {
+ if (rb_obj_is_instance_of(set, rb_cSet) && !rb_block_given_p()) {
return set;
}
- return rb_funcall_passing_block(klass, id_new, argc, argv);
+ return rb_funcall_passing_block(rb_cSet, id_new, 0, NULL);
}
/*
@@ -664,19 +683,19 @@ set_i_join(int argc, VALUE *argv, VALUE set)
* call-seq:
* add(obj) -> self
*
- * Adds the given object to the set and returns self. Use `merge` to
+ * Adds the given object to the set and returns self. Use Set#merge to
* add many elements at once.
*
- * Set[1, 2].add(3) #=> #<Set: {1, 2, 3}>
- * Set[1, 2].add([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
- * Set[1, 2].add(2) #=> #<Set: {1, 2}>
+ * Set[1, 2].add(3) #=> Set[1, 2, 3]
+ * Set[1, 2].add([3, 4]) #=> Set[1, 2, [3, 4]]
+ * Set[1, 2].add(2) #=> Set[1, 2]
*/
static VALUE
set_i_add(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_iterating_p(set)) {
- if (!set_lookup(RSET_TABLE(set), (st_data_t)item)) {
+ if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
no_new_item();
}
}
@@ -693,8 +712,8 @@ set_i_add(VALUE set, VALUE item)
* Adds the given object to the set and returns self. If the object is
* already in the set, returns nil.
*
- * Set[1, 2].add?(3) #=> #<Set: {1, 2, 3}>
- * Set[1, 2].add?([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
+ * Set[1, 2].add?(3) #=> Set[1, 2, 3]
+ * Set[1, 2].add?([3, 4]) #=> Set[1, 2, [3, 4]]
* Set[1, 2].add?(2) #=> nil
*/
static VALUE
@@ -702,7 +721,7 @@ set_i_add_p(VALUE set, VALUE item)
{
rb_check_frozen(set);
if (set_iterating_p(set)) {
- if (!set_lookup(RSET_TABLE(set), (st_data_t)item)) {
+ if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
no_new_item();
}
return Qnil;
@@ -723,7 +742,7 @@ static VALUE
set_i_delete(VALUE set, VALUE item)
{
rb_check_frozen(set);
- if (set_delete(RSET_TABLE(set), (st_data_t *)&item)) {
+ if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
set_compact_after_delete(set);
}
return set;
@@ -740,7 +759,7 @@ static VALUE
set_i_delete_p(VALUE set, VALUE item)
{
rb_check_frozen(set);
- if (set_delete(RSET_TABLE(set), (st_data_t *)&item)) {
+ if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
set_compact_after_delete(set);
return set;
}
@@ -823,9 +842,9 @@ set_classify_i(st_data_t key, st_data_t tmp)
*
* files = Set.new(Dir.glob("*.rb"))
* hash = files.classify { |f| File.mtime(f).year }
- * hash #=> {2000 => #<Set: {"a.rb", "b.rb"}>,
- * # 2001 => #<Set: {"c.rb", "d.rb", "e.rb"}>,
- * # 2002 => #<Set: {"f.rb"}>}
+ * hash #=> {2000 => Set["a.rb", "b.rb"],
+ * # 2001 => Set["c.rb", "d.rb", "e.rb"],
+ * # 2002 => Set["f.rb"]}
*
* Returns an enumerator if no block is given.
*/
@@ -840,66 +859,72 @@ set_i_classify(VALUE set)
return args[0];
}
-struct set_divide_args {
- VALUE self;
- VALUE set_class;
- VALUE final_set;
- VALUE hash;
- VALUE current_set;
- VALUE current_item;
- unsigned long ni;
- unsigned long nj;
-};
+// 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 VALUE
-set_divide_block0(RB_BLOCK_CALL_FUNC_ARGLIST(j, arg))
-{
- struct set_divide_args *args = (struct set_divide_args *)arg;
- if (args->nj > args->ni) {
- VALUE i = args->current_item;
- if (RTEST(rb_yield_values(2, i, j)) && RTEST(rb_yield_values(2, j, i))) {
- VALUE hash = args->hash;
- if (args->current_set == Qnil) {
- VALUE set = rb_hash_aref(hash, j);
- if (set == Qnil) {
- VALUE both[2] = {i, j};
- set = set_s_create(2, both, args->set_class);
- rb_hash_aset(hash, i, set);
- rb_hash_aset(hash, j, set);
- set_i_add(args->final_set, set);
- }
- else {
- set_i_add(set, i);
- rb_hash_aset(hash, i, set);
- }
- args->current_set = set;
- }
- else {
- set_i_add(args->current_set, j);
- rb_hash_aset(hash, j, args->current_set);
+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);
}
}
}
- args->nj++;
- return j;
-}
-
-static VALUE
-set_divide_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg))
-{
- struct set_divide_args *args = (struct set_divide_args *)arg;
- VALUE hash = args->hash;
- args->current_set = rb_hash_aref(hash, i);
- args->current_item = i;
- args->nj = 0;
- rb_block_call(args->self, id_each, 0, 0, set_divide_block0, arg);
- if (args->current_set == Qnil) {
- VALUE set = set_s_create(1, &i, args->set_class);
- rb_hash_aset(hash, i, set);
- set_i_add(args->final_set, set);
+ 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);
}
- args->ni++;
- return i;
+ ALLOCV_END(tmp);
+ ALLOCV_END(uf);
+ return final_set;
}
static void set_merge_enum_into(VALUE set, VALUE arg);
@@ -920,10 +945,10 @@ static void set_merge_enum_into(VALUE set, VALUE arg);
*
* numbers = Set[1, 3, 4, 6, 9, 10, 11]
* set = numbers.divide { |i,j| (i - j).abs == 1 }
- * set #=> #<Set: {#<Set: {1}>,
- * # #<Set: {3, 4}>,
- * # #<Set: {6}>}>
- * # #<Set: {9, 10, 11}>,
+ * set #=> Set[Set[1],
+ * # Set[3, 4],
+ * # Set[6],
+ * # Set[9, 10, 11]]
*
* Returns an enumerator if no block is given.
*/
@@ -933,19 +958,7 @@ set_i_divide(VALUE set)
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
if (rb_block_arity() == 2) {
- VALUE final_set = set_s_create(0, 0, rb_cSet);
- struct set_divide_args args = {
- .self = set,
- .set_class = rb_obj_class(set),
- .final_set = final_set,
- .hash = rb_hash_new(),
- .current_set = 0,
- .current_item = 0,
- .ni = 0,
- .nj = 0
- };
- rb_block_call(set, id_each, 0, 0, set_divide_block, (VALUE)&args);
- return final_set;
+ return set_divide_arity2(set);
}
VALUE values = rb_hash_values(set_i_classify(set));
@@ -966,9 +979,9 @@ set_clear_i(st_data_t key, st_data_t dummy)
*
* Removes all elements and returns self.
*
- * set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
- * set.clear #=> #<Set: {}>
- * set #=> #<Set: {}>
+ * set = Set[1, 'c', :s] #=> Set[1, "c", :s]
+ * set.clear #=> Set[]
+ * set #=> Set[]
*/
static VALUE
set_i_clear(VALUE set)
@@ -979,7 +992,7 @@ set_i_clear(VALUE set)
set_iter(set, set_clear_i, 0);
}
else {
- set_clear(RSET_TABLE(set));
+ set_table_clear(RSET_TABLE(set));
set_compact_after_delete(set);
}
return set;
@@ -995,7 +1008,7 @@ 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_lookup(data->other, key)) {
+ if (set_table_lookup(data->other, key)) {
set_table_insert_wb(data->into, data->set, key, NULL);
}
@@ -1016,8 +1029,8 @@ set_intersection_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, data))
* Returns a new set containing elements common to the set and the given
* enumerable object.
*
- * Set[1, 3, 5] & Set[3, 2, 1] #=> #<Set: {3, 1}>
- * Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> #<Set: {"a", "b"}>
+ * Set[1, 3, 5] & Set[3, 2, 1] #=> Set[3, 1]
+ * Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> Set["a", "b"]
*/
static VALUE
set_i_intersection(VALUE set, VALUE other)
@@ -1245,7 +1258,7 @@ set_xor_i(st_data_t key, st_data_t data)
VALUE set = (VALUE)data;
set_table *table = RSET_TABLE(set);
if (set_table_insert_wb(table, set, element, &element)) {
- set_delete(table, &element);
+ set_table_delete(table, &element);
}
return ST_CONTINUE;
}
@@ -1258,21 +1271,23 @@ set_xor_i(st_data_t key, st_data_t data)
* given enumerable object. <tt>(set ^ enum)</tt> is equivalent to
* <tt>((set | enum) - (set & enum))</tt>.
*
- * Set[1, 2] ^ Set[2, 3] #=> #<Set: {3, 1}>
- * Set[1, 'b', 'c'] ^ ['b', 'd'] #=> #<Set: {"d", 1, "c"}>
+ * Set[1, 2] ^ Set[2, 3] #=> Set[3, 1]
+ * Set[1, 'b', 'c'] ^ ['b', 'd'] #=> Set["d", 1, "c"]
*/
static VALUE
set_i_xor(VALUE set, VALUE other)
{
- VALUE new_set;
+ VALUE new_set = rb_obj_dup(set);
+
if (rb_obj_is_kind_of(other, rb_cSet)) {
- new_set = other;
+ set_iter(other, set_xor_i, (st_data_t)new_set);
}
else {
- new_set = set_s_alloc(rb_obj_class(set));
- set_merge_enum_into(new_set, other);
+ 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_iter(set, set_xor_i, (st_data_t)new_set);
+
return new_set;
}
@@ -1283,8 +1298,8 @@ set_i_xor(VALUE set, VALUE other)
* Returns a new set built by merging the set and the elements of the
* given enumerable object.
*
- * Set[1, 2, 3] | Set[2, 4, 5] #=> #<Set: {1, 2, 3, 4, 5}>
- * Set[1, 5, 'z'] | (1..6) #=> #<Set: {1, 5, "z", 2, 3, 4, 6}>
+ * Set[1, 2, 3] | Set[2, 4, 5] #=> Set[1, 2, 3, 4, 5]
+ * Set[1, 5, 'z'] | (1..6) #=> Set[1, 5, "z", 2, 3, 4, 6]
*/
static VALUE
set_i_union(VALUE set, VALUE other)
@@ -1297,7 +1312,7 @@ set_i_union(VALUE set, VALUE other)
static int
set_remove_i(st_data_t key, st_data_t from)
{
- set_delete((struct set_table *)from, (st_data_t *)&key);
+ set_table_delete((struct set_table *)from, (st_data_t *)&key);
return ST_CONTINUE;
}
@@ -1305,7 +1320,7 @@ static VALUE
set_remove_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
{
rb_check_frozen(set);
- set_delete(RSET_TABLE(set), (st_data_t *)&key);
+ set_table_delete(RSET_TABLE(set), (st_data_t *)&key);
return key;
}
@@ -1342,8 +1357,8 @@ set_i_subtract(VALUE set, VALUE other)
* Returns a new set built by duplicating the set, removing every
* element that appears in the given enumerable object.
*
- * Set[1, 3, 5] - Set[1, 5] #=> #<Set: {3}>
- * Set['a', 'b', 'z'] - ['a', 'c'] #=> #<Set: {"b", "z"}>
+ * Set[1, 3, 5] - Set[1, 5] #=> Set[3]
+ * Set['a', 'b', 'z'] - ['a', 'c'] #=> Set["b", "z"]
*/
static VALUE
set_i_difference(VALUE set, VALUE other)
@@ -1407,7 +1422,7 @@ static int
set_keep_if_i(st_data_t key, st_data_t into)
{
if (!RTEST(rb_yield((VALUE)key))) {
- set_delete((set_table *)into, &key);
+ set_table_delete((set_table *)into, &key);
}
return ST_CONTINUE;
}
@@ -1459,9 +1474,9 @@ set_i_select(VALUE set)
* Replaces the contents of the set with the contents of the given
* enumerable object and returns self.
*
- * set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
- * set.replace([1, 2]) #=> #<Set: {1, 2}>
- * set #=> #<Set: {1, 2}>
+ * set = Set[1, 'c', :s] #=> Set[1, "c", :s]
+ * set.replace([1, 2]) #=> Set[1, 2]
+ * set #=> Set[1, 2]
*/
static VALUE
set_i_replace(VALUE set, VALUE other)
@@ -1479,7 +1494,7 @@ set_i_replace(VALUE set, VALUE other)
// make sure enum is enumerable before calling clear
enum_method_id(other);
- set_clear(RSET_TABLE(set));
+ set_table_clear(RSET_TABLE(set));
set_merge_enum_into(set, other);
}
@@ -1590,7 +1605,7 @@ 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_lookup(data->table, key)) return ST_CONTINUE;
+ if (set_table_lookup(data->table, key)) return ST_CONTINUE;
data->result = Qfalse;
return ST_STOP;
}
@@ -1666,7 +1681,7 @@ static int
set_intersect_i(st_data_t key, st_data_t arg)
{
VALUE *args = (VALUE *)arg;
- if (set_lookup((set_table *)args[0], key)) {
+ if (set_table_lookup((set_table *)args[0], key)) {
args[1] = Qtrue;
return ST_STOP;
}
@@ -1737,7 +1752,7 @@ set_i_disjoint(VALUE set, VALUE other)
* set <=> other -> -1, 0, 1, or nil
*
* Returns 0 if the set are equal, -1 / 1 if the set is a
- * proper subset / superset of the given set, or or nil if
+ * proper subset / superset of the given set, or nil if
* they both have unique elements.
*/
static VALUE
@@ -1775,7 +1790,7 @@ set_eql_i(st_data_t item, st_data_t arg)
{
struct set_equal_data *data = (struct set_equal_data *)arg;
- if (!set_lookup(RSET_TABLE(data->set), item)) {
+ if (!set_table_lookup(RSET_TABLE(data->set), item)) {
data->result = Qfalse;
return ST_STOP;
}
@@ -1906,24 +1921,67 @@ 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
*
- * Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org>
- *
- * Documentation by Akinori MUSHA and Gavin Sinclair.
- *
- * All rights reserved. You can redistribute and/or modify it under the same
- * terms as Ruby.
- *
* The Set class implements a collection of unordered values with no
* duplicates. It is a hybrid of Array's intuitive inter-operation
* facilities and Hash's fast lookup.
*
- * Set is easy to use with Enumerable objects (implementing `each`).
+ * Set is easy to use with Enumerable objects (implementing #each).
* Most of the initializer methods and binary operators accept generic
* Enumerable objects besides sets and arrays. An Enumerable object
- * can be converted to Set using the `to_set` method.
+ * can be converted to Set using the +to_set+ method.
*
* Set uses a data structure similar to Hash for storage, except that
* it only has keys and no values.
@@ -1947,11 +2005,11 @@ compat_loader(VALUE self, VALUE a)
*
* == Example
*
- * s1 = Set[1, 2] #=> #<Set: {1, 2}>
- * s2 = [1, 2].to_set #=> #<Set: {1, 2}>
+ * s1 = Set[1, 2] #=> Set[1, 2]
+ * s2 = [1, 2].to_set #=> Set[1, 2]
* s1 == s2 #=> true
- * s1.add("foo") #=> #<Set: {1, 2, "foo"}>
- * s1.merge([2, 6]) #=> #<Set: {1, 2, "foo", 6}>
+ * s1.add("foo") #=> Set[1, 2, "foo"]
+ * s1.merge([2, 6]) #=> Set[1, 2, "foo", 6]
* s1.subset?(s2) #=> false
* s2.subset?(s1) #=> true
*
@@ -1959,9 +2017,39 @@ compat_loader(VALUE self, VALUE a)
*
* - Akinori MUSHA <knu@iDaemons.org> (current maintainer)
*
- * == What's Here
+ * == Inheriting from \Set
+ *
+ * Before Ruby 4.0 (released December 2025), \Set had a different, less
+ * efficient implementation. It was reimplemented in C, and the behavior
+ * of some of the core methods were adjusted.
*
- * First, what's elsewhere. \Class \Set:
+ * To keep backward compatibility, when a class is inherited from \Set,
+ * additional module +Set::SubclassCompatible+ is included, which makes
+ * the inherited class behavior, as well as internal method names,
+ * closer to what it was before Ruby 4.0.
+ *
+ * It can be easily seen, for example, in the #inspect method behavior:
+ *
+ * p Set[1, 2, 3]
+ * # prints "Set[1, 2, 3]"
+ *
+ * class MySet < Set
+ * end
+ * p MySet[1, 2, 3]
+ * # prints "#<MySet: {1, 2, 3}>", like it was in Ruby 3.4
+ *
+ * For new code, if backward compatibility is not necessary,
+ * it is recommended to instead inherit from +Set::CoreSet+, which
+ * avoids including the "compatibility" layer:
+ *
+ * class MyCoreSet < Set::CoreSet
+ * end
+ * p MyCoreSet[1, 2, 3]
+ * # prints "MyCoreSet[1, 2, 3]"
+ *
+ * == Set's methods
+ *
+ * First, what's elsewhere. \Class \Set:
*
* - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
* - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
@@ -1973,16 +2061,15 @@ compat_loader(VALUE self, VALUE a)
*
* Here, class \Set provides methods that are useful for:
*
- * - {Creating an Array}[rdoc-ref:Array@Methods+for+Creating+an+Array]
- * - {Creating a Set}[rdoc-ref:Array@Methods+for+Creating+a+Set]
- * - {Set Operations}[rdoc-ref:Array@Methods+for+Set+Operations]
- * - {Comparing}[rdoc-ref:Array@Methods+for+Comparing]
- * - {Querying}[rdoc-ref:Array@Methods+for+Querying]
- * - {Assigning}[rdoc-ref:Array@Methods+for+Assigning]
- * - {Deleting}[rdoc-ref:Array@Methods+for+Deleting]
- * - {Converting}[rdoc-ref:Array@Methods+for+Converting]
- * - {Iterating}[rdoc-ref:Array@Methods+for+Iterating]
- * - {And more....}[rdoc-ref:Array@Other+Methods]
+ * - {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]
+ * - {Iterating}[rdoc-ref:Set@Methods+for+Iterating]
+ * - {And more....}[rdoc-ref:Set@Other+Methods]
*
* === Methods for Creating a \Set
*
@@ -2125,6 +2212,8 @@ Init_Set(void)
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);
@@ -2189,11 +2278,16 @@ Init_Set(void)
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, -1);
+ 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");
}