実務案件などやってると、自分が決めたルールで多重ソートをしたくなる事が良くあるわけですが、それを JavaScript でやってみたいと思います。
やりたいこと
まずやりたい事は、データベースのテーブルのような二次元配列にソートをかけたいのだけど、自分の指定した値の順番でソートしたい。たとえば、ユーザー情報のリストがあったとしたときに、優先的に都道府県ごとのグループに整列させ、更に同じグループ内では男女別に並び替える。といった感じだ。

多重ソートする処理は、PHP とか MySQL とか ActionScript 3.0 とかではデフォルトで用意されていたと思う(忘れた)のだけど、ActionScript や JavaScript ない。ということで自作してます。
JavaScriptで配列内のエレメントをソート
今回やろうとしてることはちょっと複雑なので、まず ActionScript にある sortOn メソッドのような 配列内のエレメントをソート する処理を試してみます。
テストする配列はこれ
var datas=[
{name:"河童",index:6},
{name:"鎌鼬",index:5},
{name:"烏天狗",index:3},
{name:"牛鬼",index:4},
{name:"酒呑童子",index:1},
{name:"土蜘蛛",index:2},
{name:"妖狐",index:0}
];
配列内のエレメントをソートするには、sort メソッドの引数に条件を比較するための関数を入れます。
//ソート
datas.sort(function(a,b){
if(a["index"]<b["index"])return -1;
if(a["index"]>b["index"])return 1;
return 0;
});
//出力
for(var i=0,n=datas.length;i<n;i++){
console.log(datas[i]["name"],datas[i]["index"]);
}
これを実行すると index 順に並びます。
値の順番を指定して配列内のエレメントをソート
数字ではなく特定の値の順番で並び替えをする場合、たとえば以下のような配列があるとします。
var datas=[
{name:"河童",type:"妖魔"},
{name:"鎌鼬",type:"妖獣"},
{name:"烏天狗",type:"妖魔"},
{name:"牛鬼",type:"妖魔"},
{name:"酒呑童子",type:"妖魔"},
{name:"土蜘蛛",type:"妖魔"},
{name:"妖狐",type:"妖獣"}
];
これを「妖魔 → 妖獣」という順番で並び替えたいので、順序用の配列 を用意して何番目の値かを元の配列に index として代入していきます。
//indexを代入
var rule=["妖魔","妖獣"];
for(var i=0,n=datas.length;i<n;i++){
var index=rule.indexOf(datas[i]["type"]);
datas[i]["index"]=index;
}
//ソート
datas.sort(function(a,b){
if(a["index"]<b["index"])return -1;
if(a["index"]>b["index"])return 1;
return 0;
});
//出力
for(var i=0,n=datas.length;i<n;i++){
console.log(datas[i]["name"],datas[i]["type"],datas[i]["index"]);
}
「妖魔 → 妖獣」という順番で並び替えられます。
複数の順序ルールを指定して多重ソート
今度は、その値の順番を複数用意して、多重にソートをかけたいと思います。
配列内のオブジェクトの要素を増やしました。
var datas=[
{name:"河童",type:"妖魔",attribution:"水"},
{name:"鎌鼬",type:"妖獣",attribution:"風"},
{name:"烏天狗",type:"妖魔",attribution:"風"},
{name:"牛鬼",type:"妖魔",attribution:"水"},
{name:"酒呑童子",type:"妖魔",attribution:"無"},
{name:"土蜘蛛",type:"妖魔",attribution:"地"},
{name:"妖狐",type:"妖獣",attribution:"火"}
];
並び替える順番は、まず「妖魔 → 妖獣」という順番で並び替えて、重複した値は「火 → 水 → 風 → 地 → 無」という順番で更に並び替えたいと思います。
で、ここからが本題で、最終的には index でソートするのですが、index の値を導くための公式を考えました。例えば、a、b、cというカテゴリがあって、それぞれaの順序は配列A、bの順序は配列B、cの順序は配列Cという配列のルールがあった場合、以下のような index の値を求める計算式が成り立ちます。

