2

I have an object where each string contains an array:

var users = {
    name   : ["joe" , "tim" , "bob" , "joe"],
    age    : [23    ,  22   ,  21   ,  20  ],
    other  : ["AA"  , "BB"  , "CC"  , "DD"]
}

How can I sort these elements, for example by age, so I get this as a result?

var users = {
    name   : ["joe" , "tim" , "bob" , "joe"],
    age    : [ 20   ,  21   ,  22   ,  23  ],
    other  : ["DD"  , "CC"  , "BB"  , "AA" ]
}

I nserted a duplicate name on purpose, since it should work in cases like these (should keep the relationship positions).

Note: In case someone else has this same issue, after reading the answers I now recommend changing the data structure to:

var users = [ // note this is now an array
    name   : {"joe" , "tim" , "bob" , "joe"},
    age    : {23    ,  22   ,  21   ,  20  },
    other  : {"AA"  , "BB"  , "CC"  , "DD"}
]

This way, more simple approaches like .sort can be used.

4
  • Without quotes those are not strings. They will be evaluated as undefined variables. Therefore the sample does not qualify as a minimal reproducible example and would require anyone who wanted to help to add quotes themselves. Beyond that - show us what you have tried. SO isn't a free code writing service
    – charlietfl
    Commented Apr 17, 2021 at 16:23
  • yes, sorry, those are strings I wrote it wrong in here. going to correct this. Commented Apr 17, 2021 at 16:28
  • @user1582208 in your original question, the names stay the same order while the ages change order. Was that intentional? In the array, it appears tim is 22 and in the second, tim is 21. Is that correct? Commented Apr 17, 2021 at 17:05
  • @user1582208 I provided several different examples in my solution. Please take time to check each. The first example simply does what you're looking for using your sample data, and my final solution sortBy('age') works dynamically to take all properties into account and sort by whichever you want, in ascending order (default) or descending order (set 2nd param to true) Commented Apr 17, 2021 at 18:52

4 Answers 4

1

First, we can take the data you are starting with and create a new array of objects where we map each individual to their assigned age, and then we sort that array of objects by its age property value:

let users = {
  name: ["joe", "tim", "bob", "joe"],
  age: [23, 22, 21, 20]
};

const associateUserDetails = users => Array(users.name.length).fill().map((e,i) => ({name: users.name[i], age: users.age[i]})).sort((a,b) => a.age - b.age);

console.log(associateUserDetails(users));
/* -> [
        { name: "joe", age: 20 },
        { name: "bob", age: 21 },
        { name: "tim", age: 22 },
        { name: "joe", age: 23 }
      ]
*/

Once we this array of users sorted by their age, all that's left to do is split the array of objects back up into two nested arrays for name and age, like this:

let users = {
  name: ["joe", "tim", "bob", "joe"],
  age: [23, 22, 21, 20]
};

const associateUserDetails = users => Array(users.name.length).fill().map((e,i) => ({name: users.name[i], age: users.age[i]})).sort((a,b) => a.age - b.age);

const splitByDetail = users => users.flatMap(user => Object.entries(user)).reduce((a,c) => (a[c[0]] ? a[c[0]].push(c[1]) : a[c[0]] = [c[1]], a), {});

console.log(splitByDetail(associateUserDetails(users)));
/* -> {
        name: ["joe", "bob", "tim", "joe"],
        age: [20, 21, 22, 23]
      }
*/

Now let's combine this all together:

let users = {
  name: ["joe", "tim", "bob", "joe"],
  age: [23, 22, 21, 20]
};

const sortUsersByAge = users => Array(users.name.length).fill().map((e,i) => ({name: users.name[i], age: users.age[i]})).sort((a,b) => a.age - b.age).flatMap(user => Object.entries(user)).reduce((a,c) => (a[c[0]] ? a[c[0]].push(c[1]) : a[c[0]] = [c[1]], a), {});

console.log(sortUsersByAge(users));
/* -> {
        name: ["joe", "bob", "tim", "joe"],
        age: [20, 21, 22, 23]
      }
*/

BONUS

Now for a bonus— let's say you have an object like the one you provided and…

  • are working with many more user details, maybe even a variable number of details, and you would rather list them all explicitly
  • want the ability to sort by any of the user details listed, name (alphabetical sorting), age (numerical sorting), ID number (numerical sorting), or some other property (default to alphabetical sorting unless all values are numbers)
  • would like to add a boolean true/false switch to quickly reverse the order of any sorting function like sortUsers(users, true) where the second parameter would reverse each final array's state after sorting
  • you want to use this function as an Object.prototype method, like users.sortBy('age') instead of sortObject(users, 'age')

Let's make all of those improvements and put it all together into one prototype function Object.sortBy():

let users = {
  name: ["joe", "tim", "bob", "joe"],
  age: [23, 22, 21, 20],
  color: ["blue", "green", "red", "yellow"]
};

Object.prototype.sortBy = function(sortBy, descending = false) { return sortBy && Object.keys(this).includes(sortBy) ? [Array(this[Object.keys(this)[0]].length).fill().map((e,i) => Object.fromEntries(Object.keys(this).map(key => [key, this[key][i]])))].map(obj => obj.sort((a,b) => obj.every(user => typeof user[sortBy] === "number") ? a[sortBy] - b[sortBy] : (a[sortBy] > b[sortBy] ? 1 : -1)))[0].flatMap(user => Object.entries(user)).reduce((a,c) => (a[c[0]] ? a[c[0]].push(c[1]) : a[c[0]] = [c[1]], a), {}) : this };

