1

There have been other questions about How to compare arrays in JavaScript?. What I want to know is the most straightforward way to write/use a three-way comparison function like the one required by Array.sort(). Here's an example using the default one which doesn't work well:

> [ [4,5,10], [4,5,6], [4,1,2] ].sort() // no compare function, uses the default one
[ [ 4, 1, 2 ],
  [ 4, 5, 10 ], // oops, string sorting makes 10 < 6
  [ 4, 5, 6 ] ]

This is what I came up with:

// return -1 if lhs is "less" than rhs, +1 if "greater", and 0 if equal
// if lhs and rhs have different lengths, only the shorter part will be considered
function compareArrays(lhs, rhs) {
  for (var ii = 0; ii < lhs.length; ii++) {
    if (lhs[ii] < rhs[ii]) {
      return -1;
    } else if (lhs[ii] > rhs[ii]) {
      return 1;
    }
  }
  return 0;
}

Which gives us what we want:

> [ [4,5,10], [4,5,6], [4,1,2] ].sort(compareArrays)
[ [ 4, 1, 2 ],
  [ 4, 5, 6 ],
  [ 4, 5, 10 ] ]

Is there something more like a one-liner, or must I define my own function whenever I want to do this?

Supporting old browsers is not a requirement. Using libraries like jQuery or Underscore is OK.

One way to look at this is "The first nonzero value from a standard three-way compare applied to each pair of elements." But even then I haven't found a good fit in existing libraries.

4
  • Isn't your current solution a "one liner"? You could reduce the sort function to an almost one liner using forEach and the conditional operator ?:, but that's just going to reduce the amount of code by a few characters in one place, not make it faster or more reliable. Commented May 27, 2014 at 5:59
  • 1
    It's not going to get much smaller than your compareArrays() function. Commented May 27, 2014 at 6:00
  • "Using libraries like jQuery or Underscore is OK." - Well then, turn your function into a library. There is your one-liner. Commented May 27, 2014 at 6:52
  • Btw, your function does not work for arrays of different length. Commented May 27, 2014 at 9:35

3 Answers 3

1

I'd go with a generic comparison-maker function to be used in a functional way:

function compareArrays(compareItems) {
    return function(a, b) {
        for (var r, i=0, l=Math.min(a.length, b.length); i<l; i++)
            if (0 != (r = compareItems(a[i], b[i])))
                return r;
        return a.length - b.length;
     };
}
// Examples:
var compareNumberArrays = compareArray(function(a,b){ return a-b; }),
    compareGenericArrays = compareArray(function(a,b){ return +(a>b)||-(b>a); });

Now you can use

[ [4,5,10], [4,5,6], [4,1,2], [4,5] ].sort(compareNumberArrays)

Is there something more like a one-liner, or must I define my own function whenever I want to do this?

Comparing arrays is too complex for a one-liner, you should use a helper function. There's no built-in one that you can use everywhere.

Sign up to request clarification or add additional context in comments.

Comments

1

Probably not the shortest possible, but the fewest lines I can get to sensibly is:

function compareArrays(lhs, rhs) {
  var result;
  lhs.some(function(v, i) {
    return (result = v - rhs[i]);
  });
  return result;
}

or less sensibly:

function compareArrays(lhs, rhs, r) {
  lhs.some(function(v, i) {return (r = v - rhs[i])});
  return r;
}

Edit

Seems you don't want numbers. The compare part can be any relationship you want, e.g. for strings:

function compareArrays(lhs, rhs, r) {
  lhs.some(function(v, i) {return (r = v < rhs[i]? -1 : v > rhs[i]? 1 : 0)});
  return r;
}

[['a','b','c'],['a','c','d'],['a','b','d']].sort(compareArrays) // a,b,c,a,b,d,a,c,d 

But the compare function needs to know what it's getting so it doesn't sort numbers as strings or strings as numbers (and so on…).

7 Comments

Thanks--this use of some() is interesting and fits my conceptual formulation of the problem. Now if only there were a standard "three-way compare" function matching the semantics of the one applied by after to-stringing in sort()....
On further consideration, I realize that what I'd really want from some() is more like "Return the first truthy value produced by the callback, or a specified default value if none found." I looked for something like this in Underscore and Lo-dash and came up empty-handed.
This function does not work for arrays of different length. Do you think you can adapt it easily?
@JohnZwinck—isn't that what my answer does? some returns the first truthy result or defaults to 0 if none found.
@Bergi—yes, provided rules are provided on how to handle them, e.g. include an i in rhs test or similar and a value to return if it returns false.
|
0

If you know that they are always triplets of numbers (arrays of length 3), you could do this:

function combineNum(array) {
    return (array[0] * 100) + (array[1] * 10) + array[2];
}

function compareArrays(lhs, rhs) {
    return combineNum(rhs) - combineNum(lhs);
}

6 Comments

That also assumes the values in the arrays are always numeric, which is not always the case for me.
@JohnZwinck - yes it does assume that. All your examples were numeric so I thought that was the problem you were trying to solve. Oh well, that was my shot at shortening it. So, what is your real objective here? You have a working example of code. There only needs to be one copy of it in your project and you can then just refer to it. Why are you trying to shave a line or two off it?
It seemed like something that should be possible without hauling around such a trivial function. I guess I'm accustomed to languages where things like comparing arrays is more built-in...but even then, this felt like something that jQuery or Underscore should be able to do without such tedium. Also, even my working function is not optimal: it wastes time if lhs.length is very different from rhs.length...I could add more code to fix that, but this is basically a waste of time if there's an existing function that could do it.
That's also assuming that they are single digits (or at least of equal magnitude), not any numbers.
@Bergi - Yes, if you want to allow much larger numbers, you can spread out the weighting factors as much as you want. Just trying to offer another conceptual idea here that doesn't have to do so many cell by cell comparisons.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.