简体   繁体   中英

Sort with ramda

I am in a situation where I need to sort an array of objects by the name.

    Array = [
           {id: 3d4, name: A2},
           {id: 7b2, name: A1+ Beta},
           {id: 9h5, name: A2 Beta},
           {id: 1x1, name: A1+}
          ]

How can I use Ramda to sort it in this order:

  1. A1+
  2. A2
  3. A1+ Beta
  4. A2 Beta

I already thought of split by name length and then ordering, but I'm stuck on it. Can someone help me with this?

Found on documentation of Ramda, is pretty simple, you create your filtering function with something like: const sortByNameCaseInsensitive = R.sortBy(R.compose(R.toLower, R.prop('name')));

Then you apply your array in it sortByNameCaseInsensitive(arr);

You can try Reversing R.reverse the array after splitting it by new RegExp('[+]?\\s') .

 const array = [ {id: '3d4', name: 'A2'}, {id: '9hh', name: 'A3 Beta'}, {id: '7b2', name: 'A1+ Beta'}, {id: '9h5', name: 'A2 Beta'}, {id: '1x1', name: 'A1+'}, {id: '1x2', name: 'A3+'} ]; // pick name and convert to lowercase if required. const namePropCompose = R.compose(R.toLower, R.prop('name')); // split by '+ ' or ' ' and reverse the array. const splitNameAndReverse = R.compose(R.reverse, R.split(new RegExp('[+]?\\s'))); // finally sort with reversed array const sortByNameCaseInsensitive = R.sortBy(R.compose(splitNameAndReverse, namePropCompose)); const result = sortByNameCaseInsensitive(array); console.log(JSON.stringify(result))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

I separated all of them into different composes for better readability. Regex and toLower may not be that useful here, in short if you want you can do all of them in one line.

sortBy(compose(reverse, split(' '), prop('name')))

I think the ['A1+', 'A2', 'A1+ Beta', 'A2 Beta'] ranking is arbitrary enough to probably suggest to order their priority manually.

  1. find the position of each rank within the scores array
  2. Subtract their scores as a - b

 const scores = ['A1+', 'A2', 'A1+ Beta', 'A2 Beta']; const findScorePriority = R.pipe( R.prop('name'), R.equals, R.findIndex(R.__, scores), ); const byPriority = R.useWith( R.subtract, [findScorePriority, findScorePriority], ); const data = [ { id: '3d4', name: 'A2' }, { id: '7b2', name: 'A1+ Beta' }, { id: '9h5', name: 'A2 Beta' }, { id: '1x1', name: 'A1+' }, ]; console.log( R.sort(byPriority, data), );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>

It's not clear enough from your example whether your sort order is based on a simple fixed list or if you didn't supply enough examples to demonstrate the general principle. If it's the former, then some variant of the technique described by hitmands is what you want.

Here we assume you want a primary sort by the first half of the name field ( 'A1+' , 'A2' , etc.) and a secondary sort by the second half (such as 'Beta' ) if one exists.

We can write this easily enough in vanilla JS, like this:

const customSort = (array) =>
  [...array] .sort ((
    {name: a}, {name: b}, 
    [a0, a1 = ''] = a.split(' '), [b0, b1 = ''] = b.split(' ')
  ) =>
    a0 < b0 ? -1 : a0 > b0 ? 1 : a1 < b1 ? -1 : a1 > b1 ? 1 : 0
  )

Ramda offers two things that the native Array.prototype.sort does not: first, it uses functions and not methods , and therefore is easier to compose with other functions. Second, it is non-destructive. Ramda sort functions do not mutate the array in place but return a sorted copy of it.

Ramda offers three main functions to deal with sorting. First there is sort , which is similar to this:

const sort = (comp) => (array) => 
  [... array] .sort (comp)

Then there is sortBy , which takes a function from an element in your array to some ordered type ( String , Number , Date , {valueOf} ) and sorts the array ascending according to the results of that function.

And there is sortWith , which is much like sortBy , but takes an array of such functions, so that if the first two are tied according to the first function, then we test with the second, and so on.

There are three helper functions, descend and its slightly superfluous twin ascend , which takes the same sort of function passed to sortBy or sortWith and returns a function suitable for passing to sort or to Array.prototype.sort . We might use them like this:

const oldestFirst = sort (descend (prop ('age')))
// ...
const reordered = oldestFirst (people)

or like

people .sort (descend (prop('age')) // WARNING: mutates `people`

And we have comparator , which translates a binary predicate that reports whether the first item is less than the second into a comparator function suitable again for sort or Array.prototype.sort .

Since we want a two level sort for this, sortWith would be appropriate. We could write it using the ascend helper like this:

const customSort = sortWith ([
  ascend (({name}) => name .split (' ') [0]),
  ascend (({name}) => name .split (' ') [1] || ''),
])

And although I'm pretty happy with that, some prefer going completely point-free, and it's not too hard in this case with:

const customSort = sortWith ([
  ascend (pipe (prop ('name'), split (' '), prop(0))),
  ascend (pipe (prop ('name'), split (' '), propOr('', 1)))
])

These approaches are to my eyes much more readable than the vanilla version. The only downside I see to them is that they require calling split twice for every comparison, where the vanilla version can do it just once. Because of that, and because of the extra function calls, they will certainly be less performant than the vanilla version, but I would expect them to be fast enough for many uses.

You can see all these options in this snippet:

 const customSort1 = (array) => [...array].sort (( {name: a}, {name: b}, [a0, a1 = ''] = a.split(' '), [b0, b1 = ''] = b.split(' ') ) => a0 < b0? -1: a0 > b0? 1: a1 < b1? -1: a1 > b1? 1: 0 ) const customSort2 = sortWith ([ ascend (({name}) => name.split (' ') [0]), ascend (({name}) => name.split (' ') [1] || ''), ]) const customSort3 = sortWith ([ ascend (pipe (prop ('name'), split (' '), prop(0))), ascend (pipe (prop ('name'), split (' '), propOr('', 1))) ]) const array = [{id: '3d4', name: 'A2'}, {id: '9hh', name: 'A3 Beta'}, {id: '7b2', name: 'A1+ Beta'}, {id: '9h5', name: 'A2 Beta'}, {id: '5h4', name: 'A1+ Alpha'}, {id: '1x1', name: 'A1+'}, {id: '1x2', name: 'A2 Alpha'}, {id: '1x3', name: 'A3+'}] console.log (customSort1 (array)) console.log (customSort2 (array)) console.log (customSort3 (array))
 .as-console-wrapper {min-height: 100%;important: top: 0}
 <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js"></script> <script> const {sortWith, split, ascend, pipe, prop, propOr} = R </script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM