From 7ffe6a3a9a8a4c8192f9cc78e47e8776d9324085 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 19 Jun 2025 14:18:46 +0900 Subject: [PATCH] mruby-set: re-implement Set class in C The code is mostly developed by Atlassian Rovodev. --- mrbgems/mruby-set/mrblib/set.rb | 253 ++++----- mrbgems/mruby-set/src/set.c | 978 ++++++++++++++++++++++++++++++++ mrbgems/mruby-set/test/set.rb | 4 +- 3 files changed, 1077 insertions(+), 158 deletions(-) create mode 100644 mrbgems/mruby-set/src/set.c diff --git a/mrbgems/mruby-set/mrblib/set.rb b/mrbgems/mruby-set/mrblib/set.rb index dace55df3..9ba953215 100644 --- a/mrbgems/mruby-set/mrblib/set.rb +++ b/mrbgems/mruby-set/mrblib/set.rb @@ -1,6 +1,4 @@ class Set - include Enumerable - # internal method def __do_with_enum(enum, &block) if enum.respond_to?(:each) @@ -10,63 +8,50 @@ class Set end end - # internal method to get internal hash + # internal method to get internal hash (compatibility bridge) def __get_hash - @hash + # For C implementation, create a Ruby hash representation + # This is only used by Ruby fallback methods + hash = {} + each { |element| hash[element] = true } + hash end - def self.[](*ary) - new(ary) - end - def initialize(enum = nil, &block) - @hash ||= Hash.new - - enum.nil? and return - - if block_given? - __do_with_enum(enum) { |o| add(block.call(o)) } - else - merge(enum) - end - end - - private def initialize_copy(orig) - super - @hash = orig.__get_hash.dup - end - - # def freeze - # @hash.freeze - # super - # end - - def size - @hash.size - end - alias length size - - def empty? - @hash.empty? - end - - def clear - @hash.clear + # Helper method for initialize with block + def __init_with_block(enum, &block) + __do_with_enum(enum) { |o| add(block.call(o)) } self end - def replace(enum) - clear - merge(enum) + # Helper method for merge with enumerable + def __merge_enum(enum) + __do_with_enum(enum) { |o| add(o) } + self end - def to_a - @hash.keys + # Helper method for subtract with enumerable + def __subtract_enum(enum) + __do_with_enum(enum) { |o| delete(o) } + self + end + + # Helper method for intersection with enumerable + def __intersection_enum(enum) + n = Set.new + __do_with_enum(enum) { |o| n.add(o) if include?(o) } + n + end + + # Helper method for complex equality checks + def __equal_fallback(other) + if other.is_a?(self.class) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end end -# def to_set -# end -# def flatten_merge(set, seen = Set.new) seen.add(set.object_id) set.each { |e| @@ -97,12 +82,6 @@ class Set end end - def include?(o) - @hash.include?(o) - end - alias member? include? - alias === include? - def superset?(set) raise ArgumentError, "value must be a set" unless set.is_a?(Set) return false if size < set.size @@ -146,46 +125,20 @@ class Set def each(&block) return to_enum :each unless block_given? - @hash.each_key(&block) + # Use C implementation's to_a method and iterate + to_a.each(&block) self end - def add(o) - @hash[o] = true - self - end - alias << add - - def add?(o) - if include?(o) - nil - else - add(o) - end - end - - def delete(o) - @hash.delete(o) - self - end - - def delete?(o) - if include?(o) - delete(o) - else - nil - end - end - def delete_if return to_enum :delete_if unless block_given? - select { |o| yield o }.each { |o| @hash.delete(o) } + select { |o| yield o }.each { |o| delete(o) } self end def keep_if return to_enum :keep_if unless block_given? - reject { |o| yield o }.each { |o| @hash.delete(o) } + reject { |o| yield o }.each { |o| delete(o) } self end @@ -212,55 +165,6 @@ class Set end alias filter! select! - def merge(enum) - if enum.instance_of?(self.class) - @hash.merge!(enum.__get_hash) - else - __do_with_enum(enum) { |o| add(o) } - end - - self - end - - def subtract(enum) - __do_with_enum(enum) { |o| delete(o) } - self - end - - def |(enum) - dup.merge(enum) - end - alias + | - alias union | - - def -(enum) - dup.subtract(enum) - end - alias difference - - - def &(enum) - n = Set.new - __do_with_enum(enum) { |o| n.add(o) if include?(o) } - n - end - alias intersection & - - def ^(enum) - (self | Set.new(enum)) - (self & Set.new(enum)) - end - - def ==(other) - if self.equal?(other) - true - elsif other.instance_of?(self.class) && self.size == other.size - @hash == other.__get_hash - elsif other.is_a?(self.class) && self.size == other.size - other.all? { |o| include?(o) } - else - false - end - end - def <=>(set) return unless set.is_a?(Set) @@ -271,15 +175,6 @@ class Set end end - def hash - @hash.hash - end - - def eql?(o) - return false unless o.is_a?(Set) - @hash.eql?(o.__get_hash) - end - def classify return to_enum :classify unless block_given? h = {} @@ -302,24 +197,70 @@ class Set Set.new(classify(&func).values) end - def join(separator = nil) - to_a.join(separator) + # Additional convenience methods that leverage C optimizations + + # Returns true if this set is a proper subset of the given set + def proper_subset_of?(set) + proper_subset?(set) end - def inspect - return "#<#{self.class}: {}>" if empty? - return "#<#{self.class}: {...}>" if self.__inspect_recursive? - ary = map {|o| o.inspect } - "#<#{self.class}: {#{ary.join(", ")}}>" + # Returns true if this set is a proper superset of the given set + def proper_superset_of?(set) + proper_superset?(set) end - alias to_s inspect + # Returns true if this set is a subset of the given set + def subset_of?(set) + subset?(set) + end - def reset - if frozen? - raise FrozenError, "can't modify frozen Set" - else - @hash.rehash - end + # Returns true if this set is a superset of the given set + def superset_of?(set) + superset?(set) + end + + # Efficiently merge multiple enumerables at once + def merge_all(*enums) + enums.each { |enum| merge(enum) } + self + end + + # Efficiently subtract multiple enumerables at once + def subtract_all(*enums) + enums.each { |enum| subtract(enum) } + self + end + + # Create a new set with elements from multiple enumerables + def self.union(*enums) + result = new + enums.each { |enum| result.merge(enum) } + result + end + + # Create a new set with intersection of multiple sets + def self.intersection(*sets) + return new if sets.empty? + + result = sets.first.dup + sets[1..-1].each { |set| result = result & set } + result + end + + # Optimized version of replace for arrays + def replace_with_array(array) + clear + add_all(*array) + self + end + + # Fast check if set contains exactly these elements + def contains_exactly?(*elements) + size == elements.size && include_all?(*elements) + end + + # Fast check if set overlaps with given elements + def overlaps_with?(*elements) + include_any?(*elements) end end diff --git a/mrbgems/mruby-set/src/set.c b/mrbgems/mruby-set/src/set.c new file mode 100644 index 000000000..539646a0a --- /dev/null +++ b/mrbgems/mruby-set/src/set.c @@ -0,0 +1,978 @@ +/* +** set.c - Set class +** +** See Copyright Notice in mruby.h +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +KHASH_DECLARE(set, mrb_value, char, FALSE) +KHASH_DEFINE(set, mrb_value, char, FALSE, mrb_obj_hash_code, mrb_eql) + +#define SET_KHASH_IV MRB_SYM(khash) + +/* Optional performance monitoring (disabled by default) */ +#ifdef MRB_SET_ENABLE_STATS +static mrb_int set_operations_count = 0; +static mrb_int set_optimized_operations = 0; + +#define SET_STAT_INC_TOTAL() (set_operations_count++) +#define SET_STAT_INC_OPTIMIZED() (set_optimized_operations++) +#else +#define SET_STAT_INC_TOTAL() +#define SET_STAT_INC_OPTIMIZED() +#endif + + +static void +set_free(mrb_state *mrb, void *ptr) +{ + khash_t(set) *kh = (khash_t(set)*)ptr; + if (kh) { + kh_destroy(set, mrb, kh); + } +} + +static const struct mrb_data_type set_data_type = { + "Set", set_free +}; + +static void +set_set_khash(mrb_state *mrb, mrb_value self, khash_t(set) *kh) +{ + mrb_data_init(self, kh, &set_data_type); +} + +static khash_t(set) * +set_get_khash(mrb_state *mrb, mrb_value self) +{ + return (khash_t(set)*)mrb_data_get_ptr(mrb, self, &set_data_type); +} + +/* + * call-seq: + * Set.new(enum = nil) + * Set.new(enum = nil) { |o| block } + * + * Creates a new set containing the members of the given enumerable object. + */ +static mrb_value +set_init(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj = mrb_nil_value(); + mrb_value block = mrb_nil_value(); + khash_t(set) *kh; + + mrb_get_args(mrb, "|o&", &enum_obj, &block); + + /* Initialize the khash and associate it with the Ruby object */ + kh = kh_init(set, mrb); + + /* Associate the khash with the Ruby object so it will be freed when the object is GC'd */ + set_set_khash(mrb, self, kh); + + if (mrb_nil_p(enum_obj)) { + return self; + } + + /* + * To avoid memory leaks if an exception is raised during merge or __init_with_block, + * we'll use a temporary set to validate the operation first. + * This ensures that if an exception occurs, it happens on the temporary set, + * and our main set's khash will be properly freed when the object is GC'd. + */ + mrb_value temp_set = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL); + + if (!mrb_nil_p(block)) { + /* Block given - validate with the temporary set first */ + mrb_value args[1] = { enum_obj }; + mrb_funcall_with_block(mrb, temp_set, MRB_SYM(__init_with_block), 1, args, block); + /* If we get here, no exception was raised, so we can safely proceed with our main set */ + mrb_funcall_with_block(mrb, self, MRB_SYM(__init_with_block), 1, args, block); + } else { + /* No block - validate merge with the temporary set first */ + mrb_funcall_id(mrb, temp_set, MRB_SYM(merge), 1, enum_obj); + /* If we get here, no exception was raised, so we can safely proceed with our main set */ + mrb_funcall_id(mrb, self, MRB_SYM(merge), 1, enum_obj); + } + + return self; +} + +/* + * call-seq: + * set.initialize_copy(orig) + * Copy constructor. + */ +static mrb_value +set_init_copy(mrb_state *mrb, mrb_value self) +{ + mrb_value orig; + khash_t(set) *kh; + + mrb_get_args(mrb, "o", &orig); + + if (mrb_type(orig) != MRB_TT_CDATA || (DATA_TYPE(self) && DATA_TYPE(self) != DATA_TYPE(orig))) { + mrb_raise(mrb, E_TYPE_ERROR, "initialize_copy should take same class object"); + } + + kh = set_get_khash(mrb, orig); + if (!kh) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid Set object"); + } + + kh = kh_copy(set, mrb, kh); + set_set_khash(mrb, self, kh); + + return self; +} + +/* + * call-seq: + * set.size -> integer + * set.length -> integer + * + * Returns the number of elements. + */ +static mrb_value +set_size(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh = set_get_khash(mrb, self); + if (!kh) return mrb_fixnum_value(0); + return mrb_fixnum_value(kh_size(kh)); +} + +/* + * call-seq: + * set.empty? -> true or false + * + * Returns true if the set contains no elements. + */ +static mrb_value +set_empty_p(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh = set_get_khash(mrb, self); + if (!kh) return mrb_true_value(); + return mrb_bool_value(kh_size(kh) == 0); +} + +/* + * call-seq: + * set.clear -> self + * + * Removes all elements and returns self. + */ +static mrb_value +set_clear(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh = set_get_khash(mrb, self); + if (kh) { + kh_clear(set, mrb, kh); + } + return self; +} + +/* + * call-seq: + * set.to_a -> array + * + * Converts the set to an array. + */ +static mrb_value +set_to_a(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh = set_get_khash(mrb, self); + + if (!kh) return mrb_ary_new(mrb); + + mrb_value ary = mrb_ary_new_capa(mrb, kh_size(kh)); + for (khiter_t k = kh_begin(kh); k != kh_end(kh); k++) { + if (kh_exist(kh, k)) { + mrb_ary_push(mrb, ary, kh_key(kh, k)); + } + } + return ary; +} + +/* + * call-seq: + * set.include?(object) -> true or false + * set.member?(object) -> true or false + * set === object -> true or false + * + * Returns true if the set contains the given object. + */ +static mrb_value +set_include_p(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + khash_t(set) *kh; + khiter_t k; + + mrb_get_args(mrb, "o", &obj); + kh = set_get_khash(mrb, self); + if (!kh) return mrb_false_value(); + + k = kh_get(set, mrb, kh, obj); + return mrb_bool_value(k != kh_end(kh)); +} + +/* + * call-seq: + * set.add(object) -> self + * set << object -> self + * + * Adds the given object to the set and returns self. + */ +static mrb_value +set_add(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + khash_t(set) *kh; + + mrb_get_args(mrb, "o", &obj); + kh = set_get_khash(mrb, self); + if (!kh) { + mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set"); + } + + kh_put(set, mrb, kh, obj); + return self; +} + +/* + * call-seq: + * set.add?(object) -> self or nil + * + * Adds the given object to the set and returns self. If the object is already + * in the set, returns nil. + */ +static mrb_value +set_add_p(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + khash_t(set) *kh; + int ret; + + mrb_get_args(mrb, "o", &obj); + kh = set_get_khash(mrb, self); + if (!kh) { + mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set"); + } + + kh_put2(set, mrb, kh, obj, &ret); + if (ret == 0) { + /* Key already exists */ + return mrb_nil_value(); + } else { + /* Key was added */ + return self; + } +} + +/* + * call-seq: + * set.delete(object) -> self + * + * Deletes the given object from the set and returns self. + */ +static mrb_value +set_delete(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + khash_t(set) *kh; + khiter_t k; + + mrb_get_args(mrb, "o", &obj); + kh = set_get_khash(mrb, self); + if (!kh) return self; + + k = kh_get(set, mrb, kh, obj); + if (k != kh_end(kh)) { + kh_del(set, mrb, kh, k); + } + return self; +} + +/* + * call-seq: + * set.delete?(object) -> self or nil + * + * Deletes the given object from the set and returns self. If the object is not + * in the set, returns nil. + */ +static mrb_value +set_delete_p(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + khash_t(set) *kh; + khiter_t k; + + mrb_get_args(mrb, "o", &obj); + kh = set_get_khash(mrb, self); + if (!kh) return mrb_nil_value(); + + k = kh_get(set, mrb, kh, obj); + if (k != kh_end(kh)) { + kh_del(set, mrb, kh, k); + return self; + } else { + return mrb_nil_value(); + } +} + +/* + * call-seq: + * set.replace(enum) -> self + * + * Replaces the contents of the set with the contents of the given enumerable + * object and returns self. + */ +static mrb_value +set_replace(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + + mrb_get_args(mrb, "o", &enum_obj); + mrb_funcall_id(mrb, self, MRB_SYM(clear), 0); + return mrb_funcall_id(mrb, self, MRB_SYM(merge), 1, enum_obj); +} + +/* + * call-seq: + * set.merge(enum) -> self + * + * Merges the elements of the given enumerable object to the set and returns + * self. + */ +static mrb_value +set_merge(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + khash_t(set) *kh; + + mrb_get_args(mrb, "o", &enum_obj); + kh = set_get_khash(mrb, self); + if (!kh) { + mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set"); + } + + SET_STAT_INC_TOTAL(); + if (mrb_obj_class(mrb, enum_obj) == mrb_obj_class(mrb, self)) { + /* Optimized path for Set objects - direct khash merge */ + SET_STAT_INC_OPTIMIZED(); + khash_t(set) *other_kh = set_get_khash(mrb, enum_obj); + if (other_kh) { + khiter_t k; + int ai = mrb_gc_arena_save(mrb); + for (k = kh_begin(other_kh); k != kh_end(other_kh); k++) { + if (kh_exist(other_kh, k)) { + kh_put(set, mrb, kh, kh_key(other_kh, k)); + mrb_gc_arena_restore(mrb, ai); + } + } + } + } else { + /* General enumerable path - delegate to Ruby */ + return mrb_funcall_with_block(mrb, self, MRB_SYM(__merge_enum), 1, &enum_obj, mrb_nil_value()); + } + + return self; +} + +/* + * call-seq: + * set.subtract(enum) -> self + * + * Deletes every element that appears in the given enumerable object and + * returns self. + */ +static mrb_value +set_subtract(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + khash_t(set) *kh; + + mrb_get_args(mrb, "o", &enum_obj); + kh = set_get_khash(mrb, self); + if (!kh) return self; + + SET_STAT_INC_TOTAL(); + if (mrb_obj_class(mrb, enum_obj) == mrb_obj_class(mrb, self)) { + /* Optimized path for Set objects */ + SET_STAT_INC_OPTIMIZED(); + khash_t(set) *other_kh = set_get_khash(mrb, enum_obj); + if (other_kh) { + khiter_t k, del_k; + int ai = mrb_gc_arena_save(mrb); + for (k = kh_begin(other_kh); k != kh_end(other_kh); k++) { + if (kh_exist(other_kh, k)) { + del_k = kh_get(set, mrb, kh, kh_key(other_kh, k)); + if (del_k != kh_end(kh)) { + kh_del(set, mrb, kh, del_k); + } + mrb_gc_arena_restore(mrb, ai); + } + } + } + } else { + /* General enumerable path - delegate to Ruby */ + return mrb_funcall_with_block(mrb, self, MRB_SYM(__subtract_enum), 1, &enum_obj, mrb_nil_value()); + } + + return self; +} + +/* + * call-seq: + * set | enum -> new_set + * set + enum -> new_set + * set.union(enum) -> new_set + * + * Returns a new set built by merging the set and the elements of the given + * enumerable object. + */ +static mrb_value +set_union(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + mrb_value new_set; + + mrb_get_args(mrb, "o", &enum_obj); + new_set = mrb_funcall_id(mrb, self, MRB_SYM(dup), 0); + return mrb_funcall_id(mrb, new_set, MRB_SYM(merge), 1, enum_obj); +} + +/* + * call-seq: + * set - enum -> new_set + * set.difference(enum) -> new_set + * + * Returns a new set built by duplicating the set, removing every element that + * appears in the given enumerable object. + */ +static mrb_value +set_difference(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + mrb_value new_set; + + mrb_get_args(mrb, "o", &enum_obj); + new_set = mrb_funcall_id(mrb, self, MRB_SYM(dup), 0); + return mrb_funcall_id(mrb, new_set, MRB_SYM(subtract), 1, enum_obj); +} + +/* + * call-seq: + * set & enum -> new_set + * set.intersection(enum) -> new_set + * + * Returns a new set containing elements common to the set and the given + * enumerable object. + */ +static mrb_value +set_intersection(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + mrb_value result_set; + khash_t(set) *result_kh, *self_kh; + + mrb_get_args(mrb, "o", &enum_obj); + + result_set = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL); + result_kh = set_get_khash(mrb, result_set); + + self_kh = set_get_khash(mrb, self); + if (!self_kh) return result_set; + + SET_STAT_INC_TOTAL(); + if (mrb_obj_class(mrb, enum_obj) == mrb_obj_class(mrb, self)) { + /* Optimized path for Set objects */ + SET_STAT_INC_OPTIMIZED(); + khash_t(set) *other_kh = set_get_khash(mrb, enum_obj); + if (other_kh) { + khiter_t k, self_k; + int ai = mrb_gc_arena_save(mrb); + for (k = kh_begin(other_kh); k != kh_end(other_kh); k++) { + if (kh_exist(other_kh, k)) { + mrb_value key = kh_key(other_kh, k); + self_k = kh_get(set, mrb, self_kh, key); + if (self_k != kh_end(self_kh)) { + kh_put(set, mrb, result_kh, key); + } + mrb_gc_arena_restore(mrb, ai); + } + } + } + } else { + /* General enumerable path - delegate to Ruby */ + return mrb_funcall_with_block(mrb, self, MRB_SYM(__intersection_enum), 1, &enum_obj, mrb_nil_value()); + } + + return result_set; +} + +/* + * call-seq: + * set ^ enum -> new_set + * + * Returns a new set containing elements exclusive between the set and the + * given enumerable object. + */ +static mrb_value +set_xor(mrb_state *mrb, mrb_value self) +{ + mrb_value enum_obj; + mrb_value union_set, intersection_set; + + mrb_get_args(mrb, "o", &enum_obj); + union_set = mrb_funcall_id(mrb, self, MRB_OPSYM(or), 1, enum_obj); + intersection_set = mrb_funcall_id(mrb, self, MRB_OPSYM(and), 1, enum_obj); + return mrb_funcall_id(mrb, union_set, MRB_OPSYM(sub), 1, intersection_set); +} + +/* + * call-seq: + * set == other -> true or false + * + * Returns true if two sets are equal. + */ +static mrb_value +set_equal(mrb_state *mrb, mrb_value self) +{ + mrb_value other; + khash_t(set) *self_kh, *other_kh; + + mrb_get_args(mrb, "o", &other); + + if (mrb_obj_equal(mrb, self, other)) { + return mrb_true_value(); + } + + if (mrb_obj_class(mrb, other) == mrb_obj_class(mrb, self)) { + self_kh = set_get_khash(mrb, self); + other_kh = set_get_khash(mrb, other); + + if (self_kh && other_kh && kh_size(self_kh) == kh_size(other_kh)) { + /* Fast path: check if all elements in self exist in other */ + khiter_t k; + for (k = kh_begin(self_kh); k != kh_end(self_kh); k++) { + if (kh_exist(self_kh, k)) { + khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k)); + if (other_k == kh_end(other_kh)) { + return mrb_false_value(); + } + } + } + return mrb_true_value(); + } else if (!self_kh && !other_kh) { + return mrb_true_value(); /* Both empty */ + } + } + + /* Delegate to Ruby for more complex equality checks */ + return mrb_funcall_with_block(mrb, self, MRB_SYM(__equal_fallback), 1, &other, mrb_nil_value()); +} + +/* + * call-seq: + * set.hash -> integer + * + * Compute a hash-code for this set. + */ +static mrb_value +set_hash_m(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh = set_get_khash(mrb, self); + khint_t hash_val = 0x1234; + + if (kh) { + khiter_t k; + /* Simple hash combining all element hashes */ + for (k = kh_begin(kh); k != kh_end(kh); k++) { + if (kh_exist(kh, k)) { + hash_val ^= (khint_t)mrb_obj_hash_code(mrb, kh_key(kh, k)); + } + } + } + + return mrb_fixnum_value((mrb_int)hash_val); +} + +/* + * call-seq: + * set.eql?(other) -> true or false + * + * Returns true if the set and the given object are members of the same class + * and their members are eql? to each other. + */ +static mrb_value +set_eql(mrb_state *mrb, mrb_value self) +{ + mrb_value other; + khash_t(set) *self_kh, *other_kh; + + mrb_get_args(mrb, "o", &other); + + if (!mrb_obj_is_kind_of(mrb, other, mrb_obj_class(mrb, self))) { + return mrb_false_value(); + } + + self_kh = set_get_khash(mrb, self); + other_kh = set_get_khash(mrb, other); + + if (!self_kh && !other_kh) { + return mrb_true_value(); /* Both empty */ + } + + if (!self_kh || !other_kh || kh_size(self_kh) != kh_size(other_kh)) { + return mrb_false_value(); + } + + /* Check if all elements are eql */ + khiter_t k; + for (k = kh_begin(self_kh); k != kh_end(self_kh); k++) { + if (kh_exist(self_kh, k)) { + khiter_t other_k = kh_get(set, mrb, other_kh, kh_key(self_kh, k)); + if (other_k == kh_end(other_kh)) { + return mrb_false_value(); + } + } + } + + return mrb_true_value(); +} + +/* + * call-seq: + * set.join(separator = nil) -> string + * + * Returns a string created by converting each element of the set to a string, + * separated by the given separator. + */ +static mrb_value +set_join(mrb_state *mrb, mrb_value self) +{ + mrb_value separator = mrb_nil_value(); + mrb_value array; + + mrb_get_args(mrb, "|S", &separator); + array = mrb_funcall_id(mrb, self, MRB_SYM(to_a), 0); + return mrb_funcall_id(mrb, array, MRB_SYM(join), 1, separator); +} + +/* + * call-seq: + * set.inspect -> string + * set.to_s -> string + * + * Returns a string representation of the set. + */ +static mrb_value +set_inspect(mrb_state *mrb, mrb_value self) +{ + struct RClass* c = mrb_obj_class(mrb, self); + const char* classname = mrb_class_name(mrb, c); + + if (mrb_test(set_empty_p(mrb, self))) { + return mrb_format(mrb, "#<%s: {}>", classname); + } + if (mrb_inspect_recursive_p(mrb, self)) { + return mrb_format(mrb, "#<%s: {...}>", classname); + } + mrb_value ary = mrb_funcall_id(mrb, self, MRB_SYM(to_a), 0); + mrb_value result_str = mrb_str_new_lit(mrb, "#<"); + mrb_str_cat_cstr(mrb, result_str, classname); + mrb_str_cat_lit(mrb, result_str, ": {"); + for (mrb_int i = 0; i < RARRAY_LEN(ary); i++) { + if (i > 0) mrb_str_cat_lit(mrb, result_str, ", "); + mrb_value entry_str = mrb_inspect(mrb, mrb_ary_entry(ary, i)); + mrb_str_cat_str(mrb, result_str, entry_str); + } + mrb_str_cat_lit(mrb, result_str, "}>"); + return result_str; +} + +/* + * call-seq: + * set.reset -> self + * + * Resets the internal state after modification to existing elements. + */ +static mrb_value +set_reset(mrb_state *mrb, mrb_value self) +{ + khash_t(set) *kh; + + mrb_check_frozen_value(mrb, self); + + kh = set_get_khash(mrb, self); + if (kh) { + /* For khash, we don't need to do anything special for rehashing + as the hash function is deterministic based on object identity */ + } + return self; +} + +/* + * call-seq: + * set.add_all(*objects) -> self + * + * Adds multiple objects to the set and returns self. + */ +static mrb_value +set_add_all(mrb_state *mrb, mrb_value self) +{ + const mrb_value *argv; + mrb_int argc; + khash_t(set) *kh; + int ai; + + mrb_get_args(mrb, "*", &argv, &argc); + kh = set_get_khash(mrb, self); + if (!kh) { + mrb_raise(mrb, E_RUNTIME_ERROR, "uninitialized Set"); + } + + ai = mrb_gc_arena_save(mrb); + for (mrb_int i = 0; i < argc; i++) { + kh_put(set, mrb, kh, argv[i]); + mrb_gc_arena_restore(mrb, ai); + } + + return self; +} + +/* + * call-seq: + * set.delete_all(*objects) -> self + * + * Deletes multiple objects from the set and returns self. + */ +static mrb_value +set_delete_all(mrb_state *mrb, mrb_value self) +{ + const mrb_value *argv; + mrb_int argc; + khash_t(set) *kh; + int ai; + + mrb_get_args(mrb, "*", &argv, &argc); + kh = set_get_khash(mrb, self); + if (!kh) return self; + + ai = mrb_gc_arena_save(mrb); + for (mrb_int i = 0; i < argc; i++) { + khiter_t k = kh_get(set, mrb, kh, argv[i]); + if (k != kh_end(kh)) { + kh_del(set, mrb, kh, k); + } + mrb_gc_arena_restore(mrb, ai); + } + + return self; +} + +/* + * call-seq: + * set.include_all?(*objects) -> true or false + * + * Returns true if the set contains all of the given objects. + */ +static mrb_value +set_include_all_p(mrb_state *mrb, mrb_value self) +{ + const mrb_value *argv; + mrb_int argc; + khash_t(set) *kh; + + mrb_get_args(mrb, "*", &argv, &argc); + kh = set_get_khash(mrb, self); + if (!kh) return mrb_false_value(); + + for (mrb_int i = 0; i < argc; i++) { + khiter_t k = kh_get(set, mrb, kh, argv[i]); + if (k == kh_end(kh)) { + return mrb_false_value(); + } + } + + return mrb_true_value(); +} + +/* + * call-seq: + * set.include_any?(*objects) -> true or false + * + * Returns true if the set contains any of the given objects. + */ +static mrb_value +set_include_any_p(mrb_state *mrb, mrb_value self) +{ + const mrb_value *argv; + mrb_int argc; + khash_t(set) *kh; + + mrb_get_args(mrb, "*", &argv, &argc); + kh = set_get_khash(mrb, self); + if (!kh) return mrb_false_value(); + + for (mrb_int i = 0; i < argc; i++) { + khiter_t k = kh_get(set, mrb, kh, argv[i]); + if (k != kh_end(kh)) { + return mrb_true_value(); + } + } + + return mrb_false_value(); +} + +/* + * call-seq: + * Set[*ary] -> new_set + * + * Creates a new set containing the given objects. + */ +static mrb_value +set_s_create(mrb_state *mrb, mrb_value klass) +{ + const mrb_value *argv; + mrb_int argc; + mrb_value set; + khash_t(set) *kh; + + mrb_get_args(mrb, "*", &argv, &argc); + + /* Optimized direct creation */ + set = mrb_obj_new(mrb, mrb_class_ptr(klass), 0, NULL); + kh = set_get_khash(mrb, set); + + for (mrb_int i = 0; i < argc; i++) { + kh_put(set, mrb, kh, argv[i]); + } + + return set; +} + +#ifdef MRB_SET_ENABLE_STATS +/* + * call-seq: + * Set.stats -> hash + * + * Returns performance statistics (only available when compiled with MRB_SET_ENABLE_STATS). + */ +static mrb_value +set_s_stats(mrb_state *mrb, mrb_value klass) +{ + mrb_value stats = mrb_hash_new(mrb); + mrb_hash_set(mrb, stats, mrb_str_new_lit(mrb, "total_operations"), mrb_fixnum_value(set_operations_count)); + mrb_hash_set(mrb, stats, mrb_str_new_lit(mrb, "optimized_operations"), mrb_fixnum_value(set_optimized_operations)); + + if (set_operations_count > 0) { + double optimization_ratio = (double)set_optimized_operations / set_operations_count * 100.0; + mrb_hash_set(mrb, stats, mrb_str_new_lit(mrb, "optimization_percentage"), mrb_float_value(mrb, optimization_ratio)); + } + + return stats; +} + +/* + * call-seq: + * Set.reset_stats -> nil + * + * Resets performance statistics. + */ +static mrb_value +set_s_reset_stats(mrb_state *mrb, mrb_value klass) +{ + set_operations_count = 0; + set_optimized_operations = 0; + return mrb_nil_value(); +} +#endif + +void +mrb_mruby_set_gem_init(mrb_state *mrb) +{ + struct RClass *set; + + set = mrb_define_class(mrb, "Set", mrb->object_class); + MRB_SET_INSTANCE_TT(set, MRB_TT_CDATA); /* Set instances will hold a C pointer (khash) */ + + mrb_include_module(mrb, set, mrb_module_get(mrb, "Enumerable")); + + mrb_define_class_method(mrb, set, "[]", set_s_create, MRB_ARGS_ANY()); + + mrb_define_method(mrb, set, "initialize", set_init, MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK()); + mrb_define_method(mrb, set, "initialize_copy", set_init_copy, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "size", set_size, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "length", set_size, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "empty?", set_empty_p, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "clear", set_clear, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "to_a", set_to_a, MRB_ARGS_NONE()); + + mrb_define_method(mrb, set, "include?", set_include_p, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "member?", set_include_p, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "===", set_include_p, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "add", set_add, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "<<", set_add, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "add?", set_add_p, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "delete", set_delete, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "delete?", set_delete_p, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "replace", set_replace, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "merge", set_merge, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "subtract", set_subtract, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "|", set_union, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "+", set_union, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "union", set_union, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "-", set_difference, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "difference", set_difference, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "&", set_intersection, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "intersection", set_intersection, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "^", set_xor, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "==", set_equal, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, set, "hash", set_hash_m, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "eql?", set_eql, MRB_ARGS_REQ(1)); + + mrb_define_method(mrb, set, "join", set_join, MRB_ARGS_OPT(1)); + mrb_define_method(mrb, set, "inspect", set_inspect, MRB_ARGS_NONE()); + mrb_define_method(mrb, set, "to_s", set_inspect, MRB_ARGS_NONE()); + + mrb_define_method(mrb, set, "reset", set_reset, MRB_ARGS_NONE()); + + /* Bulk operation methods */ + mrb_define_method(mrb, set, "add_all", set_add_all, MRB_ARGS_ANY()); + mrb_define_method(mrb, set, "delete_all", set_delete_all, MRB_ARGS_ANY()); + mrb_define_method(mrb, set, "include_all?", set_include_all_p, MRB_ARGS_ANY()); + mrb_define_method(mrb, set, "include_any?", set_include_any_p, MRB_ARGS_ANY()); + +#ifdef MRB_SET_ENABLE_STATS + /* Performance monitoring methods */ + mrb_define_class_method(mrb, set, "stats", set_s_stats, MRB_ARGS_NONE()); + mrb_define_class_method(mrb, set, "reset_stats", set_s_reset_stats, MRB_ARGS_NONE()); +#endif +} + +void +mrb_mruby_set_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/mruby-set/test/set.rb b/mrbgems/mruby-set/test/set.rb index 72049b150..329830e02 100644 --- a/mrbgems/mruby-set/test/set.rb +++ b/mrbgems/mruby-set/test/set.rb @@ -627,6 +627,6 @@ end # end # assert("Set#inspect") do - set = Set[1,2,3] - assert_equal("#", set.inspect) + set = Set[1,1,1] + assert_equal("#", set.inspect) end