JavaScriptの重複をSet Object, Map Objectを用いて取り除く方法について解説します。
const members = [
{ name: 'Alice', group: 'A班' },
{ name: 'Bob', group: 'B班' },
{ name: 'Carol', group: 'A班' },
{ name: 'Dave', group: 'C班' },
{ name: 'Eve', group: 'D班' },
]
例えば、上記のmembersをグループ毎に分けたい時、下記の手順を取るかと思います。
// 重複のないグループ名の配列に変換する
const _groups = distinctGroups(members)
// => [{ group: 'A班' ), { group: 'B班' }, { group: 'C班' }, { group: 'D班' }]
// _groupsの各要素について、同一のグループ名を持つ者をmembersの中から抽出してまとめる
const groups = assignMember(_groups, members)
/* =>
[
{ group: 'A班', members: ['Alice', 'Carol'] },
{ group: 'B班', members: ['Bob'] },
{ group: 'C班', members: ['Dave'] },
{ group: 'D班', members: ['Eve'] }
]
*/
一旦重複のないグループの配列を作るわけですが、上記のgroupの重複除去にはSet Objectが利用できます。
Set オブジェクトは値のコレクションです。挿入順に要素を反復することができます。Set に重複する値は格納出来ません。Set 内の値はコレクション内で一意となります。
Set Objectには要素を次々追加することができますが、既に存在する要素を追加した場合は無視されます。
let mySet = new Set()
mySet.add(1) // Set [ 1 ]
mySet.add(5) // Set [ 1, 5 ]
mySet.add(5) // Set [ 1, 5 ]
引用: Set - JavaScript | MDN
また、コンストラクタに引数を渡すことにより、渡した要素を加えたSet Objectが生成できます。
const set = new Set([1, 1, 1, 2, 3, 4, 1, 3, 5, 3])
console.log(set)
// => Set(5) { 1, 2, 3, 4, 5 }
Set Objectはそのままでは配列としては使えず、一旦values()メソッドでイテレーターオブジェクトを返却してから、それを配列に変換する必要があります。
Array.from()やスプレッド構文が使えます。
const arr = [...set.values()]
const arr2 = Array.from(set.values())
// => どちらも[1, 2, 3, 4, 5]
Set Objectを使うと、下記のように重複を除去するdistinctGroups()を書くことができます。
const distinctGroups = members => {
// グループ名のみ抽出
const groups = members.map(e => e.group)
// Set Objectを生成して重複を除去
const set = new Set(groups)
// グループ名のstring配列から、{ group: string } のオブジェクトの配列にmap
return [...set.values()].map(e => ({ group: e }))
}
console.log(distinctGroups(members))
// => [ { group: 'A班' }, { group: 'B班' }, { group: 'C班' }, { group: 'D班' } ]
groupがただのstringのグループ名であれば上記の方法で問題ないのですが、Objectだった場合、Set Objectを用いた重複除去は意図した通りに動作しません。
const members = [
{ name: 'Alice', group: { id: '001', name: 'A班' } },
{ name: 'Bob', group: { id: '002', name: 'B班' } },
{ name: 'Carol', group: { id: '001', name: 'A班' } },
{ name: 'Dave', group: { id: '003', name: 'C班' } },
{ name: 'Eve', group: { id: '004', name: 'D班' } },
]
この場合、Set Objectを用いて重複除去を行おうとした場合下記の結果となります。
const set = new Set(members.map(e => e.group))
console.log(set)
// 出力内容
Set(5) {
{ id: '001', name: 'A班' },
{ id: '002', name: 'B班' },
{ id: '001', name: 'A班' },
{ id: '003', name: 'C班' },
{ id: '004', name: 'D班' }
}
なぜかと言うと、JavaScriptのObjectの等価性の問題です。
Object同士が等価かどうかは、「Objectの内容が同一かどうか」ではなく、「同一のインスタンスか」で確認しているので、「内容が同じでも別のインスタンスのObject同士」は異なるオブジェクトと判断されます。
内容は全く見ずに「全部別のインスタンスだからそれぞれ異なる内容や!」って具合でまるごとSet Objectに含まれてしまっている感じです。
const group = members[0].group
const group2 = { id: '001', name: 'A班' }
const group3 = { id: '001', name: 'A班' }
console.log(group === group2)
// => false
console.log(group === group3)
// => false
console.log(group2 === group3)
// => false
この場合は、Map Objectを利用することで意図した通りに重複を除去できます。
Map Objectはkeyとvalueのペアを格納できるObjectで、keyに関してSet Objectのように重複が排除されます。
Set ObjectのKey value pairのような形で使用できます。
// コンストラクタには[key, value]の配列を渡せる
const map = new Map([
['1', {id: 1, name: 'user1'}],
['2', {id: 2, name: 'user2'}],
['2', {id: 3, name: 'user3'}],
])
console.log(map)
// 出力内容
Map(2) {
'1' => { id: 1, name: 'user1' },
'2' => { id: 3, name: 'user3' }
}
// map.values()は、Map Objectに格納されたvalueのみのイテレーターオブジェクトを返却する。
// Set Objectのvalues()と同様配列に変換するにはArray.from()やスプレッド構文が使える。
console.log([...map.values()])
// => [ { id: 1, name: 'user1' }, { id: 3, name: 'user3' } ]
Map Objectを用いてdistinctGroups()を書き直すと下記のようになります。
const distinctGroups = members => {
// グループオブジェクトのみ抽出
const groups = members.map(e => e.group)
// key valueペアを生成 keyはgroup.id, valueはgroupオブジェクトそのままとする
const kvp = groups.map(group => ([group.id, group]))
// Map Objectを生成して重複を除去
const map = new Map(kvp)
// 配列に変換して返却
return [...map.values()]
}
console.log(distinctGroups(members))
// => [ { id: '001', name: 'A班' }, { id: '002', name: 'B班' }, { id: '003', name: 'C班' }, { id: '004', name: 'D班' } ]
以上、Set Object, Map Objectを用いた重複除去についてでした。
参考:
Set - JavaScript | MDN
Map - JavaScript | MDN