92

I've got two arrays of Tasks - created and assigned. I want to remove all assigned tasks from the array of created tasks. Here's my working, but messy, code:

    @assigned_tasks = @user.assigned_tasks
    @created_tasks = @user.created_tasks

    #Do not show created tasks assigned to self
    @created_not_doing_tasks = Array.new
    @created_tasks.each do |task|
        unless @assigned_tasks.include?(task)
            @created_not_doing_tasks << task
        end
    end

I'm sure there's a better way. What is it? Thanks :-)

hobodave
  • 28,925
  • 4
  • 72
  • 77
doctororange
  • 11,670
  • 12
  • 42
  • 58

2 Answers2

189

You can subtract arrays in Ruby:

[1,2,3,4,5] - [1,3,4]  #=> [2,5]

ary - other_ary → new_ary Array Difference

Returns a new array that is a copy of the original array, removing any items that also appear in other_ary. The order is preserved from the original array.

It compares elements using their hash and eql? methods for efficiency.

[ 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 the Array documentation.

Jon Doe
  • 2,172
  • 1
  • 18
  • 35
hobodave
  • 28,925
  • 4
  • 72
  • 77
  • 2
    Arg. Big face-palm moment for me. For some reason I thought that wouldn't work with objects. Worked just fine - thanks! – doctororange Jul 28 '09 at 06:23
  • 32
    careful with this, test it in IRB first, for example: [5, 5, 5, 5] - [5, 5] = [] ... the subtraction removes the unique elements in the array. – hagope Aug 10 '11 at 00:22
  • @hagope, yep, it's greedy one. – poige May 31 '12 at 04:58
  • 9
    Also note, this will not work: `[1,2]-[1,2,3] => []`. But `[1,2,3]-[1,2] => [3]`. Argh. – Zabba Jun 28 '12 at 04:42
  • 19
    If you think in terms of *subtraction* then this last "gotchas" actually make sense. To *subtract* something you aren't asking for a *diff*... you are asking to subtract Y from X... if Y has something not even in X then the result is kind of 'undefined', hence the extra Y-element wouldn't be included in the X-result. – Bane Oct 04 '13 at 19:07
  • 2
    Specifically, `Array#-` is a set difference. It's more an inverse of `Array#|`, a set union, than it is of `Array#+`, concatenation (not a set operation at all!). – ymbirtt Mar 02 '15 at 15:59
  • If you think in terms of subtraction, then if I start with 3 coins labeled 2 ([2,2,2]) and I subtract one coin labeled 2 ([2]), I would end up with 2 coins labeled 2 ([2,2]). So yes, it behaves more like a set. – Hakanai Aug 02 '17 at 04:12
  • 1
    to get intersection `[1,2,3,5] & [1,3,4] => [1, 3]` – Lex Aug 03 '17 at 01:10
14

The above solution

a - b

deletes all instances of elements in array b from array a.

[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ]  #=>  [ 3, 3, 5 ]

In some cases, you want the result to be [1, 2, 3, 3, 5]. That is, you don't want to delete all duplicates, but only the elements individually.

You could achieve this by

class Array
  def delete_elements_in(ary)
    ary.each do |x|
      if index = index(x)
        delete_at(index)
      end
    end
  end
end

test

irb(main):198:0> a = [ 1, 1, 2, 2, 3, 3, 4, 5 ]
=> [1, 1, 2, 2, 3, 3, 4, 5]
irb(main):199:0> b = [ 1, 2, 4 ]
=> [1, 2, 4]
irb(main):200:0> a.delete_elements_in(b)
=> [1, 2, 4]
irb(main):201:0> a
=> [1, 2, 3, 3, 5]

The code works even when the two arrays are not sorted. In the example, the arrays are sorted, but this is not required.

Stephen Newell
  • 7,330
  • 1
  • 24
  • 28
Zack Xu
  • 11,505
  • 9
  • 70
  • 78