![](/img/trans.png)
[英]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.