【问题标题】:Javascript: shuffling groups of objects in an arrayJavascript:改组数组中的对象组
【发布时间】:2020-07-25 09:27:45
【问题描述】:

我有一个对象数组,我在其中按一个键(下面的group)排序,这样group 具有相同值的所有对象在data 的索引中彼此相邻。例如:

var data = [{foo: "cat", group:"house"},
            {foo: "cat", group: "house"},
            {foo: "cat", group: "tree"},
            {foo: "dog", group: "tree"},
            {foo: "dog", group: "car"}];

我正在尝试打乱data 数组中对象的顺序,同时保留键group 的值内的顺序。换句话说,我试图洗牌data 中的对象组,而不是单个对象。虽然我知道如何打乱数组中的对象,但我不知道如何打乱数组中的对象组。

我的想法是,可能有一种方法可以利用这个事实,即只有当组发生变化时,组的值才会发生变化。

【问题讨论】:

    标签: javascript arrays shuffle


    【解决方案1】:

    只需创建一个随机属性以在组级别进行排序,并将该属性分配给数组中的每个相应对象:

    var data = [{foo: "cat", group: "house"},
                {foo: "cat", group: "house"},
                {foo: "cat", group: "tree"},
                {foo: "dog", group: "tree"},
                {foo: "dog", group: "car"}];
    
    //get random sorting at the group level (via a hashtable)
    let randomGroupSortKey = {}
    data.forEach(d => randomGroupSortKey[d.group] = Math.random())
    console.log("Group sort keys:", randomGroupSortKey)
    
    //add the sortKey property to the individual array entries
    let dataSortable = data.map(x => {
      return {
        ...x, 
        sortKey: randomGroupSortKey[x.group]
      }
    })
    
    dataSortable.sort((a, b) => a.sortKey - b.sortKey) //sort the groups!
    
    console.log("Result:", dataSortable)
    console.log("Result without sortKey:", dataSortable.map(({ sortKey, ...x }) => x))

    【讨论】:

    • 这是最有意义的,有效的,而且做得很好。
    • 由于您使用x => ({...x, sortKey: "..."}) 添加排序键,我将以相同的方式删除它({sortKey, ...x}) => x 而不是x => ({foo: x.foo, group: x.group}),它只提取“foo”和“group”属性。跨度>
    • @3limin4t0r 谢谢!我什至不知道我们可以在这里使用 rest 语法。对于那些感兴趣的人,这里有一个很好的解释:stackoverflow.com/a/56156100/2328210
    • 由于某种原因,我收到一个错误,即 ... 是它的两个实例的无法识别的令牌。有什么建议吗?
    • 好吧,不知道为什么“...”休息语法对我不起作用。我在自己的应用程序中对其进行了编辑,以明确说明要返回的内容。
    【解决方案2】:

    您可以先按对象的group 属性对对象进行分组,然后随机分组,最后取消嵌套。

    function groupBy(iterable, keyFn = obj => obj) {
      const groups = new Map();
      for (const item of iterable) {
        const key = keyFn(item);
        if (!groups.has(key)) groups.set(key, []);
        groups.get(key).push(item);
      }
      return groups;
    }
    
    function shuffle(array) {
      array = array.slice(0);
      for (let limit = array.length; limit > 0; --limit) {
        const index = Math.floor(Math.random() * limit);
        array.push(...array.splice(index, 1));
      }
      return array;
    }
    
    var data = [{foo:"cat",group:"house"},{foo:"cat",group:"house"},{foo:"cat",group:"tree"},{foo:"dog",group:"tree"},{foo:"dog",group:"car"}];
    
    data = groupBy(data, obj => obj.group);
    data = Array.from(data.values());
    data = shuffle(data);
    data = data.flat();
    
    console.log(data);

    【讨论】:

      【解决方案3】:

      你有一个有趣的问题。我只是wrote about this recently,所以如果您对此答案中提出的想法感兴趣,请点击该链接-

      const randInt = (n = 0) =>
        Math.floor(Math.random() * n)
      
      const { empty, map, concat } =
        Comparison
      
      const sortByGroup =
        map(empty, x => x.group)
      
      const sortByRand =
        map(empty, _ => randInt(3) - 1) // -1, 0, 1
      

      直观地说,我们使用map(empty, ...) 进行新的比较(排序器)。 concat 是我们用来将一个比较与另一个比较结合起来的 -

      // sort by .group then sort by rand
      const mySorter =
        concat(sortByGroup, sortByRand) 
      

      我们的比较直接插入Array.prototype.sort -

      const data =
        [ { name: "Alice", group: "staff" }
        , { name: "Monty", group: "client" }
        , { name: "Cooper", group: "client" }
        , { name: "Jason", group: "staff" }
        , { name: "Farrah", group: "staff" }
        , { name: "Celeste", group: "guest" }
        , { name: "Briana", group: "staff" }
        ]
      
      console.log("first", data.sort(mySorter)) // shuffle once
      console.log("second", data.sort(mySorter)) // shuffle again
      

      在输出中,我们看到项目按group 分组,然后随机化 -

      // first
      [ { name: "Cooper", group: "client" }
      , { name: "Monty", group: "client" }
      , { name: "Celeste", group: "guest" }
      , { name: "Alice", group: "staff" }
      , { name: "Jason", group: "staff" }
      , { name: "Farrah", group: "staff" }
      , { name: "Briana", group: "staff" }
      ]
      
      // second
      [ { name: "Monty", group: "client" }
      , { name: "Cooper", group: "client" }
      , { name: "Celeste", group: "guest" }
      , { name: "Farrah", group: "staff" }
      , { name: "Alice", group: "staff" }
      , { name: "Jason", group: "staff" }
      , { name: "Briana", group: "staff" }
      ]
      

      最后,我们实现Comparison -

      const Comparison =
        { empty: (a, b) =>
            a < b ? -1
              : a > b ? 1
                : 0
        , map: (m, f) =>
            (a, b) => m(f(a), f(b))
        , concat: (m, n) =>
            (a, b) => Ordered.concat(m(a, b), n(a, b))
        }
      
      const Ordered =
        { empty: 0
        , concat: (a, b) =>
            a === 0 ? b : a
        }
      

      展开下面的 sn-p 以在您自己的浏览器中验证结果。运行程序多次查看结果总是按group排序,然后随机化-

      const Comparison =
        { empty: (a, b) =>
            a < b ? -1
              : a > b ? 1
                : 0
        , map: (m, f) =>
            (a, b) => m(f(a), f(b))
        , concat: (m, n) =>
            (a, b) => Ordered.concat(m(a, b), n(a, b))
        }
      
      const Ordered =
        { empty: 0
        , concat: (a, b) =>
            a === 0 ? b : a
        }
      
      const randInt = (n = 0) =>
        Math.floor(Math.random() * n)
      
      const { empty, map, concat } =
        Comparison
      
      const sortByGroup =
        map(empty, x => x.group)
      
      const sortByRand =
        map(empty, _ => randInt(3) - 1) // -1, 0, 1
      
      const mySorter =
        concat(sortByGroup, sortByRand) // sort by .group then sort by rand
      
      const data =
        [ { name: "Alice", group: "staff" }
        , { name: "Monty", group: "client" }
        , { name: "Cooper", group: "client" }
        , { name: "Jason", group: "staff" }
        , { name: "Farrah", group: "staff" }
        , { name: "Celeste", group: "guest" }
        , { name: "Briana", group: "staff" }
        ]
         
      console.log(JSON.stringify(data.sort(mySorter))) // shuffle once
      console.log(JSON.stringify(data.sort(mySorter))) // shuffle again

      小改进

      我们可以进行参数化比较,而不是像sortByGroup 这样的硬编码排序器,sortByProp -

      const sortByProp = (prop = "") =>
        map(empty, (o = {}) => o[prop])
      
      const sortByFullName =
        concat
          ( sortByProp("lastName")  // primary: sort by obj.lastName
          , sortByProp("firstName") // secondary: sort by obj.firstName
          )
      
      data.sort(sortByFullName) // ...
      

      为什么是模块?

      定义一个单独的Comparison 模块的好处很多,但我不会在这里重复它们。该模块允许我们轻松地对复杂的排序逻辑进行建模 -

      const sortByName =
        map(empty, x => x.name)
      
      const sortByAge =
        map(empty, x => x.age)
      
      const data =
        [ { name: 'Alicia', age: 10 }
        , { name: 'Alice', age: 15 }
        , { name: 'Alice', age: 10 }
        , { name: 'Alice', age: 16 }
        ]
      

      name排序,然后按age排序-

      data.sort(concat(sortByName, sortByAge))
      // [ { name: 'Alice', age: 10 }
      // , { name: 'Alice', age: 15 }
      // , { name: 'Alice', age: 16 }
      // , { name: 'Alicia', age: 10 }
      // ]
      

      age排序,然后按name排序-

      data.sort(concat(sortByAge, sortByName))
      // [ { name: 'Alice', age: 10 }
      // , { name: 'Alicia', age: 10 }
      // , { name: 'Alice', age: 15 }
      // , { name: 'Alice', age: 16 }
      // ]
      

      轻松reverse 任何分拣机。这里我们按name排序,然后按age反向排序-

      const Comparison =
        { // ...
        , reverse: (m) =>
            (a, b) => m(b, a)
        }
      
      data.sort(concat(sortByName, reverse(sortByAge)))
      // [ { name: 'Alice', age: 16 }
      // , { name: 'Alice', age: 15 }
      // , { name: 'Alice', age: 10 }
      // , { name: 'Alicia', age: 10 }
      // ]
      

      功能原则

      我们的Comparison 模块既灵活又可靠。这允许我们以类似公式的方式编写分类器 -

      // this...
      concat(reverse(sortByName), reverse(sortByAge))
      
      // is the same as...
      reverse(concat(sortByName, sortByAge))
      

      concat 表达式类似 -

      // this...
      concat(sortByYear, concat(sortByMonth, sortByDay))
      
      // is the same as...
      concat(concat(sortByYear, sortByMonth), sortByDay)
      
      // is the same as...
      nsort(sortByYear, sortByMonth, sortByDay)
      

      多重排序

      因为我们的比较可以组合起来以创建更复杂的比较,所以我们可以有效地按任意数量的因素进行排序。例如,对日期对象进行排序需要三个比较:yearmonthday。感谢功能性原则,我们的concatempty 完成了所有艰苦的工作 -

      const Comparison =
        { // ...
        , nsort: (...m) =>
            m.reduce(Comparison.concat, Comparison.empty)
        }
      
      const { empty, map, reverse, nsort } =
        Comparison
      
      const data =
        [ { year: 2020, month: 4, day: 5 }
        , { year: 2018, month: 1, day: 20 }
        , { year: 2019, month: 3, day: 14 }
        ]
      
      const sortByDate =
        nsort
          ( map(empty, x => x.year)  // primary: sort by year
          , map(empty, x => x.month) // secondary: sort by month
          , map(empty, x => x.day)   // tertiary: sort by day
          )
      

      现在我们可以按yearmonthday 排序-

      data.sort(sortByDate)
      // [ { year: 2019, month: 11, day: 14 }
      // , { year: 2020, month: 4, day: 3 }
      // , { year: 2020, month: 4, day: 5 }
      // ]
      

      同样可以通过yearmonthday 轻松反向排序 -

      data.sort(reverse(sortByDate))
      // [ { year: 2020, month: 4, day: 5 }
      // , { year: 2020, month: 4, day: 3 }
      // , { year: 2019, month: 11, day: 14 }
      // ]
      

      要运行reversensort 示例,请按照original post ?


      复杂排序

      您肯定在寻找细致入微的分拣机,但不用担心,我们的模块能够处理它 -

      const { empty, map } =
        Comparison
      
      const randParitionBy = (prop = "", m = new Map) =>
        map
          ( empty
          , ({ [prop]: value }) =>
              m.has(value)
                ? m.get(value)
                : ( m.set(value, Math.random())
                  , m.get(value)
                  )
          )
      
      console.log(data)                               // presort...
      console.log(data.sort(randParitionBy("group"))) // first...
      console.log(data.sort(randParitionBy("group"))) // again...
      

      输出 -

      // pre-sort
      [ {name:"Alice",group:"staff"}
      , {name:"Monty",group:"client"}
      , {name:"Cooper",group:"client"}
      , {name:"Jason",group:"staff"}
      , {name:"Farrah",group:"staff"}
      , {name:"Celeste",group:"guest"}
      , {name:"Briana",group:"staff"}
      ]
      
      // first run (elements keep order, but sorted by groups, groups are sorted randomly)
      [ {name:"Celeste",group:"guest"}
      , {name:"Alice",group:"staff"}
      , {name:"Jason",group:"staff"}
      , {name:"Farrah",group:"staff"}
      , {name:"Briana",group:"staff"}
      , {name:"Monty",group:"client"}
      , {name:"Cooper",group:"client"}
      ]
      
      // second run (elements keep order and still sorted by groups, but groups are sorted differently)
      [ {name:"Alice",group:"staff"}
      , {name:"Jason",group:"staff"}
      , {name:"Farrah",group:"staff"}
      , {name:"Briana",group:"staff"}
      , {name:"Monty",group:"client"}
      , {name:"Cooper",group:"client"}
      , {name:"Celeste",group:"guest"}
      ]
      

      const Comparison =
        { empty: (a, b) =>
            a < b ? -1
              : a > b ? 1
                : 0
        , map: (m, f) =>
            (a, b) => m(f(a), f(b))
        }
        
      const { empty, map } =
        Comparison
      
      const data =
        [ { name: "Alice", group: "staff" }
        , { name: "Monty", group: "client" }
        , { name: "Cooper", group: "client" }
        , { name: "Jason", group: "staff" }
        , { name: "Farrah", group: "staff" }
        , { name: "Celeste", group: "guest" }
        , { name: "Briana", group: "staff" }
        ]
      
      const randParitionBy = (prop = "", m = new Map) =>
        map
          ( empty
          , ({ [prop]: value }) =>
              m.has(value)
                ? m.get(value)
                : ( m.set(value, Math.random())
                  , m.get(value)
                  )
          )
      
      console.log(JSON.stringify(data.sort(randParitionBy("group")))) // run multiple times!

      【讨论】:

      • 所以我尝试将其调整为我自己的代码,它似乎正在对组中的所有观察结果进行排序,然后在组内进行随机化。这实际上与预期行为相反,其中组本身会更改顺序,但在组内对象不会。我的期望是,如果我按随机数排序,然后按组排序会解决问题,但事实并非如此。想法?
      • @user3614648 我误解了您原始帖子中的解释。我帖子底部的编辑是否回答了您的问题?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      • 1970-01-01
      • 1970-01-01
      • 2022-11-25
      • 1970-01-01
      • 1970-01-01
      • 2017-06-28
      相关资源
      最近更新 更多