[英]How to use array method to combine objects with same property value in an array
[英]How to insert objects in array next to objects with the same property value
我有一系列对象
const allRecords = [
{
type: 'fruit',
name: 'apple'
},
{
type: 'vegetable',
name: 'celery'
},
{
type: 'meat',
name: 'chicken'
}
]
我想从另一个数组插入对象,以便将元素放置在相同类型的元素旁边。
const newRecords = [
{
type: 'fruit',
name: 'pear'
},
{
type: 'vegetable',
name: 'spinach'
},
{
type: 'meat',
name: 'pork'
}
]
这样的调用是这样的:
allRecords.sortAndInsert(newRecords)
返回如下内容:
[
{
type: 'fruit',
name: 'apple'
},
{
type: 'fruit',
name: 'pear'
},
{
type: 'vegetable',
name: 'celery'
},
{
type: 'vegetable',
name: 'spinach'
},
{
type: 'meat',
name: 'chicken'
},
{
type: 'meat',
name: 'pork'
},
在我的情况下,我无法比较“类型”来确定应按字母顺序或按长度排列的位置((蔬菜在肉类之前,但在水果之后)。此外,没有ID属性可以对事物进行数字定位。我只想按相同的类型对事物进行分组。
我发现我可以通过使用数组的长度获取索引来插入正确的索引:
// This gives the amount of records for each group.
//In our example, this would be 2 for 'apple' and 'pear', etc
const multiplier = (allRecords.length + newRecords.length) /
(newRecords.length);
for (let i = 0; i < newRecords.length; i++){
// Insert the record at 1 + i + multiplier. 'pear' will go to 1 + 0 * 2 = 1
allRecords.splice(1 + i * multiplier, 0, newRecords[i]);
}
return allRecords;
但是,该函数在做什么不是很容易理解。 此外,它假定新记录具有每种类型之一。
我想要一个函数,而不是查看属性并将它们组合在一起。 理想情况下,它还应该能够按某种顺序对组进行排序(例如,指定“水果”组排在最前面,“蔬菜”组排在其后,然后是“肉类”组)。
我会完全使用地图。 一个例子如下。
let myMap = new Map(); myMap.set('fruit', [{ name: 'apple', type: 'fruit' }]); myMap.set('vegetable', [{ name: 'celery', type: 'vegetable' }]); myMap.set('meat', [{ name: 'chicken', type: 'meat' }]); const newRecords = [{ type: 'fruit', name: 'pear' }, { type: 'vegetable', name: 'spinach' }, { type: 'meat', name: 'pork' }] newRecords.forEach(function(el) { let arr = myMap.get(el.type); arr.push(el); myMap.set(el.type, arr); }); for (let [k, v] of myMap) { console.log(k); console.log(v); }
分组
这里有很多要讨论的内容,所以我要快一点。 如果您有任何卡住的地方,请发表评论,我会尽力扩大任何有问题的领域。
首先,不能保证allRecords
或newRecords
在合并之前会被排序。 使用Map
可以轻松有效地对类似项目进行分组。 但是,当我们要按所需顺序打印项目时,将需要对地图的值进行排序。 我们将在此答案的第二部分中进行处理。 我们allRecords
type
属性将allRecords
分组-
const allRecords =
[ { type: 'fruit', name: 'apple' }
, { type: 'vegetable', name: 'spinach' }
, { type: 'meat', name: 'chicken' }
, { type: 'fruit', name: 'raspberry' } // added this item
]
const m1 =
groupBy(x => x.type, allRecords)
console.log(m1)
// Map
// { 'fruit' =>
// [ { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// ]
// , 'vegetable' =>
// [ { type: 'vegetable', name: 'spinach' }
// ]
// , 'meat' =>
// [ { type: 'meat', name: 'chicken' }
// ]
// }
接下来,我们以相同的方式对newRecords
进行分组-
const newRecords =
[ { type: 'meat', name: 'pork' }
, { type: 'fruit', name: 'pear' }
, { type: 'vegetable', name: 'celery' }
, { type: 'dairy', name: 'milk' } // added this item
]
const m2 =
groupBy(x => x.type, newRecords)
console.log(m2)
// Map
// { 'meat' =>
// [ { type: 'meat', name: 'pork' }
// ]
// , 'fruit' =>
// [ { type: 'fruit', name: 'pear' }
// ]
// , 'vegetable' =>
// [ { type: 'vegetable', name: 'celery' }
// ]
// , 'dairy' =>
// [ { type: 'dairy', name: 'milk' }
// ]
// }
在继续之前,让我们定义通用函数groupBy
const groupBy = (f, a = []) =>
a.reduce
( (map, v) => upsert(map, [ f (v), v ])
, new Map
)
// helper
const upsert = (map, [ k, v ]) =>
map.has(k)
? map.set(k, map.get(k).concat(v))
: map.set(k, [].concat(v))
接下来,我们需要一种方法来组合两个映射m1
和m2
const m3 =
mergeMap(m1, m2)
console.log(m3)
// Map
// { 'fruit' =>
// [ { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'fruit', name: 'pear' }
// ]
// , 'vegetable' =>
// [ { type: 'vegetable', name: 'spinach' }
// , { type: 'vegetable', name: 'celery' }
// ]
// , 'meat' =>
// [ { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// ]
// , 'dairy' =>
// [ { type: 'dairy', name: 'milk' }
// ]
// }
我们可以轻松定义mergeMap
来支持将任意数量的地图合并在一起-
const mergeMap = (...maps) =>
maps.reduce(mergeMap1, new Map)
// helper
const mergeMap1 = (m1, m2) =>
Array.from(m2.entries()).reduce(upsert, m1)
我们可以看到,地图很好地将项目分组在一起。 让我们现在收集所有值-
const unsorted =
[].concat(...m3.values())
console.log(unsorted)
// [ { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'vegetable', name: 'spinach' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'dairy', name: 'milk' }
// ]
排序
答案的这一部分不适合胆小的人,但我强烈建议您坚持使用。 我们采用一种功能性方法来编写比较函数,但是在使用的技术上需要权衡取舍。 在这里,我们使用了许多易于编写,测试和维护的简单功能。 因此,这些功能更加灵活,可以在程序的其他区域中重复使用。 有关此方法背后的更多原因以及不使用这些技术时会发生什么的详细信息,请参阅本主题的最新解答 。
好的,所以我们看到列表当前是按水果 , 蔬菜 , 肉和乳制品排序的。 这是由于它们在原始地图中的分组顺序。 如果您希望他们以其他方式订购怎么办?
unsorted.sort(orderByTypes("vegetable", "meat", "fruit"))
// [ { type: 'vegetable', name: 'spinach' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'dairy', name: 'milk' }
// ]
好的,如果我们希望它们按name
排序怎么办?
unsorted.sort(orderByName)
// [ { type: 'fruit', name: 'apple' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'vegetable', name: 'spinach' }
// ]
是否可以先orderByTypes
然后使用orderByName
进行二级排序?
unsorted.sort
( mergeComparator
( orderByTypes("meat", "fruit", "dairy") // primary sort
, orderByName // secondary sort (tie breaker)
)
)
// [ { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'vegetable', name: 'spinach' }
// ]
我们看到结果是按类型, 肉类 , 水果和乳制品优先的第一顺序。 我们还看到按name
二级排序。 肉鸡肉和猪肉的含量升高, 苹果 , 梨和覆盆子的含量也较高 。 请注意,即使orderByTypes
中未使用"vegetables"
,次级排序仍然适用,因此芹菜和菠菜是有序的。
如您所见,我们可以定义灵活的比较器函数,例如orderByTypes
和orderByName
,并使用mergeComparator
对其进行mergeComparator
以实现更为复杂和复杂的行为。 我们将从两者中的简单者开始,即orderByName
const orderByName =
contramap
( ascending // transform base comparator
, x => x.name // by first getting object's name property
)
// base comparator
const ascending = (a, b) =>
a > b
? 1
: a < b
? -1
: 0
// functional utility
const contramap = (f, g) =>
(a, b) =>
f(g(a), g(b))
orderByTypes
比较器的作用更多一些-
const orderByTypes = (...types) =>
contramap
( ascending // transform base comparator
, pipe // using a function sequence
( x => x.type // first get the item's type property
, x => matchIndex(types, x) // then get the index of the matched type
, x => x === -1 ? Infinity : x // then if it doesn't match, put it at the end
)
)
// helper
const matchIndex = (values = [], query) =>
values.findIndex(v => v === query)
// functional utility
const identity = x =>
x
// functional utility
const pipe = (f = identity, ...more) =>
more.reduce(pipe1, f)
// pipe helper
const pipe1 = (f, g) =>
x => g(f(x))
我们定义了两(2)个独立的比较器orderByName
和orderByTypes
,我们要做的最后一件事是确定如何组合它们-
const mergeComparator = (c = ascending, ...more) =>
more.reduce(mergeComparator1, c)
// helper 1
const mergeComparator1 = (c1, c2) =>
(a, b) =>
mergeComparator2(c1(a, b), c2(a, b))
// helper 2
const mergeComparator2 = (a, b) =>
a === 0 ? b : a
放在一起
好吧,让我们看看是否可以鞠躬-
const allRecords =
[ { type: 'fruit', name: 'apple' }
, { type: 'vegetable', name: 'spinach' }
, { type: 'meat', name: 'chicken' }
, { type: 'fruit', name: 'raspberry' }
]
const newRecords =
[ { type: 'meat', name: 'pork' }
, { type: 'fruit', name: 'pear' }
, { type: 'vegetable', name: 'celery' }
, { type: 'dairy', name: 'milk' }
]
// efficient grouping, can support any number of maps
const grouped =
mergeMap
( groupBy(x => x.type, allRecords)
, groupBy(x => x.type, newRecords)
)
const unsorted =
[].concat(...grouped.values())
// efficient sorting; can support any number of comparators
const sorted =
unsorted.sort
( mergeComparator
( orderByTypes("meat", "fruit", "dairy")
, orderByName
)
)
输出量
console.log(sorted)
// [ { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'vegetable', name: 'spinach' }
// ]
展开下面的代码段,以在您自己的浏览器中验证结果-
// --------------------------------------------------- // STEP 1 const upsert = (map, [ k, v ]) => map.has(k) ? map.set(k, map.get(k).concat(v)) : map.set(k, [].concat(v)) const groupBy = (f, a = []) => a.reduce ( (map, v) => upsert(map, [ f (v), v ]) , new Map ) const allRecords = [ { type: 'fruit', name: 'apple' } , { type: 'vegetable', name: 'spinach' } , { type: 'meat', name: 'chicken' } , { type: 'fruit', name: 'raspberry' } ] const newRecords = [ { type: 'meat', name: 'pork' } , { type: 'fruit', name: 'pear' } , { type: 'vegetable', name: 'celery' } , { type: 'dairy', name: 'milk' } ] const m1 = groupBy(x => x.type, allRecords) console.log("first grouping\\n", m1) // Map // { 'fruit' => // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'spinach' } // ] // , 'meat' => // [ { type: 'meat', name: 'chicken' } // ] // } const m2 = groupBy(x => x.type, newRecords) console.log("second grouping\\n", m2) // Map // { 'meat' => // [ { type: 'meat', name: 'pork' } // ] // , 'fruit' => // [ { type: 'fruit', name: 'pear' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'celery' } // ] // , 'dairy' => // [ { type: 'dairy', name: 'milk' } // ] // } // --------------------------------------------------- // STEP 2 const mergeMap1 = (m1, m2) => Array.from(m2.entries()).reduce(upsert, m1) const mergeMap = (...maps) => maps.reduce(mergeMap1, new Map) const m3 = mergeMap(m1, m2) console.log("merged grouping\\n", m3) // Map // { 'fruit' => // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // ] // , 'meat' => // [ { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // ] // , 'dairy' => // [ { type: 'dairy', name: 'milk' } // ] // } const unsorted = [].concat(...m3.values()) console.log("unsorted\\n", unsorted) // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // , { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'dairy', name: 'milk' } // ] // --------------------------------------------------- // STEP 3 const ascending = (a, b) => a > b ? 1 : a < b ? -1 : 0 const contramap = (f, g) => (a, b) => f(g(a), g(b)) const orderByName = contramap(ascending, x => x.name) const sorted1 = unsorted.sort(orderByName) console.log("sorted by name only\\n", sorted1) // [ { type: 'fruit', name: 'apple' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'dairy', name: 'milk' } // , { type: 'fruit', name: 'pear' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'vegetable', name: 'spinach' } // ] // --------------------------------------------------- // STEP 4 const identity = x => x const pipe1 = (f, g) => x => g(f(x)) const pipe = (f = identity, ...more) => more.reduce(pipe1, f) const matchIndex = (values = [], query) => values.findIndex(v => v === query) const orderByTypes = (...types) => contramap ( ascending , pipe ( x => x.type , x => matchIndex(types, x) , x => x === -1 ? Infinity : x ) ) const sorted2 = unsorted.sort(orderByTypes("vegetable", "meat", "fruit")) console.log("sorted by types\\n", sorted2) // [ { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // , { type: 'dairy', name: 'milk' } // ] // --------------------------------------------------- // STEP 5 const mergeComparator = (c = ascending, ...more) => more.reduce(mergeComparator1, c) const mergeComparator1 = (c1, c2) => (a, b) => mergeComparator2(c1(a, b), c2(a, b)) const mergeComparator2 = (a, b) => a === 0 ? b : a const sorted3 = unsorted.sort ( mergeComparator ( orderByTypes("meat", "fruit", "dairy") , orderByName ) ) console.log("sorted by types, then name\\n", sorted3) // [ { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'pear' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'dairy', name: 'milk' } // , { type: 'vegetable', name: 'celery' } // , { type: 'vegetable', name: 'spinach' } // ]
请注意,如果您想查看地图内容,则需要打开浏览器的开发者控制台。
假设allRecords
已经按type
排序,那么具有任何特定type
的值都位于数组的一个连续段中(或者该type
在数组中根本不存在),那么以下内容将与Object.assign()
非常相似Object.assign()
:
function spliceBy<T, K extends keyof T> (key: K, target: T[], ...sources: Iterable<T>[]) {
const groups: Map<T[K], T[]> = new Map()
for (const source of sources) {
for (const entry of source) {
const value = entry[key]
const oldEntries = groups.get(value)
const entries = oldEntries || []
if (!oldEntries) groups.set(value, entries)
entries.push(entry)
}
}
for (const [value, entries] of groups) {
// find the end of a group of entries
let found = false
const index = target.findIndex(
entry => entry[key] === value ? (found = true, false) : found
)
if (found) target.splice(index, 0, ...entries)
else target.push(...entries)
}
return target
}
const allRecords = [{type:'fruit',name:'apple'},{type:'vegetable',name:'celery'},{type:'meat',name:'chicken'}]
const newRecords = [{type:'fruit',name:'pear'},{type:'vegetable',name:'spinach'},{type:'meat',name:'pork'}]
console.log(spliceBy('type', allRecords, newRecords))
如果您不想修改allRecords
,则可以这样调用它:
console.log(spliceBy('type', [], allRecords, newRecords))
这应该做的工作:
interface Record {
type: string;
name: string;
}
interface TypedRecords {
[type: string]: records[];
}
private _recordsByType: TypedRecords = {};
sortAndInsert(allRecords: Record[], newRecords: Record[]): Record[] {
const records: Record[] = [];
this.insert(allRecords);
this.insert(newRecords);
Object.keys(this._recordsByType).forEach(type => {
this._recordsByType[type].forEach(name => {
records.push({type, name});
});
});
return records;
}
private insert(records: Record[]) {
records.forEach(record => {
if (!this._recordsByType[record.type]) {
this._recordsByType[record.type] = [];
}
this._recordsByType[record.type].push(record.value);
});
}
不知道这是否是最佳解决方案性能,但是这里是:
const allRecords = [
{
type: 'fruit',
name: 'apple'
},
{
type: 'vegetable',
name: 'celery'
},
{
type: 'meat',
name: 'chicken'
}
]
const newRecords = [
{
type: 'fruit',
name: 'pear'
},
{
type: 'vegetable',
name: 'spinach'
},
{
type: 'meat',
name: 'pork'
}
]
function sortAndInsert(...records){
let totalRecords = [];
for(let record of records){
totalRecords = totalRecords.concat(record);
}
totalRecords.sort((rec1, rec2)=>{
if(rec1.type == rec2.type)
return 0;
else if(rec1.type > rec2.type)
return 1;
else
return -1;
})
return totalRecords;
}
let completeRecords = sortAndInsert(newRecords, allRecords);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.