janlindso
janlindso

Reputation: 1253

Sort an array of object by the value of a child object

I'm getting some json in the following structure:

[
    {
        "name": "First object", 
        "info": [
            "2 people",
            "this is a description"
        ]
    },
    {
        "name": "Second object", 
        "info": [
            "this is a description",
            "1 furniture"
        ]
    },
    {
        "name": "Third object", 
        "info": [
            "3 animals",
            "this is a description"
        ]
    },
]

I know how to sort arrays by values (like if I was supposed to sort only by name), but how can I sort it based on the number in the array? They will not always be at the same index, so I guess I will need a sorting routine to put them in the right place or something. The array value with a number will be the only value containing a digit. And that will be used for sorting. So the output should be:

1 furniture - Second object
2 people - First object
3 animals - Third object

Will I have to traverse the whole array, or is there a simpler way to do it? Any help would be appreciated so I'm able to wrap my head around it.

Upvotes: -1

Views: 73

Answers (5)

Nina Scholz
Nina Scholz

Reputation: 386756

You could parse the values and take the truty ones for getting the delta.

const
    array = [{ name: "First object",  info: ["2 people", "this is a description"] }, { name: "Second object", info: ["this is a description", "1 furniture"] }, { name: "Third object", info: ["3 animals", "this is a description"] }];

array.sort(({ info: [a0, a1] }, { info: [b0, b1] }) =>
    (parseInt(a0, 10) || parseInt(a1, 10)) - 
    (parseInt(b0, 10) || parseInt(b1, 10))
);

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

Upvotes: 0

Alexander Nenashev
Alexander Nenashev

Reputation: 23597

If you are interested in performance you can sort without intermediate arrays with caching found numbers in a map:

const arr = [{"name":"First object","info":["2 people","this is a description"]},{"name":"Second object","info":["this is a description","1 furniture"]},{"name":"Third object","info":["3 animals","this is a description"]}]

const map = new Map;
const getNumber = item => {
  let num = map.get(item);
  if(num) return num;
  num = Infinity;
  for(const line of item.info){
    num = parseInt(line);
    if(num === num){
      break;
    }
  }
  map.set(item, num);
  return num;
}

arr.sort((a, b) => {
  a = getNumber(a), b = getNumber(b);
  return a > b ? 1 : a < b ? -1 : 0;
});

console.log(arr);

` Chrome/133
---------------------------------------------------------------------------------------------------
>                              n=3       |       n=30        |       n=300       |      n=3000     
Alexander                  1.25x x1m 222 | ■ 1.00x   x1m 683 | ■ 1.00x x100k 461 | ■ 1.00x x10k 445
Alexander (no caching)   ■ 1.00x x1m 178 |   2.53x x100k 173 |   3.71x  x10k 171 |   3.80x  x1k 169
Ori                        1.46x x1m 260 |   3.60x x100k 246 |   6.40x  x10k 295 |   7.03x  x1k 313
Carsten                    1.89x x1m 336 |   4.61x x100k 315 |   7.42x  x10k 342 |   8.40x  x1k 374
--------------------------------------------------------------------------------------------------- `

Open in the playground

const $chunk = [{"name":"First object","info":["2 people","this is a description"]},{"name":"Second object","info":["this is a description","1 furniture"]},{"name":"Third object","info":["3 animals","this is a description"]}]
const $input = [];
const arr = $input;

// @benchmark Alexander
{
const map = new Map;
const getNumber = item => {
  let num = map.get(item);
  if(num) return num;
  num = Infinity;
  for(const line of item.info){
    num = parseInt(line);
    if(num === num){
      break;
    }
  }
  map.set(item, num);
  return num;
}

arr.sort((a, b) => {
  a = getNumber(a), b = getNumber(b);
  return a > b ? 1 : a < b ? -1 : 0;
});
}

// @benchmark Alexander (no caching)
{
const getNumber = item => {
  let num = Infinity;
  for(const line of item.info){
    num = parseInt(line);
    if(num === num){
      return num;
    }
  }
  return num;
}

arr.sort((a, b) => {
  a = getNumber(a), b = getNumber(b);
  return a > b ? 1 : a < b ? -1 : 0;
});
}

// @benchmark Ori
{
const getNumber = arr => 
  parseInt(arr.find(s => !Number.isNaN(parseInt(s, 10))) ?? 0, 10)

arr
  .map(o => [getNumber(o.info), o]) // decorate
  .sort(([a], [b]) => a - b) // sort
  .map(([, o]) => o) // undecorate
}

// @benchmark Carsten
const perm=arr.map((e,i)=>[parseInt(e.info.find(f=>f.match(/^\d/)),10),i]).sort(([a],[b])=>a-b);
perm.map(([_,i])=>arr[i])

/*@skip*/ fetch('https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js').then(r => r.text().then(eval));

Upvotes: 0

Carsten Massmann
Carsten Massmann

Reputation: 28226

Another way could be to create a permutation array perm and use that to list the elements of arr in the desired order:

const arr = [{"name":"First object","info":["2 people","this is a description"]},{"name":"Second object","info":["this is a description","1 furniture"]},{"name":"Third object","info":["3 animals","this is a description"]}]

const perm=arr.map((e,i)=>[parseInt(e.info.find(f=>f.match(/^\d/)),10),i]).sort(([a],[b])=>a-b);
console.log("permutation array:",perm);

console.log("result:",perm.map(([_,i])=>arr[i]))

Upvotes: 0

Ori Drori
Ori Drori

Reputation: 192607

Use a Schwartzian transform also known as DSU (decorate, sort, undecorate):

  1. Decorate - map the array, extract the number (or fallback 0), and store the number and original object in a tuple.
  2. Sort by the number.
  3. Undecorate - map again to get an array of the original object.

const arr = [{"name":"First object","info":["2 people","this is a description"]},{"name":"Second object","info":["this is a description","1 furniture"]},{"name":"Third object","info":["3 animals","this is a description"]}]

// get the number from the array of strings, assuming that the number is always 1st in a string
const getNumber = arr => 
  parseInt(arr.find(s => !Number.isNaN(parseInt(s, 10))) ?? 0, 10)

const result = arr
  .map(o => [getNumber(o.info), o]) // decorate
  .sort(([a], [b]) => a - b) // sort
  .map(([, o]) => o) // undecorate

console.log(result)

Upvotes: 1

user29644178
user29644178

Reputation: 48

As @Barmar said it would probably be best to store that data in an object, not an array. However, if you get the data in such structure from an API you can go about it in a few ways.

Parse the data:

array = JSON.parse(`[
    {
        "name": "First object", 
        "info": [
            {"amount": "2 people"},
            {"description": "this is a description"}
        ]
    },
    {
        "name": "Second object", 
        "info": [
            {"description": "this is a description"},
            {"amount": "1 furniture"}
        ]
    },
    {
        "name": "Third object", 
        "info": [
            {"amount": "3 animals"},
            {"description": "this is a description"}
        ]
    }
]`)

You're probably gonna use it later so might as well map it to flat objects:

flat_array = array.map(x => Object.assign({ name: x.name }, ...x.info)

Then you can sort it by simply doing:

flat_array.sort((a, b) => a.amount.localeCompare(b.amount))

Otherwise, if you just want to sort the data, without modifying it, you can do:

array.sort((a, b) => Object.assign({}, ...a.info).amount.localeCompare(Object.assign({}, ...b.info).amount))

following the Object.assign example. Or as other people suggested in the comments:

array.sort((a, b) => a.info.find(x => "amount" in x).amount.localeCompare(b.info.find(x => "amount" in x).amount))

Upvotes: 0

Related Questions