users.sortBy('age');
/* -> {
        name: ["joe", "bob", "tim", "joe"],
        age: [20, 21, 22, 23],
        color: ["yellow", "red", "green", "blue"]
      }
*/

users.sortBy('name');
/* -> {
        name: ["bob", "joe", "joe", "tim"],
        age: [21, 20, 23, 22],
        color: ["red", "yellow", "blue", "green"]
      }
*/

users.sortBy('color', true); // true here will sort colors by name in descending order
/* -> {
        name: ["joe", "bob", "tim", "joe"],
        age: [20, 21, 22, 23],
        color: ["yellow", "red", "green", "blue"]
      }
*/
4
  • Thank you so much! This is really really good! Commented Apr 17, 2021 at 21:29
  • @user1582208 Thanks, glad to hear it! If this worked for you, would you consider selecting my answer as the correct solution? Commented Apr 18, 2021 at 1:30
  • 1
    Sure! your answer didn't only solve my problem, but made me realize I could have set up the objects like that from the start (one array with many objects, instead of one object with many arrays). Very noob of my part I see now, but Ireceived it like that and ended up going with it. I am now rewriting a lot of code but it will be MUCH better. Thanks for helping out a newbie Commented Apr 18, 2021 at 16:45
  • @user1582208 I'm really glad it helped! I thoroughly enjoyed writing it and exploring your object setup as well :) Commented Apr 18, 2021 at 19:46
1

One way is group the user names by age in a Map or object keyed by ages.

Then after sorting the ages, map the ages array to names array plucking the name values from the groupBy object/Map

var users = {
  name: ["bob", "joe", "tim", "bob", "joe"],
  age: [20, 23, 22, 21, 20]// added duplicate age also
}
// Map using ages as keys , empty array for values
const grouped = new Map(users.age.map((age, i) => [age, []]))
// push each name to appropriate age array in map
users.name.forEach((name, i) => grouped.get(users.age[i]).push(name));

const sortedAges = users.age.slice().sort((a, b) => a - b),
      // rebuild names array from items in Map
      sortedNames = sortedAges.map(age => grouped.get(age).shift());

const sortedUsers = {
  name:sortedNames, 
  age: sortedAges
}


for(prop in sortedUsers){
console.log(JSON.stringify(sortedUsers[prop]))
}

1

Explanation Provided all the values in the object are arrays.

  1. Firstly, duplicate the original array to prevent changing the original object.

  2. Using a while loop to find the index of the smallest passed property using the Math.min function.

  3. Splicing the index from all the keys of the passed object and pushing it into the new object.

let users = {name:["joe", "tim", "bob", "joe"], age:[23, 22, 21, 20], other  : ["AA", "BB", "CC", "DD"]};

function sortObject(obj, prop) {
  obj = {...obj}; // To avoid changing the initial object
  const keys = Object.keys(obj);
  const newObj = keys.reduce((a, b) => {
    a[b] = [];
    return a;
  }, {});

  while (obj[prop].length) {
    const index = obj[prop].findIndex(age => age == Math.min(...obj[prop]));

    keys.forEach(key => newObj[key].push(obj[key].splice(index, 1)[0]));
  }

  return newObj;
}

console.log(sortObject(users, 'age'));

0
0

Now found this:

/**
 *  Sorts all arrays together with the first. Pass either a list of arrays, or a map. Any key is accepted.
 *     Array|Object arrays               [sortableArray, ...otherArrays]; {sortableArray: [], secondaryArray: [], ...}
 *     Function comparator(?,?) -> int   optional compareFunction, compatible with Array.sort(compareFunction) 
 */
function sortArrays(arrays, comparator = (a, b) => (a < b) ? -1 : (a > b) ? 1 : 0) {
  let arrayKeys = Object.keys(arrays);
  let sortableArray = Object.values(arrays)[0];
  let indexes = Object.keys(sortableArray);
  let sortedIndexes = indexes.sort((a, b) => comparator(sortableArray[a], sortableArray[b]));

  let sortByIndexes = (array, sortedIndexes) => sortedIndexes.map(sortedIndex => array[sortedIndex]);

  if (Array.isArray(arrays)) {
    return arrayKeys.map(arrayIndex => sortByIndexes(arrays[arrayIndex], sortedIndexes));
  } else {
    let sortedArrays = {};
    arrayKeys.forEach((arrayKey) => {
      sortedArrays[arrayKey] = sortByIndexes(arrays[arrayKey], sortedIndexes);
    });
    return sortedArrays;
  }
}

Example:

let people = ["john", "benny", "sally", "george"];
let peopleIds = [10, 20, 30, 40];

sortArrays([people, peopleIds]);
[["benny", "george", "john", "sally"], [20, 40, 10, 30]] // output

sortArrays({people, peopleIds});
{"people": ["benny", "george", "john", "sally"], "peopleIds": [20, 40, 10, 30]} // output

Source: https://gist.github.com/boukeversteegh/3219ffb912ac6ef7282b1f5ce7a379ad

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.