Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ramda: Check if two arrays are equal

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.

like image 513
J. Hesters Avatar asked Apr 09 '19 12:04

J. Hesters


Video Answer


3 Answers

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/[email protected]"></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.

like image 197
Scott Sauyet Avatar answered Oct 05 '22 03:10

Scott Sauyet


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
like image 29
customcommander Avatar answered Oct 05 '22 01:10

customcommander


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]) // -> []

like image 31
bugs Avatar answered Oct 05 '22 02:10

bugs