mruby-set: implement comparison methods in C for better performance

Reimplemented the following methods in C for improved efficiency:
- superset? and proper_superset? (>= and >)
- subset? and proper_subset? (<= and <)
- intersect? and disjoint?
- <=> comparison operator
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-06-21 14:47:09 +09:00
parent 60f06e7a0f
commit deb5c47fc6
2 changed files with 370 additions and 76 deletions
+6 -76
View File
@@ -178,70 +178,14 @@ class Set
end
end
# Returns true if this set is a superset of the given set.
#
# @param [Set] set The set to check against
# @return [Boolean] true if this set is a superset of the given set
def superset?(set)
raise ArgumentError, "value must be a set" unless set.is_a?(Set)
return false if size < set.size
set.all? { include?(_1) }
end
alias >= superset?
# superset? and proper_superset? are now implemented in C
# See mrbgems/mruby-set/src/set.c
# Returns true if this set is a proper superset of the given set.
#
# @param [Set] set The set to check against
# @return [Boolean] true if this set is a proper superset of the given set
def proper_superset?(set)
raise ArgumentError, "value must be a set" unless set.is_a?(Set)
return false if size <= set.size
set.all? { include?(_1) }
end
alias > proper_superset?
# subset? and proper_subset? are now implemented in C
# See mrbgems/mruby-set/src/set.c
# Returns true if this set is a subset of the given set.
#
# @param [Set] set The set to check against
# @return [Boolean] true if this set is a subset of the given set
def subset?(set)
raise ArgumentError, "value must be a set" unless set.is_a?(Set)
return false if set.size < size
all? { set.include?(_1) }
end
alias <= subset?
# Returns true if this set is a proper subset of the given set.
#
# @param [Set] set The set to check against
# @return [Boolean] true if this set is a proper subset of the given set
def proper_subset?(set)
raise ArgumentError, "value must be a set" unless set.is_a?(Set)
return false if set.size <= size
all? { set.include?(_1) }
end
alias < proper_subset?
# Returns true if this set and the given set have at least one element in common.
#
# @param [Set] set The set to check against
# @return [Boolean] true if the sets intersect
def intersect?(set)
raise ArgumentError, "value must be a set" unless set.is_a?(Set)
if size < set.size
any? { set.include?(_1) }
else
set.any? { include?(_1) }
end
end
# Returns true if this set and the given set have no elements in common.
#
# @param [Set] set The set to check against
# @return [Boolean] true if the sets are disjoint
def disjoint?(set)
!intersect?(set)
end
# intersect? and disjoint? are now implemented in C
# See mrbgems/mruby-set/src/set.c
# Iterates over each element in the set.
#
@@ -314,20 +258,6 @@ class Set
end
alias filter! select!
# Compares this set with another set.
#
# @param [Set] set The set to compare with
# @return [Integer, nil] -1, 0, 1, or nil if not comparable
def <=>(set)
return unless set.is_a?(Set)
case size <=> set.size
when -1 then -1 if proper_subset?(set)
when +1 then +1 if proper_superset?(set)
else 0 if self.==(set)
end
end
# Classifies the elements of the set by the result of the given block.
#
# @yield [Object] Each element in the set
+364
View File
@@ -636,6 +636,354 @@ set_eql(mrb_state *mrb, mrb_value self)
return mrb_true_value();
}
/*
* call-seq:
* set.superset?(other) -> true or false
* set >= other -> true or false
*
* Returns true if the set is a superset of the given set.
*/
static mrb_value
set_superset_p(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!other_kh || kh_size(other_kh) == 0) {
return mrb_true_value(); /* Empty set is a subset of any set */
}
if (!self_kh) {
return mrb_false_value(); /* Empty set is not a superset of a non-empty set */
}
/* Check size first - a superset must be at least as large as the subset */
if (kh_size(self_kh) < kh_size(other_kh)) {
return mrb_false_value();
}
/* Check if all elements in other are in self */
for (khiter_t k = kh_begin(other_kh); k != kh_end(other_kh); k++) {
if (kh_exist(other_kh, k)) {
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
if (self_k == kh_end(self_kh)) {
return mrb_false_value(); /* Element in other not found in self */
}
}
}
return mrb_true_value();
}
/*
* call-seq:
* set.proper_superset?(other) -> true or false
* set > other -> true or false
*
* Returns true if the set is a proper superset of the given set.
*/
static mrb_value
set_proper_superset_p(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!other_kh || kh_size(other_kh) == 0) {
/* Empty set is a proper subset of any non-empty set */
return self_kh && kh_size(self_kh) > 0 ? mrb_true_value() : mrb_false_value();
}
if (!self_kh) {
return mrb_false_value(); /* Empty set is not a proper superset of any set */
}
/* For a proper superset, self must be strictly larger than other */
if (kh_size(self_kh) <= kh_size(other_kh)) {
return mrb_false_value();
}
/* Check if all elements in other are in self */
for (khiter_t k = kh_begin(other_kh); k != kh_end(other_kh); k++) {
if (kh_exist(other_kh, k)) {
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
if (self_k == kh_end(self_kh)) {
return mrb_false_value(); /* Element in other not found in self */
}
}
}
return mrb_true_value();
}
/*
* call-seq:
* set.subset?(other) -> true or false
* set <= other -> true or false
*
* Returns true if the set is a subset of the given set.
*/
static mrb_value
set_subset_p(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!self_kh || kh_size(self_kh) == 0) {
return mrb_true_value(); /* Empty set is a subset of any set */
}
if (!other_kh) {
return mrb_false_value(); /* Non-empty set is not a subset of an empty set */
}
/* Check size first - a subset cannot be larger than its superset */
if (kh_size(other_kh) < kh_size(self_kh)) {
return mrb_false_value();
}
/* Check if all elements in self are in other */
for (khiter_t 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(); /* Element in self not found in other */
}
}
}
return mrb_true_value();
}
/*
* call-seq:
* set.proper_subset?(other) -> true or false
* set < other -> true or false
*
* Returns true if the set is a proper subset of the given set.
*/
static mrb_value
set_proper_subset_p(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!self_kh || kh_size(self_kh) == 0) {
/* Empty set is a proper subset of any non-empty set */
return other_kh && kh_size(other_kh) > 0 ? mrb_true_value() : mrb_false_value();
}
if (!other_kh) {
return mrb_false_value(); /* Non-empty set is not a proper subset of an empty set */
}
/* For a proper subset, self must be strictly smaller than other */
if (kh_size(other_kh) <= kh_size(self_kh)) {
return mrb_false_value();
}
/* Check if all elements in self are in other */
for (khiter_t 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(); /* Element in self not found in other */
}
}
}
return mrb_true_value();
}
/*
* call-seq:
* set.intersect?(other) -> true or false
*
* Returns true if the set and the given set have at least one element in common.
*/
static mrb_value
set_intersect_p(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be a set");
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!self_kh || !other_kh || kh_size(self_kh) == 0 || kh_size(other_kh) == 0) {
return mrb_false_value(); /* Empty sets have no elements in common */
}
/* Iterate through the smaller set for efficiency */
if (kh_size(self_kh) < kh_size(other_kh)) {
for (khiter_t 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_true_value(); /* Found a common element */
}
}
}
} else {
for (khiter_t k = kh_begin(other_kh); k != kh_end(other_kh); k++) {
if (kh_exist(other_kh, k)) {
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
if (self_k != kh_end(self_kh)) {
return mrb_true_value(); /* Found a common element */
}
}
}
}
return mrb_false_value(); /* No common elements found */
}
/*
* call-seq:
* set.disjoint?(other) -> true or false
*
* Returns true if the set and the given set have no elements in common.
*/
static mrb_value
set_disjoint_p(mrb_state *mrb, mrb_value self)
{
mrb_value result = set_intersect_p(mrb, self);
return mrb_bool_value(!mrb_test(result));
}
/*
* call-seq:
* set <=> other -> -1, 0, +1, or nil
*
* Compares this set with another set.
* Returns -1 if this set is a proper subset of the other set,
* +1 if this set is a proper superset of the other set,
* 0 if the sets are equal,
* or nil if the sets cannot be compared (they are neither subsets nor supersets).
*/
static mrb_value
set_cmp(mrb_state *mrb, mrb_value self)
{
mrb_value other = mrb_get_arg1(mrb);
/* Check if other is a Set */
if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Set"))) {
return mrb_nil_value();
}
khash_t(set) *self_kh = set_get_khash(mrb, self);
khash_t(set) *other_kh = set_get_khash(mrb, other);
/* Handle empty sets */
if (!self_kh || kh_size(self_kh) == 0) {
if (!other_kh || kh_size(other_kh) == 0) {
return mrb_fixnum_value(0); /* Both empty, they're equal */
}
return mrb_fixnum_value(-1); /* Empty set is a proper subset of any non-empty set */
}
if (!other_kh || kh_size(other_kh) == 0) {
return mrb_fixnum_value(1); /* Any non-empty set is a proper superset of an empty set */
}
/* Compare sizes */
int size_cmp = kh_size(self_kh) - kh_size(other_kh);
if (size_cmp < 0) {
/* self might be a proper subset of other */
mrb_bool is_subset = TRUE;
for (khiter_t 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)) {
is_subset = FALSE;
break;
}
}
}
if (is_subset) {
return mrb_fixnum_value(-1); /* self is a proper subset of other */
}
}
else if (size_cmp > 0) {
/* self might be a proper superset of other */
mrb_bool is_superset = TRUE;
for (khiter_t k = kh_begin(other_kh); k != kh_end(other_kh); k++) {
if (kh_exist(other_kh, k)) {
khiter_t self_k = kh_get(set, mrb, self_kh, kh_key(other_kh, k));
if (self_k == kh_end(self_kh)) {
is_superset = FALSE;
break;
}
}
}
if (is_superset) {
return mrb_fixnum_value(1); /* self is a proper superset of other */
}
}
else {
/* Same size, check if they're equal */
mrb_bool is_equal = TRUE;
for (khiter_t 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)) {
is_equal = FALSE;
break;
}
}
}
if (is_equal) {
return mrb_fixnum_value(0); /* Sets are equal */
}
}
/* Sets are not comparable */
return mrb_nil_value();
}
/*
* call-seq:
* set.join(separator = nil) -> string
@@ -892,6 +1240,22 @@ mrb_mruby_set_gem_init(mrb_state *mrb)
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());
/* Register our new C implementations */
mrb_define_method(mrb, set, "superset?", set_superset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, ">=", set_superset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "proper_superset?", set_proper_superset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, ">", set_proper_superset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "subset?", set_subset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "<=", set_subset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "proper_subset?", set_proper_subset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "<", set_proper_subset_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "intersect?", set_intersect_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "disjoint?", set_disjoint_p, MRB_ARGS_REQ(1));
mrb_define_method(mrb, set, "<=>", set_cmp, MRB_ARGS_REQ(1));
}
void