7

I'm still learning functional programming in JavaScript and I enjoy using Ramda a lot.

I have two arrays. I want to check if they have the same values, independent of order. I thought this could be done with equals. But apparently

R.equals([1, 2], [2, 1]) // false

Is there an efficient way to check whether two arrays are equal? My arrays constist of objects and can hold up to X * 10E4 values if that matters with 1 < X < 10.

J. Hesters
  • 13,117
  • 31
  • 133
  • 249

3 Answers3

14

The reason it doesn't work like that -- beyond the fact that the Ramda function is named equals and not isEqual -- is that Arrays are intrinsically ordered containers. [1, 2] is materially different from [2, 1].

The standard unordered container is the Set. Unfortunately that is based on reference equality, so it could get multiple copies of items Ramda would think of as equal. So the most obvious answer will not work properly:

// ** Broken -- do not use **
const eqValues = (a1, a2) => R.equals(new Set(a1), new Set(a2))

console.log(eqValues(
  [{x: 1}, {x: 2}], 
  [{x: 1}, {x: 3}]
)) //=> false
console.log(eqValues(
  [{x: 1}, {x: 2}], 
  [{x: 2}, {x: 1}]
)) //=> true

because it would fail due to a length check in this case:

console.log(eqValues(
  [{x: 1}, {x: 2}, {x: 2}], 
  [{x: 2}, {x: 1}]
)) //=> false, but should be true, since {x: 2} is the same as {x: 2}

Ramda does not expose its internal _Set type -- and perhaps it should -- but it uses them in such functions as difference, and through that in symmetricDifference. These are appropriate functions for testing values whose value equality is in question.

So my answer would be similar to the one from bugs, but I would phrase it a bit differently:

const eqValues = compose(isEmpty, symmetricDifference)

console.log(eqValues(
  [{x: 1}, {x: 2}], 
  [{x: 1}, {x: 3}]
)) //=> false
console.log(eqValues(
  [{x: 1}, {x: 2}], 
  [{x: 2}, {x: 1}]
)) //=> true
console.log(eqValues(
  [{x: 1}, {x: 2}], 
  [{x: 2}, {x: 1}, {x: 1}]
)) //=> true
<script src="https://bundle.run/ramda@0.26.1"></script><script>
const {compose, isEmpty, symmetricDifference} = ramda;   </script>

However, if you need to test multiplicities -- that is, arr1 contains two copies of {x: 42} and arr2 only has one, so they're different -- then I would use the answer from customcommander.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Thank you for you answer, I fixed the error in my question. The objects in the arrays are all unique because of unique id's as values. So you answer should work, right? – J. Hesters Apr 09 '19 at 16:48
  • 3
    _Arrays are intrinsically ordered containers_ this intuition for types in an untyped language like Javascript is rare here on SO. That's just my subjective view, though. +1 –  Apr 09 '19 at 16:56
  • Your implementation is actually the only that passes. customcommander throws a false positive: `const equalValues = (arr1, arr2) => R.eqBy(R.countBy(R.identity), arr1, arr2); equalValues([{ foo: 'bar', baz: null }, { hi: 'ho' }], [{ hello: 'world' }, { foo: 'bar', baz: null }])` – J. Hesters Apr 09 '19 at 17:34
  • @J.Hesters: Yes, if the array elements are guaranteed to be unique, then this should work. But then the first answer I gave would also work in that case. – Scott Sauyet Apr 09 '19 at 22:27
6

I would use eqBy with countBy:

You can use countBy to build a "profile" of your array:

countBy(identity, [1, 2]);
//=> {"1": 1, "2": 1}

countBy(identity, [2, 1]);
//=> {"1": 1, "2": 1}

Then you can compare the two profiles with eqBy:

eqBy(countBy(identity), [1,2], [2,1])
//=> true
customcommander
  • 17,580
  • 5
  • 58
  • 84
  • Hmm, your solution throws a false positive: `const equalValues = (arr1, arr2) => R.eqBy(R.countBy(R.identity), arr1, arr2); equalValues([{ foo: 'bar', baz: null }, { hi: 'ho' }], [{ hello: 'world' }, { foo: 'bar', baz: null }])` – J. Hesters Apr 09 '19 at 17:30
  • For comparing objects the function in countBy needs to be more involved I’m afraid. One option is to produce unique signature for each. – customcommander Apr 09 '19 at 17:32
3

There are many ways to achieve this with Ramda

One that comes to mind is

R.length([1,2]) === R.length([2,1]) && R.isEmpty(R.symmetricDifference([1,2], [2,1]))

edit: using R.difference as opposed to R.symmetricDifference wouldn't work, as the first one only returns the elements in the first list that are not contained in the second list.

R.difference([1,2], [2,1,3]) // -> []

bugs
  • 14,631
  • 5
  • 48
  • 52