Home > database >  How to sort an array of objects by custom order given as a string or array?
How to sort an array of objects by custom order given as a string or array?

Time:10-13

Given array:

var someAnswers = [
  {
    answer:  'Lyndon Johnson', // answer A
    comment: '...'
  },
  {
    answer:  'Richard Nixon', // answer B
    comment: '...'
  },
  {
    answer:  'Jimmy Carter', // answer C
    comment: '...'
  },
  {
    answer:  'Gerald Ford', // answer D
    comment: '...'
  }
];

Some custom order:

customOrder = 'A, C, B, D';

or

customOrder = ['A', 'C', 'B', 'D'];

Do something like this:

someAnswers.sort(customOrder);

Desired result:

[
  {
    "answer": "Lyndon Johnson",
    "comment": "..."
  },
  {
    "answer": "Jimmy Carter",
    "comment": "..."
  },
  {
    "answer": "Richard Nixon",
    "comment": "..."
  },
  {
    "answer": "Gerald Ford",
    "comment": "..."
  }
]

Another custom order:

anotherCustomOrder = 'D, B, A, C';

or

anotherCustomOrder = ['D', 'B', 'A', 'C'];

Do something like this:

someAnswers.sort(anotherCustomOrder);

Desired result:

[
  {
    "answer": "Gerald Ford",
    "comment": "..."
  },
  {
    "answer": "Richard Nixon",
    "comment": "..."
  },
  {
    "answer": "Lyndon Johnson",
    "comment": "..."
  },
  {
    "answer": "Jimmy Carter",
    "comment": "..."
  }
]

CodePudding user response:

let sorting = (currentArray, indexArr) => {
    const reArrangedArr = [];
    const deepCopied = JSON.parse(JSON.stringify(currentArray));
    indexArr.forEach(index => reArrangedArr.push(deepCopied[index]));
    return reArrangedArr;
}
var someAnswers = [
  {
    answer:  'Lyndon Johnson', // answer A
    comment: '...'
  },
  {
    answer:  'Richard Nixon', // answer B
    comment: '...'
  },
  {
    answer:  'Jimmy Carter', // answer C
    comment: '...'
  },
  {
    answer:  'Gerald Ford', // answer D
    comment: '...'
  }
];
sorting(someAnswers, [3,0,1,2])

    let sorting = (currentArray, indexArr) => {
        const reArrangedArr = [];
        const deepCopied = JSON.parse(JSON.stringify(currentArray));
        indexArr.forEach(index => reArrangedArr.push(deepCopied[index]));
        return reArrangedArr;
    }
    var someAnswers = [
      {
        answer:  'Lyndon Johnson', // answer A
        comment: '...'
      },
      {
        answer:  'Richard Nixon', // answer B
        comment: '...'
      },
      {
        answer:  'Jimmy Carter', // answer C
        comment: '...'
      },
      {
        answer:  'Gerald Ford', // answer D
        comment: '...'
      }
    ];
    console.log(sorting(someAnswers, [3,0,1,2]))
    

CodePudding user response:

If you were willing to replace the letters with numbers in customOrder, you could do something like this:

customOrder = [0, 2, 1, 3];

sort(someAnswers, customOrder) {
    res = [];
    customOrder.forEach((n) => {
        res.push(someAnswers[n]);
    }
    return res;
}

Alternatively, if you really want to use letters:

customOrder = ["A", "C", "B", "D"];

sort(someAnswers, customOrder) {
    res = [];
    customOrder.forEach((n) => {
        res.push(someAnswers[n.charCodeAt(0) - 65]);
    }
    return res;
}

CodePudding user response:

You can create an object with the indexes according to the desired order, and then use the function Array.prototype.map and extract the values by using the previously created indexes array.

const someAnswers = [  {    answer:  'Lyndon Johnson', comment: '...'  },  {    answer:  'Richard Nixon',    comment: '...'  },  {    answer:  'Jimmy Carter',     comment: '...'  },  {    answer:  'Gerald Ford',    comment: '...'  }],
      answerIndexes = ['A', 'B', 'C', 'D'].reduce((a, c, i) => ({...a, [c]: i}), {}),
      customOrder = ['A', 'C', 'B', 'D'],
      sorted = customOrder.map(L => someAnswers[answerIndexes[L]]);

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

CodePudding user response:

Wrap → sort → unwrap

const toLetter = num => 
  String.fromCharCode(num   65);
  
const wrap = (item, index) =>
  ({ letter: toLetter(index), item });

const unwrap = ({item}) => item;
  
const sorter = lookup => ({letter: a}, {letter: b}) =>
  lookup.indexOf(a) - lookup.indexOf(b);

function sortCustom(arr, order) {
  return arr
    .map(wrap)
    .sort(sorter(order))
    .map(unwrap);
}


var someAnswers = [ { answer: 'Lyndon Johnson', comment: '...' }, { answer: 'Richard Nixon', comment: '...' }, { answer: 'Jimmy Carter', comment: '...' }, { answer: 'Gerald Ford', comment: '...' } ];

var customOrder = ['A', 'C', 'B', 'D'];

console.log(sortCustom(someAnswers, customOrder));

This will process the data in three steps:

  1. It wraps every item in a sortable container. For example, the first item:

    {
        answer:  'Lyndon Johnson', // answer A
        comment: '...'
    }
    

    becomes:

    {
        letter: "A",
        item: {
            answer:  'Lyndon Johnson',
            comment: '...'
          }
    }
    
    • The transformation to a letter uses the character codes. A capital "A" is 65 and each following capital letter is one more - "B" is 66 etc.
    • The wrapping here is done to avoid having to lookup in which position each item was originally in order to find out which letter it corresponds to. The data is computed once and placed on the sortable container and the item is also kept there for easy access.
  2. Sorts based on another array according to letter.

  3. Unwraps the sortable container back to the base item again once the sorting information is no longer needed.

This is a straightforward implementation of the idea. The sorter function can be inefficient for large arrays as it will have to do a lot of lookup to find the order. One way to avoid it is to cache the lookup data, in order to avoid indexOf traversing the array multiple times:

const sorter = order => {
  const lookup = Object.fromEntries(order.map((x, i) => [x, i]))
  return ({letter: a}, {letter: b}) =>
    lookup[a] - lookup[b];
};

Transform order to a new array

const toIndex = letter => 
  letter.charCodeAt(0) - 65;
  
function sortCustom(arr, order) {
  return order.map(x => arr[toIndex(x)]);
}


var someAnswers = [ { answer: 'Lyndon Johnson', comment: '...' }, { answer: 'Richard Nixon', comment: '...' }, { answer: 'Jimmy Carter', comment: '...' }, { answer: 'Gerald Ford', comment: '...' } ];

var customOrder = ['A', 'C', 'B', 'D'];

console.log(sortCustom(someAnswers, customOrder));

In a way, this is the inverse of sorting. We take the already sorted order array and directly transform it into the array of items via .map(). The key thing here is using the opposite of toLetter() - toIndex() takes a letter and returns the corresponding index.

  • Related