difference between two lists that include duplicates

Viewed 78

I have a problem with two lists which contain duplicates

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

I would like to be able to extract the differences between the two lists ie.

c = [1,4]
but if I do c = a-b I get c =[]

It should be trivial but I can't find out :( I tried also to parse the biggest list and remove items from it when I find them in the smallest list but I can't update lists on the fly, it does not work either

has anyone got an idea ? thanks

2 Answers

You see an empty c as a result, because removing e.g. 1 removes all elements that are equal 1.

groovy:000> [1,1,1,1,1,2] - 1
===> [2]

What you need instead is to remove each occurrence of specific value separately. For that, you can use Groovy's Collection.removeElement(n) that removes a single element that matches the value. You can do it in a regular for-loop manner, or you can use another Groovy's collection method, e.g. inject to reduce a copy of a by removing each occurrence separately.

def c = b.inject([*a]) { acc, val -> acc.removeElement(val); acc }

assert c == [1,4]

Keep in mind, that inject method receives a copy of the a list (expression [*a] creates a new list from the a list elements.) Otherwise, acc.removeElement() would modify an existing a list. The inject method is an equivalent of a popular reduce or fold operation. Each iteration from this example could be visualized as:

--inject starts--
acc = [1,1,2,3,4,4]; val = 1; acc.removeElement(1) -> return [1,2,3,4,4] 
acc = [1,2,3,4,4]; val = 2; acc.removeElement(2) -> return [1,3,4,4]
acc = [1,3,4,4]; val = 3; acc.removeElement(3) -> return [1,4,4]
acc = [1,4,4]; val = 4; acc.removeElement(4) -> return [1,4]
-- inject ends -->

PS: Kudos to almighty tim_yates who recommended improvements to that answer. Thanks, Tim!

the most readable that comes to my mind is:

a = [1,1,2,3,4,4]
b = [1,2,3,4]
c = a.clone()
b.each {c.removeElement(it)}

if you use this frequently you could add a method to the List metaClass:

List.metaClass.removeElements = { values -> values.each { delegate.removeElement(it) } }
a = [1,1,2,3,4,4]
b = [1,2,3,4]
c = a.clone()
c.removeElements(b)
Related