配列の要素から、重複を除いたユニーク化をしたい場合は、時折あります。たとえばゲームで、「複数のエフェクトの予約を配列に格納した際、高速化のために、同じ座標のエフェクトは除去したい」などが考えられます。
JavaScriptの場合、配列の要素が単純な数値や文字列の場合は、ユニーク化は簡単です。
よくある方法としては、各要素を、オブジェクトのプロパティに設定して、値を「true
」や「1
」にして、重複を確認するというものがあります。
しかし、要素がオブジェクトの場合は、一工夫が必要になります。
要素がオブジェクトの際、このオブジェクトをプロパティに設定すると問題が出ます。プロパティ名として登録する際、内部処理で「toString
」メソッドが呼び出されます。そして、その戻り値である「[object Object]
」という文字列が、プロパティとして登録されます。
この名前は全てのオブジェクトで同じため、ユニーク化すると1つの要素だけが抽出されることになります。
そこで、このページで用意した、ユニーク化を行う「unique
」関数には、第2引数として、オブジェクトを文字列化するための関数を指定可能にしています。
また、何も指定しない場合は、単純に各要素を、オブジェクトのプロパティに設定して処理を行います。数値や文字列の配列の場合は、第2引数は不要でしょう。
このようにして、数値や文字列だけでなく、オブジェクトを各要素に持つ配列も、ユニーク化することができます。
ファイル構成は以下の通りです。>>ダウンロード
※ 初期化用関数「init」の処理については「Webブラウザ/Node.js 互換関数」をご覧下さい。
'use strict';
(function() {
const _t = init('com.crocro.util');
// unique | 配列のユニーク化
// fncは関数。オブジェクトの比較用に、文字列化して戻します。
_t.unique = function(arr, fnc) {
const o = {};
if (typeof fnc === 'function') {
return arr.filter(x => {
x = fnc(x);
if (o[x]) {return false}
return (o[x] = true);
});
} else {
return arr.filter(x => {
if (o[x]) {return false}
return (o[x] = true);
});
}
};
// 初期化用関数
function init(p) {
try {return module.exports = {}} catch(e) {}
return ((o, p) => {
p.split('.').forEach(k => o = o[k]||(o[k]={}));
return o})(window, p);
};
})();
ユニーク化の処理では、第2引数の「fnc
」が関数かどうかを、「typeof fnc === 'function'
」という判定で分岐させています。これは、パフォーマンスを上げるためです。第2引数が設定されていない場合は、無駄な処理を行わないようにしています。
関数の場合は「x = fnc(x)
」という処理を行っています。分岐した処理は、ここだけ違います。あとは同じです。
配列の「filter
」メソッドは、「true
」を戻した要素のみを集めて、新しい配列を作成します。ここでは、「o[x]
」が存在する場合は「false
」を、「o[x]
」が存在しない(undefined
の)場合は「true
」を格納して、この「true
」を戻り値にしています。
サンプルでは「unique(arrSrc, JSON.stringify)
」として、「JSON.stringify
」を引数にしています。この関数は、オブジェクトを文字列にしてくれます。そのため、この文字列を元に、プロパティが存在するかを判定します。
応用として、「fnc
」関数を用いて、配列の「重複と見なしてよい要素」を間引くこともできます。たとえば以下のようにすれば、1桁目の数字を誤差と見なして、ユニーク化することができます。
// モジュールの読み込み
const unique = require('./unique.js').unique;
// 実行
const arrSrc = [1, 2, 5, 12, 15, 23, 26, 27, 28, 32, 35,
40, 55, 65, 66, 67, 79, 81, 83, 84, 90];
const arrDst = unique(arrSrc, x => x / 10 | 0);
console.log(arrDst);
// [ 1, 12, 23, 32, 40, 55, 65, 79, 81, 90 ]
こうした方法を用いて、大量のキャラのうち、描画しないキャラを間引いたりできます。
const unique = com.crocro.util.unique;
/*
Node.jsの場合は、こちら。
const unique = require('./unique.js').unique;
*/
const arrSrc = [
{ name: 'cat'},
{ name: 'cat'},
{ name: 'cat', age: 1},
{ name: 'cat', age: 2}
];
// 各要素を普通に文字列化すると「[object Object]」になり、
// 全て同じと見なされます
const arrDst0 = unique(arrSrc);
console.log(JSON.stringify(arrDst0, null, ' '));
// [
// {
// "name": "cat"
// }
// ]
console.log('\n----------\n');
// 文字列化する関数を別途用意すると、正しくユニーク化されます
const arrDst1 = unique(arrSrc, x => JSON.stringify(x));
console.log(JSON.stringify(arrDst1, null, ' '));
// [
// {
// "name": "cat"
// },
// {
// "name": "cat",
// "age": 1
// },
// {
// "name": "cat",
// "age": 2
// }
// ]
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>配列のユニーク化 - JavaScript実用サンプルコード解説付き</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">
</script>
<script src="unique.js"></script>
<script src="main.js"></script>
<link rel="stylesheet" href="main.css">
</head>
<body>
<textarea id="src"></textarea>
<button id="exec">実行</button>
<textarea id="dst"></textarea>
</body>
</html>
'use strict';
$(function() {
// 変数の初期化
const $src = $('#src');
const $dst = $('#dst');
const $exec = $('#exec');
const $reset = $('#reset');
// 値のセット
$src.val(String.raw`
const unique = com.crocro.util.unique;
const arrSrc = [
{name: 'cat'},
{name: 'cat'},
{name: 'cat', age: 1},
{name: 'cat', age: 2}
];
// 各要素を普通に文字列化すると「[object Object]」になり、
// 全て同じと見なされます
const arrDst0 = unique(arrSrc);
console.log(JSON.stringify(arrDst0, null, ' '));
console.log('\n----------\n');
// 文字列化する関数を別途用意すると、正しくユニーク化されます
const arrDst1 = unique(arrSrc, JSON.stringify);
console.log(JSON.stringify(arrDst1, null, ' '));
`.trim());
// コンソール ログのラップ
const log = console.log;
console.log = function() {
for (let i = 0; i < arguments.length; i ++) {
$dst.append(arguments[i] + ' ');
}
$dst.append('\n');
log.apply(console, arguments);
};
// クリック時の処理
$exec.click(() => {
$dst.empty();
eval($src.val());
});
});
html,
body {
margin: 0;
padding: 0;
}
textarea,
button {
box-sizing: border-box;
width: 100%;
vertical-align:bottom;
}
textarea {
height: 19em;
}
// モジュールの読み込み
const unique = require('./unique.js').unique;
// 実行
const arrSrc = [
{name: 'cat'},
{name: 'cat'},
{name: 'cat', age: 1},
{name: 'cat', age: 2}
];
// 各要素を普通に文字列化すると「[object Object]」になり、
// 全て同じと見なされます
const arrDst0 = unique(arrSrc);
console.log(JSON.stringify(arrDst0, null, ' '));
console.log('\n----------\n');
// 文字列化する関数を別途用意すると、正しくユニーク化されます
const arrDst1 = unique(arrSrc, JSON.stringify);
console.log(JSON.stringify(arrDst1, null, ' '));