【问题标题】:Sort object elements by string, where each string is an array按字符串对对象元素进行排序,其中每个字符串都是一个数组
【发布时间】:2021-07-12 08:53:14
【问题描述】:

我有一个对象,其中每个字符串都包含一个数组:

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

如何对这些元素进行排序,例如按年龄排序,以便得到这个结果?

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

我故意插入了一个重复的名字,因为它应该在这样的情况下工作(应该保持关系位置)。

注意:如果其他人有同样的问题,在阅读完答案后,我现在建议将数据结构更改为:

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

这样,可以使用 .sort 等更简单的方法。

【问题讨论】:

  • 没有引号的不是字符串。它们将被评估为未定义的变量。因此,该示例不符合minimal reproducible example 的资格,并且需要任何想要帮助自己添加引号的人。除此之外 - 向我们展示您的尝试。 SO 不是免费的代码编写服务
  • 是的,对不起,这些是我在这里写错的字符串。将纠正这一点。
  • @user1582208 在您的原始问题中,名称保持不变,而年龄改变顺序。那是故意的吗?在数组中,tim22,而在第二个数组中,tim21。对吗?
  • @user1582208 我在我的解决方案中提供了几个不同的示例。请花时间检查每一个。第一个示例只是使用示例数据完成您正在寻找的事情,而我的最终解决方案sortBy('age') 动态工作以考虑所有属性并按您想要的任何一个进行排序,按升序(默认)或降序(设置第二参数到true)

标签: javascript arrays sorting object


【解决方案1】:

首先,我们可以获取您开始使用的数据并创建一个新的对象数组,在其中我们将每个人映射到他们指定的年龄,然后我们按其age 属性值对该对象数组进行排序:

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 }
      ]
*/

一旦我们按年龄对这个用户数组进行排序,剩下要做的就是将对象数组拆分为 nameage 的两个嵌套数组,如下所示:

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]
      }
*/

现在让我们把这一切结合起来:

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]
      }
*/

奖金

现在有一个奖励 - 假设您有一个像您提供的对象一样的对象......

  • 正在处理更多用户详细信息,甚至可能是可变数量的详细信息,您宁愿明确列出它们
  • 希望能够按列出的任何用户详细信息、姓名(字母排序)、年龄(数字排序)、ID 号(数字排序)或其他一些属性(默认为字母排序,除非所有值都是数字)
  • 希望添加一个布尔值 true/false 开关,以快速反转任何排序函数的顺序,例如 sortUsers(users, true),其中第二个参数将在排序后反转每个最终数组的状态
  • 您想将此函数用作Object.prototype 方法,例如users.sortBy('age') 而不是sortObject(users, 'age')

让我们进行所有这些改进,并将它们整合到一个原型函数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"]
      }
*/

【讨论】:

  • 非常感谢!这真是太好了!
  • @user1582208 谢谢,很高兴听到!如果这对您有用,您会考虑选择我的答案作为正确的解决方案吗?
  • 当然!你的回答不仅解决了我的问题,而且让我意识到我可以从一开始就设置这样的对象(一个数组有很多对象,而不是一个对象有很多数组)。我现在看到的我的部分非常菜鸟,但我收到了它并最终接受了它。我现在正在重写很多代码,但它会好得多。感谢您帮助新手
  • @user1582208 我真的很高兴它有帮助!我非常喜欢编写它并探索您的对象设置:)
【解决方案2】:

说明前提是对象中的所有值都是数组。

  1. 首先,复制原始数组以防止更改原始对象。

  2. 使用while 循环使用Math.min 函数查找传递的最小属性的索引。

  3. 从传递的对象的所有键中拼接index并将其推送到新对象中。

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'));

【讨论】:

    【解决方案3】:

    一种方法是在 Map 中按年龄对用户名进行分组,或者按年龄对对象进行分组。

    然后在对年龄进行排序后,将年龄数组映射到名称数组,从 groupBy 对象/映射中提取名称值

    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]))
    }

    【讨论】:

      【解决方案4】:

      现在找到了:

      /**
       *  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;
        }
      }
      

      例子:

      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
      

      来源:https://gist.github.com/boukeversteegh/3219ffb912ac6ef7282b1f5ce7a379ad

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-25
        • 2015-09-17
        • 2017-05-25
        • 1970-01-01
        • 2018-08-29
        • 2013-10-04
        • 1970-01-01
        相关资源
        最近更新 更多