0

This is the array what I am getting

[
  { id: 1, name: 'hello world', reference_id: null },
  { id: 2, name: 'hello world', reference_id: null },
  { id: 3, name: 'hello world', reference_id: 1 },
  { id: 4, name: 'hello world', reference_id: null },
  { id: 5, name: 'hello world', reference_id: 1 },
  { id: 6, name: 'hello world', reference_id: 2 },
]

I need to reorder this array into something similar to this.

[
  { id: 1, name: 'hello world', reference_id: null },
  { id: 3, name: 'hello world', reference_id: 1 },
  { id: 5, name: 'hello world', reference_id: 1 },
  { id: 2, name: 'hello world', reference_id: null },
  { id: 6, name: 'hello world', reference_id: 2 },
  { id: 4, name: 'hello world', reference_id: null },
]

This is the code which I tried

const parentIndex = skus?.findIndex(item => item.id === item.reference_id);
    console.log(parentIndex)

    if (parentIndex !== -1) {
      const parentId = skus[parentIndex].id;

      const parentItem = skus.splice(parentIndex, 1)[0];
      const referencedItem = skus.find(item => item.id === parentId);

      if (referencedItem) {
        const referencedIndex = skus.indexOf(referencedItem);
        skus.splice(referencedIndex + 1, 0, parentItem);
      } else {
        skus.push(parentItem);
      }
    }

When I run this code I am getting some weird unexpected results and most of the time it's not running after the first line.

Can someone help me to resolve this issue I am struggling to find a solution.

rasif sahl
  • 172
  • 1
  • 8
  • 2
    You'll need to explain your sorting logic. Otherwise just [How to sort an array of objects by multiple fields?](https://stackoverflow.com/questions/6913512/how-to-sort-an-array-of-objects-by-multiple-fields) – pilchard May 19 '23 at 08:52

3 Answers3

0

A .map() can help to define a sorting criterion that can be applied and then lateron removed again (by a second .map() call):

const arr=[
  { id: 1, name: 'hello world', reference_id: null },
  { id: 2, name: 'hello world', reference_id: null },
  { id: 3, name: 'hello world', reference_id: 1 },
  { id: 4, name: 'hello world', reference_id: null },
  { id: 5, name: 'hello world', reference_id: 1 },
  { id: 6, name: 'hello world', reference_id: 2 },
];

const res=arr.map(c=>({ref:(c.reference_id??"")+`${c.id}`,el:c}))
             .sort((a,b)=>a.ref.localeCompare(b.ref))
             .map(c=>c.el);

console.log(res)

OK, as requested in the comments. Here a solution in one go (just one .sort() call):

const arr=[
  { id: 1, name: 'hello world', reference_id: null },
  { id: 2, name: 'hello world', reference_id: null },
  { id: 3, name: 'hello world', reference_id: 1 },
  { id: 4, name: 'hello world', reference_id: null },
  { id: 5, name: 'hello world', reference_id: 1 },
  { id: 6, name: 'hello world', reference_id: 2 },
];
const crit=o=>(""+(o.reference_id??"")).padStart(4,"0")+"|"+(""+o.id).padStart(4,"0"),
      res=arr.sort((a,b)=>crit(a).localeCompare(crit(b)));

console.log(res);

console.log("these are the computed sort criteria:")
console.log(res.map(crit))

The second version can deal with any kid of numeric id (up to "9999").

Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43
  • The downvote is not mine, but why map then sort and map again, when you can just sort? You should add the *just sort* approach to your answer. – anotherOne May 19 '23 at 09:51
  • 1
    The downvote was mine. This creates multiple ambiguous `ref` cases and also doesn't respect numeric sorting. (ie. `{ id: 3, reference_id: 1 }` -> `ref: '13'`, `{id: 13, reference: null}` -> `ref: 13`) – pilchard May 19 '23 at 09:59
  • @pilchard You are right. Just sorting is the answer. – anotherOne May 19 '23 at 10:05
0

It looks like what you are after is that elements with a reference_id to immediately follow the element with that id.

If that is the case then you are actually looking for a preorder traversal of the data as a tree (see: tree traversal). The following works for any level of nesting.

