1

Let's say I have an array of objects like:

var posts = [{
  queued: false,
  score: 3
}, {
  queued: false,
  score: 2
}, {
  queued: false,
  score: 1
}, {
  queued: 100,
  score: 0
}, {
  queued: 56,
  score: 1
}]

I want to split up based on the queued property, one array has all the queued: false, the other has everything else. I then want to sort by score and join them.

I can do the following:

let queuedPosts = _.filter(posts, function(post) {
  return post.queued !== false;
});
let unqueuedPosts = _.filter(posts, function(post) {
  return post.queued === false;
});

Then sort queuedPosts by its property queued, then unqueuedPosts by score, and posts = _.concat(queuedPosts, unqueuedPosts). This would not be scalable if I were to split by some other property in the future. What would be a cleaner way to do this?

EDIT: Code currently looks like this

 sortPosts(posts) {
    let queuedPosts = _.filter(posts, (post) => (post.queued !== false));
    let unqueuedPosts = _.difference(posts, queuedPosts);
    queuedPosts = orderBy(queuedPosts, 'queued', 'asc');
    unqueuedPosts = orderBy(unqueuedPosts, 'score', 'desc');
    return [...queuedPosts, ...unqueuedPosts];
  }

I believe if I make use of the _.remove method, I can programmatically use another object containing keys, sort order, and limits, I can modify this to dynamically sort depending on user's needs via an input like some checkboxes.

Maurits Rijk
  • 9,789
  • 2
  • 36
  • 53
PGT
  • 1,468
  • 20
  • 34
  • 1
    http://stackoverflow.com/questions/24111535/how-can-i-use-lodash-underscore-to-sort-by-multiple-nested-fields – yantrab Mar 23 '17 at 02:45

4 Answers4

2

I would just use a single .sort() operation to do everything in one step. You don't need to split the array up first.

I found your wording a little confusing, but my interpretation of the desired result is as follows:

  • Non-false queued items to appear first, sorted by queued then score
  • queued: false items to appear last, sorted by score

var posts = [
  { queued: false, score: 3 },
  { queued: false, score: 2 },
  { queued: 100, score: 10 },
  { queued: false, score: 1 },
  { queued: 100, score: 0 },
  { queued: 56, score: 1 }
]

var sorted = posts.slice().sort(function(a,b) {
  return a.queued === b.queued
       ? a.score - b.score
       : !a.queued ? 1 : !b.queued ? -1 : a.queued - b.queued
})

console.log(sorted)
nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • This is perfect in terms of order but was looking for a Lodash solution. The problem being the scalability. If we were to add something like a `date` property, the logic would get pretty complicated as we scale up. It seems I may have caused confusion with my phrasing. – PGT Mar 23 '17 at 05:47
1

To make it more scalable you can simply use a separate filter function like this:

function filter(post) {
   return post.queued !== false;
}

Then call the lodash filter function like:

let queuedPosts = _.filter(posts,filter);
//Use lodash difference to find unqueued posts
let unqueuedPosts = _.difference(posts, queuedPosts);

You can also use _.remove to get two separate arrays like:

let queuedPosts = _.remove(posts,filter);
//posts now has all the unqueued objects.

Edit

To sort and concat you can simply use following single line code to get the desired results (without separating the array)

Edit 2

Allow negative queued values.

const posts = [{
  queued: false,
  score: 3
}, {
  queued: false,
  score: 2
}, {
  queued: false,
  score: 1
}, {
  queued: 100,
  score: 0
}, {
  queued: 56,
  score: 1
}, {
  queued: -3, //Negative queue value
  score: 2
}];

//best possible answer to the problem.
const sortedPosts = _.orderBy(posts,[(p)=>[p.queued!==false,p.queued===false]],['desc','desc']);

console.log(sortedPosts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Apoorv Joshi
  • 389
  • 3
  • 15
  • This may work. The `_.remove()` method may be the best option for scalability. – PGT Mar 23 '17 at 06:02
  • @ryeballar for negative values for `queued` key you would need to replace property array with functions. For eg: `_.orderBy(posts,[(p)=>p.queued !== false, (p)=>p.queued === false],['desc','desc']);` – Apoorv Joshi Mar 23 '17 at 06:27
  • @PGT `_.remove` mutates the original array, you may want to use `_.reject` as a better alternative – ryeballar Mar 23 '17 at 06:31
1

Just sort the elements, using the appropriate sort function.

var posts = [
  {queued: false, score: 3}, 
  {queued: false, score: 2}, 
  {queued: false, score: 1}, 
  {queued: 100, score: 0}, 
  {queued: 56, score: 1}
];

posts.sort((p1, p2) => {
  const q1 = Boolean(p1.queued), q2 = Boolean(p2.queued);
  
  return q1 === q2 ? 
    p1.score - p2.score : // sort by ascending score within group
    q2 - q1;              // sort non-false `queue` values first
});

console.log(posts);

Using lodash:

var posts = [
  {queued: false, score: 3}, 
  {queued: false, score: 2}, 
  {queued: false, score: 1}, 
  {queued: 100, score: 0}, 
  {queued: 56, score: 1}
];

console.log(_.sortBy(posts, [p => !Boolean(p.queued), 'score']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
  • I agree that a single sort function can do the job (and I suggested as much already), but I don't think this one is sorting in the desired order. OP seems to want `queued:false` items at the end, and also, your `q1-q2` part doesn't sort the non-boolean `queued` values. – nnnnnn Mar 23 '17 at 03:35
  • My interpretation of the OP's question is that he **does** want all elements with non-false `queued` grouped together, sorted by score. I have changed the solution to put the `queued: false` elements at the end as you pointed out. –  Mar 23 '17 at 03:38
  • Fair enough. I went ahead and posted my original comment as an answer based on my interpretation. – nnnnnn Mar 23 '17 at 03:42
1

If splitting up is your main worry, you might want to look at the lodash partition function:

const [queuedPosts, unqueuedPosts] = _.partition(posts, 'queued');

Of course you can replace 'queued' by any other predicate.

Maurits Rijk
  • 9,789
  • 2
  • 36
  • 53