Project

General

Profile

Feature #11815

Updated by CaryInVictoria (Cary Swoveland) over 8 years ago

I propose that a method `Array#difference` be added to the Ruby core. It is similar to [Array#-](http://ruby-doc.org/core-2.2.0/Array.html#method-i-2D) but for each element of the (array) argument it removes only one matching element from the receiver. For example: 

     a = [1,2,3,4,3,2,2,4] 
     b = [2,3,4,4,4] 

     a - b #=> [1] 
     c = a.difference b #=> [1, 3, 2, 2]  

 As you see, `a` contains three `2`'s and `b` contains `1`, so the first `2` in `a` has been removed from `a` in constructing `c`. When `b` contains as least as many instances of an element as does `a`, `c` contains no instances of that element.  

 It could be implemented as follows: 

     

      class Array 
       
        def difference(other) 
         h 
          dup.tap do |cpy| 
            other.each do |e| 
              ndx = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 } cpy.index(e) 
              cpy.delete_at(ndx) if ndx 
             end 
           end 
         reject { |e| h[e] > 0 && h[e] -= 1 } end 
       end 
     end 

 Here are a few examples of its use: 

 *Determine if two arrays of the same size contain the same elements* 

       a = [2,1,4,2,5,3,3,1] 
       b = [3,4,1,1,2,3,5,2]  
       a.difference(b).empty? 
         #=> true 

 *Identify an array's unique elements* 

       a = [1,3,2,4,3,4] 
       u = a.uniq #=> [1, 2, 3, 4] 
       u - a.difference(u) #=> [1, 2] 

 *Identify a maximal number of 1-1 matches between the elements of two arrays and return an array of all elements from both arrays that were not matched* 

       a = [1, 2, 4, 2, 1, 7, 4, 2, 9]  
       b = [4, 7, 3, 2, 2, 7]  
       a.difference(b).concat(b.difference(a)) 
         #=> [1, 1, 4, 2, 9, 3, 7]  

 To remove elements from `a` starting at the end (rather the beginning) of `a`: 

     a = [1,2,3,4,3,2,2,4] 
     b = [2,3,4,4,4] 

     a.reverse.difference(b).reverse #=> [1,2,3,2] 

 `Array#difference!` could be defined in the obvious way. 

 Note: I initially wrote the method as follows: 

      class Array 
        def difference(other) 
          dup.tap do |cpy| 
            other.each do |e| 
              ndx = cpy.index(e) 
              cpy.delete_at(ndx) if ndx 
             end 
           end 
         end 
       end 

Back