つまり、配列Aは typeの順が「妖魔 → 妖獣」、配列Bは attributionの順が「火 → 水 → 風 → 地 → 無」、配列Cは省略するので、以下のような計算をします。

//indexを代入
var ruleA=["妖魔","妖獣"];
var ruleB=["火","水","風","地","無"];
for(var i=0,n=datas.length;i<n;i++){
var indexA=ruleA.indexOf(datas[i]["type"]);
var indexB=ruleB.indexOf(datas[i]["attribution"]);
datas[i]["index"]=indexA*ruleB.length+indexB;
}
//ソート
datas.sort(function(a,b){
if(a["index"]<b["index"])return -1;
if(a["index"]>b["index"])return 1;
return 0;
});
//出力
for(var i=0,n=datas.length;i<n;i++){
console.log(datas[i]["name"],datas[i]["type"],datas[i]["attribution"],datas[i]["index"]);
}
最優先は「妖魔 → 妖獣」、続いて「火 → 水 → 風 → 地 → 無」という順番で並び替えられます。
多重ソート関数
上記のことを踏まえまして、多重ソートができる関数を作りました。
msort(datas, rules)
- 第1引数(datas)
- 並び替えする対象の二次元配列
- 第2引数(rules)
- 並び替えルールを集めた配列
/**
* 多重ソート関数
* Aの位置×Bの数×Cの数+Bの位置×Cの数+Cの位置
* @param datas {array} 並び替えする対象の二次元配列
* @param rules {array} 並び替えルール
*/
function msort(datas,rules){
if(!(datas instanceof Array))throw new Error('datas is not array.');
if(!(rules instanceof Array))throw new Error('rules is not array.');
//順序番号のパラメータを生成
var p='_index_'+Math.floor(Math.random()*1000)+((new Date()).getTime()).toString();
//並び替えする対象の二次元配列をループ
for(var i1=0,n1=datas.length;i1<n1;i1++){
var n2=rules.length;
//並び替えの順番が指定されていないときに、datasから値を取り出して、通常のsortをした順序指定をrules変数内に上書きする
for(var i2=0;i2<n2;i2++){
var key=rules[i2][0];
var rule=rules[i2][1];
if(!(rule instanceof Array)){
var tmp=new Array(n1);
for(var i3=0;i3<n1;i3++){
tmp[i3]=datas[i3][key];
}
tmp.sort();
if(rule=='desc')tmp.reverse();
rules[i2][1]=new Array(n1);
for(var i3=0;i3<n1;i3++){
rules[i2][1][i3]=tmp[i3];
}
}
}
//順序を計算する公式によって、順序番号の値を代入する
var indexs=new Array(n2);
for(var i2=0;i2<n2;i2++){
var key=rules[i2][0];
var rule=rules[i2][1];
var index=rule.indexOf(datas[i1][key]);
indexs[i2]=(index>-1)?index:rule.length;
for(var i3=i2+1;i3<n2;i3++){
var rule=rules[i3][1];
if(rule instanceof Array){
indexs[i2]*=rule.length+1;
}
}
}
datas[i1][p]=0;
for(var i2=0;i2<n2;i2++){
datas[i1][p]+=indexs[i2];
}
}
//順序番号の値でソートする
datas.sort(function(a,b){
if(a[p]<b[p])return -1;
if(a[p]>b[p])return 1;
return 0;
});
}
並び替えルールの指定方法は以下より。
指定されたルールのソート
keyという項目から指定されたルールに並び替える
var rules=[
['key',["火","水","地","風","無"]]
];
msort(datas,rules);
文字コード順のソート
keyという項目から数字順/文字コード順に並び替える
var rules=[
['key']
];
msort(datas,rules);
降順のソート
keyという項目から数字順/文字コード順を降順に並び替える
var rules=[
['key','desc']
];
msort(datas,rules);
使用サンプル
多重ソート関数を確認できるサンプルページを用意しました。
もし、必要なときがあればぜひ参考にしてみて下さい。
- Example : 自分が決めたルールで二次元配列の多重ソート


