実務案件などやってると、自分が決めたルールで多重ソートをしたくなる事が良くあるわけですが、それを 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 : 自分が決めたルールで二次元配列の多重ソート