我有一个对象数组,我在其中对一个键(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
而不是单个对象中随机播放对象组。虽然我知道如何在数组中洗牌对象,但我不知道如何在数组中洗牌对象组。
我的想法是,可能有一种方法可以利用组的值仅在组更改时才更改的事实。
只需创建一个随机属性以在组级别进行排序,并将该属性分配给数组中的每个相应对象:
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))
您可以先按对象的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);
你这里有一个有趣的问题。我最近刚刚写过这个,所以如果您对这个答案中提出的想法感兴趣,请点击该链接 -
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
}
展开下面的代码段,在您自己的浏览器中验证结果。多次运行程序以查看结果始终按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)
多重排序
由于我们的比较可以组合在一起以创建更复杂的比较,因此我们可以有效地按任意数量的因素进行排序。例如,对日期对象进行排序需要三个比较:year
、month
和day
。由于功能原理,我们的concat
和empty
完成了所有艰苦的工作 -
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
)
现在我们可以按year
、month
、day
排序 -
data.sort(sortByDate)
// [ { year: 2019, month: 11, day: 14 }
// , { year: 2020, month: 4, day: 3 }
// , { year: 2020, month: 4, day: 5 }
// ]
同样轻松地按year
、month
、day
反向排序 -
data.sort(reverse(sortByDate))
// [ { year: 2020, month: 4, day: 5 }
// , { year: 2020, month: 4, day: 3 }
// , { year: 2019, month: 11, day: 14 }
// ]
要运行reverse
和nsort
示例,请按照原始帖子进行操作
复杂排序
您当然正在寻找细致入微的分拣机,但不用担心,我们的模块能够处理它 -
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!