From b123a839847161322359a2177f4a9f3045ebbfd2 Mon Sep 17 00:00:00 2001 From: Owen Stephens Date: Thu, 22 Feb 2018 01:44:58 +0000 Subject: [PATCH v2] range.c: add subset/superset methods For similarity with Set, add methods to Range: Range#subset? (aliased as <=) Range#strict_subset? (aliased as <) Range#superset? (aliased as >=) Range#strict_superset? (aliased as >) which compare a range to another Range, with behaviour equivalent to converting both Ranges to a Set before calling the corresponding Set method. --- range.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++- test/ruby/test_range.rb | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/range.c b/range.c index 589755753f..190900ee44 100644 --- a/range.c +++ b/range.c @@ -18,7 +18,7 @@ #include VALUE rb_cRange; -static ID id_beg, id_end, id_excl, id_integer_p, id_div; +static ID id_beg, id_end, id_excl, id_integer_p, id_div, id_min, id_max; #define id_cmp idCmp #define id_succ idSucc @@ -1201,6 +1201,115 @@ r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val) return Qfalse; } +/* + * call-seq: + * rng.subset?(obj) -> true or false + * rng <= obj -> true or false + * + * Returns true if +obj+ is a Range and it includes the first and + * last elements of the range, false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."d").subset?("b".."e") #=> true + * ("b".."e").subset?("b".."e") #=> true + * ("a".."e").subset?("b".."e") #=> false + */ + +static VALUE +range_subset(VALUE range, VALUE obj) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + VALUE min = rb_funcall(range, id_min, 0); + if (min == Qnil) + return Qtrue; + + if (range_cover(obj, min) && + range_cover(obj, rb_funcall(range, id_max, 0))) { + return Qtrue; + } + return Qfalse; +} + +static VALUE +r_differ_at(VALUE range, VALUE obj, ID id) +{ + return !rb_equal(rb_funcall(range, id, 0), rb_funcall(obj, id, 0)); +} + +/* + * call-seq: + * rng.strict_subset?(obj) -> true or false + * rng < obj -> true or false + * + * Returns true if +obj+ is a Range and it includes the first and + * last elements of the range, but is not equal to the range, + * false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."d").strict_subset?("b".."e") #=> true + * ("b".."e").strict_subset?("b".."e") #=> false + * ("a".."e").strict_subset?("b".."e") #=> false + */ + +static VALUE +range_strict_subset(VALUE range, VALUE obj) +{ + if (range_subset(range, obj) && + (r_differ_at(range, obj, id_min) || r_differ_at(range, obj, id_max))) { + return Qtrue; + } + return Qfalse; +} + +/* + * call-seq: + * rng.superset?(obj) -> true or false + * rng >= obj -> true or false + * + * Returns true if +obj+ is a Range, and its first and last + * elements are included in the range, false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."e").superset?("b".."d") #=> true + * ("b".."e").superset?("b".."e") #=> true + * ("b".."e").superset?("a".."e") #=> false + */ + +static VALUE +range_superset(VALUE range, VALUE obj) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + return range_subset(obj, range); +} + +/* + * call-seq: + * rng.strict_superset?(obj) -> true or false + * rng > obj -> true or false + * + * Returns true if +obj+ is a Range, and its first and last + * elements are included in the range, but it is not equal to the range, + * false otherwise. + * Raises +TypeError+ if +obj+ is not a Range. + * + * ("b".."e").strict_superset?("b".."d") #=> true + * ("b".."e").strict_superset?("b".."e") #=> false + * ("b".."e").strict_superset?("a".."e") #=> false + */ + +static VALUE +range_strict_superset(VALUE range, VALUE obj) +{ + if (!rb_obj_is_kind_of(obj, rb_cRange)) + rb_raise(rb_eTypeError, "not a range object"); + + return range_strict_subset(obj, range); +} + static VALUE range_dumper(VALUE range) { @@ -1310,6 +1419,8 @@ Init_Range(void) id_excl = rb_intern("excl"); id_integer_p = rb_intern("integer?"); id_div = rb_intern("div"); + id_min = rb_intern("min"); + id_max = rb_intern("max"); rb_cRange = rb_struct_define_without_accessor( "Range", rb_cObject, range_alloc, @@ -1341,4 +1452,14 @@ Init_Range(void) rb_define_method(rb_cRange, "member?", range_include, 1); rb_define_method(rb_cRange, "include?", range_include, 1); rb_define_method(rb_cRange, "cover?", range_cover, 1); + + rb_define_method(rb_cRange, "subset?", range_subset, 1); + rb_define_method(rb_cRange, "<=", range_subset, 1); + rb_define_method(rb_cRange, "strict_subset?", range_strict_subset, 1); + rb_define_method(rb_cRange, "<", range_strict_subset, 1); + + rb_define_method(rb_cRange, "superset?", range_superset, 1); + rb_define_method(rb_cRange, ">=", range_superset, 1); + rb_define_method(rb_cRange, "strict_superset?", range_strict_superset, 1); + rb_define_method(rb_cRange, ">", range_strict_superset, 1); } diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index fef7acc062..82dba7ad23 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -379,6 +379,92 @@ def test_cover assert_operator("a".."z", :cover?, "cc") end + def test_subset + [:subset?, :<=].each do |op| + assert_operator("b".."e", op, "b".."e") + assert_operator("b".."e", op, "b"..."f") + assert_operator("b"..."f", op, "b".."e") + assert_operator("b".."d", op, "b".."e") + assert_operator("c".."e", op, "b".."e") + assert_operator("c".."d", op, "b".."e") + assert_operator("g".."f", op, "b".."e") + assert_operator("c".."b", op, "d".."a") + + assert_not_operator("a".."e", op, "b".."e") + assert_not_operator("a".."e", op, "b"..."f") + assert_not_operator("a"..."f", op, "b".."e") + assert_not_operator("b".."f", op, "b".."e") + assert_not_operator("a".."f", op, "b".."e") + + assert_raise(TypeError) { ("a".."c").send(op, "x") } + end + end + + def test_strict_subset + [:strict_subset?, :<].each do |op| + assert_operator("b".."d", op, "b".."e") + assert_operator("b".."d", op, "b"..."f") + assert_operator("b"..."e", op, "b".."e") + assert_operator("c".."e", op, "b".."e") + assert_operator("c".."d", op, "b".."e") + assert_operator("g".."f", op, "b".."e") + + assert_not_operator("b".."e", op, "b".."e") + assert_not_operator("b".."e", op, "b"..."f") + assert_not_operator("b"..."f", op, "b".."e") + assert_not_operator("a".."e", op, "b".."e") + assert_not_operator("b".."f", op, "b".."e") + assert_not_operator("a".."f", op, "b".."e") + assert_not_operator("c".."b", op, "d".."a") + + assert_raise(TypeError) { ("a".."c").send(op, "x") } + end + end + + def test_superset + [:superset?, :>=].each do |op| + assert_operator("b".."e", op, "b".."e") + assert_operator("b".."e", op, "b"..."f") + assert_operator("b"..."f", op, "b".."e") + assert_operator("b".."e", op, "b".."d") + assert_operator("b".."e", op, "c".."e") + assert_operator("b".."e", op, "c".."d") + assert_operator("b".."e", op, "g".."f") + assert_operator("d".."a", op, "c".."b") + + assert_not_operator("b".."e", op, "a".."e") + assert_not_operator("b".."e", op, "a"..."f") + assert_not_operator("b"..."f", op, "a".."e") + assert_not_operator("b".."e", op, "b".."f") + assert_not_operator("b".."e", op, "a".."f") + assert_not_operator("b".."e", op, "b".."f") + assert_not_operator("b".."e", op, "a".."f") + + assert_raise(TypeError) { ("a".."c").send(op, "x") } + end + end + + def test_strict_superset + [:strict_superset?, :>].each do |op| + assert_operator("b".."e", op, "b".."d") + assert_operator("b".."e", op, "b"..."e") + assert_operator("b"..."f", op, "b".."d") + assert_operator("b".."e", op, "c".."e") + assert_operator("b".."e", op, "c".."d") + assert_operator("b".."e", op, "g".."f") + + assert_not_operator("b".."e", op, "b".."e") + assert_not_operator("b".."e", op, "b"..."f") + assert_not_operator("b"..."f", op, "b".."e") + assert_not_operator("b".."e", op, "a".."e") + assert_not_operator("b".."e", op, "b".."f") + assert_not_operator("b".."e", op, "a".."f") + assert_not_operator("d".."a", op, "c".."b") + + assert_raise(TypeError) { ("a".."c").send(op, "x") } + end + end + def test_beg_len o = Object.new assert_raise(TypeError) { [][o] } -- 2.15.0