const input = [
  { id: 4, reference_id: null },
  { id: 7, reference_id: 6 },
  { id: 5, reference_id: 1 },
  { id: 2, reference_id: null },
  { id: 1, reference_id: null },
  { id: 3, reference_id: 1 },
  { id: 6, reference_id: 2 },
];

// build the tree
const tree = input.reduce((a, node) => {
  (a[node.reference_id] ??= []).push({
    node,
    children: (a[node.id] ??= []),
  });

  return a;
}, {})['null'];

// recursive depth-first traversal
function flatten(arr) {
  let result = [];
  for (const { node, children } of arr.sort((a, b) => a.node.id - b.node.id)) {
    result.push(node, ...flatten(children));
  }

  return result;
}

const sorted = flatten(tree);

console.log(sorted.map((o) => JSON.stringify(o)).join(',\n'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

If however you are just looking for a primary sorting by id with elements that share a reference_id grouped together then you can first group-by reference_id then sort by the id of the first element of each group before flattening the grouped array. Here performing an initial sort by id on a copy of the input array.

const input = [
  { id: 4, reference_id: null },
  { id: 13, reference_id: null },
  { id: 5, reference_id: 1 },
  { id: 2, reference_id: null },
  { id: 1, reference_id: null },
  { id: 3, reference_id: 1 },
  { id: 6, reference_id: 2 },
];

const sorted = Object
  .values(
    [...input]
      .sort((a, b) => a.id - b.id)
      .reduce((a, c, i) => ((a[c.reference_id ?? `null_${i}`] ??= []).push(c), a), {})
  )
  .sort(([a], [b]) => a.id - b.id)
  .flat();

// logging
console.log(sorted.map((o) => JSON.stringify(o)).join(',\n'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
pilchard
  • 12,414
  • 5
  • 11
  • 23
0

You can do so by a custom sort() function:

const list = [
  { id: 1, name: "hello world", reference_id: null },
  { id: 2, name: "hello world", reference_id: null },
  { id: 3, name: "hello world", reference_id: 1 },
  { id: 4, name: "hello world", reference_id: null },
  { id: 5, name: "hello world", reference_id: 1 },
  { id: 6, name: "hello world", reference_id: 2 }
];

list.sort((a, b) => {

  // obj.reference_id = 1 goes before obj.reference_id = 2
  if (a.reference_id && b.reference_id) {
    return a.reference_id - b.reference_id;
  }

  // obj.reference_id = 1 goes before obj.id = 2
  if (a.reference_id && !b.reference_id) {
    return a.reference_id - b.id;
  }

  // again, obj.reference_id = 1 goes before obj.id = 2
  if (!a.reference_id && b.reference_id) {
    return a.id - b.reference_id;
  }

  // if reference_id is null then obj.id = 1 goes before obj.id = 2
  return a.id - b.id;
});

console.log(list);

Update

If

1- given array could be unordered by id or

2- there could be nested references as said in the comments, this is my suggested solution:

const list = [
  { id: 7, name: "hello world", reference_id: 3 },
  { id: 3, name: "hello world", reference_id: 1 },
  { id: 4, name: "hello world", reference_id: null },
  { id: 1, name: "hello world", reference_id: null },
  { id: 5, name: "hello world", reference_id: 1 },
  { id: 2, name: "hello world", reference_id: null },
  { id: 6, name: "hello world", reference_id: 2 }
];


list
  .sort((a, b) => a.id - b.id) // first sort by id
  .sort((a, b) => a.reference_id - b.reference_id) // then by referenceId
  .forEach((dep, index) => { // then move dependant entries next to their parents
    if (dep.reference_id !== null) {
    
      // find index of parent
      const parentIndex = list.findIndex((p) => p.id === dep.reference_id);
      
      // find a sibling that is already moved next to the common parent
      const siblingIndex = list.findLastIndex(
        (s, i) => s.reference_id === dep.reference_id && i < index
      );

      // remove entry from its original slot
      list.splice(index, 1);

      // calc its new slot
      const depNewIndex = (parentIndex + 1 > siblingIndex) ? parentIndex + 1 : siblingIndex + 1;

      // move
      list.splice(depNewIndex, 0, dep);
    }
  });

console.log(list);
mohsen dorparasti
  • 8,107
  • 7
  • 41
  • 61