summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--array.c60
-rw-r--r--test/ruby/test_array.rb21
3 files changed, 82 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index 7824dfc..ed96bae 100644
--- a/NEWS
+++ b/NEWS
@@ -41,7 +41,7 @@ sufficient information, see the ChangeLog file or Redmine
* New methods:
- * Added `Array#union` instance method.
+ * Added `Array#union` and `Array#difference` instance method.
[Feature #14097]
* Modified methods:
diff --git a/array.c b/array.c
index 7aeec16..8a1eed7 100644
--- a/array.c
+++ b/array.c
@@ -4190,6 +4190,8 @@ ary_recycle_hash(VALUE hash)
* [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
*
* If you need set-like behavior, see the library class Set.
+ *
+ * See Array#difference.
*/
static VALUE
@@ -4222,6 +4224,63 @@ rb_ary_diff(VALUE ary1, VALUE ary2)
/*
* call-seq:
+ * ary.difference(other_ary1, other_ary2,...) -> ary
+ *
+ * Array Difference
+ *
+ * Returns a new array that is a copy of +self+, removing any items
+ * that also appear in any of the +other_ary+s. The order of +self+ is
+ * preserved.
+ *
+ * It compares elements using their #hash and #eql? methods for efficiency.
+ *
+ * [ 1, 1, 2, 2, 3, 3, 4, 5 ].difference([ 1, 2, 4 ]) #=> [ 3, 3, 5 ]
+ * [ 1, 'c', :s, 'yep' ].difference([ 1 ], [ 'a', 'c' ]) #=> [:s, "yep"]
+ *
+ * If you need set-like behavior, see the library class Set.
+ *
+ * See Array#-.
+ */
+
+static VALUE
+rb_ary_difference_multi(int argc, VALUE *argv, VALUE ary)
+{
+ VALUE ary_diff;
+ long i, length;
+ volatile VALUE t0;
+ bool *is_hash = ALLOCV_N(bool, t0, argc);
+ ary_diff = rb_ary_new();
+ length = RARRAY_LEN(ary);
+
+ for (i = 0; i < argc; i++) {
+ argv[i] = to_ary(argv[i]);
+ is_hash[i] = (length > SMALL_ARRAY_LEN && RARRAY_LEN(argv[i]) > SMALL_ARRAY_LEN);
+ if (is_hash[i]) argv[i] = ary_make_hash(argv[i]);
+ }
+
+ for (i = 0; i < RARRAY_LEN(ary); i++) {
+ int j;
+ VALUE elt = rb_ary_elt(ary, i);
+ for (j = 0; j < argc; j++){
+ if (is_hash[j]) {
+ if (st_lookup(rb_hash_tbl_raw(argv[j]), RARRAY_AREF(ary, i), 0))
+ break;
+ }
+ else {
+ if (rb_ary_includes_by_eql(argv[j], elt)) break;
+ }
+ }
+ if (j == argc) rb_ary_push(ary_diff, elt);
+ }
+
+ ALLOCV_END(t0);
+
+ return ary_diff;
+}
+
+
+/*
+ * call-seq:
* ary & other_ary -> new_ary
*
* Set Intersection --- Returns a new array containing unique elements common to the
@@ -6363,6 +6422,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "last", rb_ary_last, -1);
rb_define_method(rb_cArray, "concat", rb_ary_concat_multi, -1);
rb_define_method(rb_cArray, "union", rb_ary_union_multi, -1);
+ rb_define_method(rb_cArray, "difference", rb_ary_difference_multi, -1);
rb_define_method(rb_cArray, "<<", rb_ary_push, 1);
rb_define_method(rb_cArray, "push", rb_ary_push_m, -1);
rb_define_alias(rb_cArray, "append", "push");
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 1440d00..1b102c4 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -276,6 +276,27 @@ class TestArray < Test::Unit::TestCase
assert_equal(@cls[1] * 1000, a - @cls[2])
end
+ def test_difference
+ assert_equal(@cls[], @cls[1].difference(@cls[1]))
+ assert_equal(@cls[1], @cls[1, 2, 3, 4, 5].difference(@cls[2, 3, 4, 5]))
+ assert_equal(@cls[1, 1], @cls[1, 2, 1].difference(@cls[2]))
+ assert_equal(@cls[1, 1, 1, 1], @cls[1, 2, 1, 3, 1, 4, 1, 5].difference(@cls[2, 3, 4, 5]))
+ assert_equal(@cls[], @cls[1, 2, 3, 4].difference(@cls[1], @cls[2], @cls[3], @cls[4]))
+ a = [1]
+ assert_equal(@cls[1], a.difference(@cls[2], @cls[2]))
+ assert_equal(@cls[], a.difference(@cls[1]))
+ assert_equal(@cls[1], a)
+ end
+
+ def test_difference_big_array
+ assert_equal(@cls[1]*64, (@cls[1, 2, 3, 4, 5] * 64).difference(@cls[2, 3, 4] * 64, @cls[3, 5] * 64))
+ assert_equal(@cls[1, 1, 1, 1]*64, (@cls[1, 2, 1, 3, 1, 4, 1, 5] * 64).difference(@cls[2, 3, 4, 5] * 64))
+ a = @cls[1] * 1000
+ assert_equal(@cls[1] * 1000, a.difference(@cls[2], @cls[2]))
+ assert_equal(@cls[], a.difference(@cls[1]))
+ assert_equal(@cls[1] * 1000, a)
+ end
+
def test_LSHIFT # '<<'
a = @cls[]
a << 1