0

How can I return a target object from an array based on two properties, one which requires min/max and the other, in this case, is a boolean?

For example, in the below array, I'd like to return the object with the smallest order attribute and whose isComplete is false.

var a = [
    {
        order: 3,
        isComplete: false,
        name: 'A',
    },
    {
        order: 2,
        isComplete: false,
        name: 'B',
    },
    {
        order: 1,
        isComplete: true,
        name: 'C',
    },
    {
        order: 4,
        isComplete: false,
        name: 'D',
    },
    {
        order: 5,
        isComplete: false,
        name: 'E',
    },
];

If I check for order attribute, reduce correctly returns smallest object:

var r = a.reduce((prev, curr) => {
    return (
        prev.order < curr.order 
    ) ? prev : curr;
});

// CORRECT Output: {order: 1, isComplete: true, name: "C"}

However, if I also want to return the one with isComplete as false, Order 4 is incorrectly returned. Expected is the next smallest Order 2 to be returned:

var r = a.reduce((prev, curr) => {
    return (
        prev.order < curr.order
        && 
        !prev.isComplete
    ) ? prev : curr;
});

// INCORRECT Output: {order: 4, isComplete: false, name: "D"}
// EXPECTED Output: {order: 2, isComplete: false, name: "B"}

I'm assuming reduce will not work for what I'm trying to do.

One way I can think of is to pre-filter on the other attributes and then sort, but this doesn't seem optimal:

var r = a.filter(el => !el.isComplete).reduce((prev, curr) => {
    return (
        prev.order < curr.order
    ) ? prev : curr;
});

// CORRECT Output: {order: 2, isComplete: false, name: "B"}
user3871
  • 12,432
  • 33
  • 128
  • 268
  • It looks like you have your logic swapped. You want `(curr.order < prev.order && !curr.isComplete ) ? curr : prev`. Previous is not the previous element in the array, it's the last thing returned from your reudcer. – Andy Ray Jun 03 '21 at 20:34
  • Do you want to sort the array or just want to return an object? – Hassan Imam Jun 03 '21 at 20:35
  • Would be better to do filter by `isComplete` and then do sort? Looks like you have strict requirement. I don't see a need to double sort here. – ulou Jun 03 '21 at 20:36
  • `let r = a.filter((cur) => !cur.isComplete).reduce((prev, cur) => prev.order < cur.order ? prev : cur );` – Souvik Nandi Jun 03 '21 at 20:42
  • 1
    `arr.reduce((r,o) => !o.isComplete && o.order < r.order ? o : r)` – Hassan Imam Jun 03 '21 at 20:42

4 Answers4

1

Use this

let r = a.filter((cur) => !cur.isComplete).reduce((prev, curr) => prev.order < curr.order ? prev : curr );
Souvik Nandi
  • 354
  • 2
  • 6
0

You could sort by

  • isComplete and then by
  • order.

const
    array = [{ order: 3, isComplete: false, name: 'A' }, { order: 2, isComplete: false, name: 'B' }, { order: 1, isComplete: true, name: 'C' }, { order: 4, isComplete: false, name: 'D' }, { order: 5, isComplete: false, name: 'E' }];

array.sort((a, b) =>
    b.isComplete - a.isComplete ||
    a.order - b.order
);

console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You will need to evaluate the boolean comparison. If they are equal, move onto the order. If they are not equal, reverse the sort by multiplying by -1.

If you want to find the first incomplete, using a reducer, just compare the current order to the previous, only if it is incomplete.

const data = [
  { order: 3 , isComplete: false , name: 'A' },
  { order: 2 , isComplete: false , name: 'B' },
  { order: 1 , isComplete: true  , name: 'C' },
  { order: 4 , isComplete: false , name: 'D' },
  { order: 5 , isComplete: false , name: 'E' },
];

const firstIncomplete = data.reduce((prev, curr) =>
  prev ? !curr.isComplete && curr.order < prev.order ? curr : prev : curr);

console.log(firstIncomplete);

const compareBoolean = (a, b) => a === b ? 0 : a ? -1 : 1;

const sorted = data.sort((
  { order: orderA, isComplete: isCompleteA },
  { order: orderB, isComplete: isCompleteB }
) =>
  (compareBoolean(isCompleteA, isCompleteB) * -1) || (orderA - orderB));

console.log(sorted);
.as-console-wrapper { top: 0; max-height: 100% !important; }

The output should read:

[
  { order: 2 , isComplete: false , name: "B" },
  { order: 3 , isComplete: false , name: "A" },
  { order: 4 , isComplete: false , name: "D" },
  { order: 5 , isComplete: false , name: "E" },
  { order: 1 , isComplete: true  , name: "C" },
]

Without the boolean comparator function

Although you can subtract boolean values, doesn't mean you should. I would advise against this and evaluate the boolean values as numerical values instead.

See: "Sort array of objects by a boolean property"

const data = [
  { order: 3 , isComplete: false , name: 'A' },
  { order: 2 , isComplete: false , name: 'B' },
  { order: 1 , isComplete: true  , name: 'C' },
  { order: 4 , isComplete: false , name: 'D' },
  { order: 5 , isComplete: false , name: 'E' },
];

const sorted = data.sort((
  { order: orderA, isComplete: isCompleteA },
  { order: orderB, isComplete: isCompleteB }
) =>
  (isCompleteA - isCompleteB) || (orderA - orderB));

console.log(sorted);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
0

I do not see a need to do double sort here. You can just sort array by 1 property and find first element with property b that match your requirement.

Code:

const a = [{order:3,isComplete:!1,name:"A"},{order:2,isComplete:!1,name:"B"},{order:1,isComplete:!0,name:"C"},{order:4,isComplete:!1,name:"D"},{order:5,isComplete:!1,name:"E"}];

const sortedByOrder = a.sort((a, b) => a.order - b.order)
const res = sortedByOrder.find(e => !e.isComplete)

console.log(res)
ulou
  • 5,542
  • 5
  • 37
  • 47