連想配列ソート関数

はてな人力検索で回答した際に、連想配列ソート関数を作成したので載せておきます。

Operaで想定外の動作をする場合があります。追記を参照してください。

<html>
<head>
<title></title>
<script>
/**
 * sortObj 連想配列ソート関数
 * @obj    対象オブジェクト
 * @isKey    true          : キーでソート
 *           false OR null : 値でソート
 * @isNumber true          : 数値としてソート
 *           false OR null : 文字列としてソート
 * @isDesc   true          : 降順ソート
 *           false OR null : 昇順ソート
 */
function sortObj(obj, isKey, isNumber, isDesc){
  var ary = new Array();
  for(var i in obj){
    ary.push({key:i, value:obj[i]});
  }
  ary = ary.sort(sortFunc);
  var ret = new Object();
  for(var i = 0; i < ary.length; i++){
    ret[ary[i].key] = ary[i].value;
  }
  return ret;
  
  function sortFunc(left, right){
    var kv = (isKey) ? "key" : "value";
    var a = left[kv], b = right[kv];
    if(isNumber){
      a = parseFloat(a);
      b = parseFloat(b);
    }else{
      a = String(a);
      b = String(b);
    }
    if(isDesc){
      return a > b ? -1 : a < b ? 1 : 0;
    }else{
      return a < b ? -1 : a > b ? 1 : 0;
    }
  }
}

var hoge = new Object();
hoge['ringo'] = 200;
hoge['banana'] = 1300;
hoge['mikan'] = 130;
hoge['budou'] = 300;

var str = "";
var result = null;

str += "値 文字列 昇順>";
result = sortObj(hoge);
for(var i in result){
  str += i + ":" + result[i] + ",";
}
str += "<br/>";

str += "キー 文字列 昇順>";
result = sortObj(hoge, true);
for(var i in result){
  str += i + ":" + result[i] + ",";
}
str += "<br/>";

str += "値 数値 昇順>";
result = sortObj(hoge, false, true);
for(var i in result){
  str += i + ":" + result[i] + ",";
}
str += "<br/>";

str += "値 文字列 降順>";
result = sortObj(hoge, false, false, true);
for(var i in result){
  str += i + ":" + result[i] + ",";
}
str += "<br/>";
</script>
</head>
<body onload="document.getElementById('test').innerHTML = str;">
<div id="test"></div>
</body>
</html>

prototype.jsでの実現方法についてはEnumerableクラス(3)のsortByメソッドを参照してください。

追記。

上記の関数はOperaで想定外の動作をする場合があります。Operaではキーが自然数である場合、これが優先的に(昇順に)処理されるためです。つまり、

var hoge = new Object();
hoge['1'] = 200;
hoge['2'] = 1300;
hoge['3'] = 130;
hoge['4'] = 300;

とすると、ソート方法の指定にかかわらず、結果はすべて以下となります。

1:200,2:1300,3:130,4:300

上記関数は、オブジェクトメンバが登録された順が、for inループで処理される順と同じであることを利用していたのですが、そもそもこの二つの相関は保証されているわけではないんですね。。。

処理の途中で生成している、キーと値のペアを格納した配列をそのまま使用したほうがいいかもしれません*1。その場合は以下になります。

<html>
<head>
<title></title>
<script>
/**
 * sortObj 連想配列ソート関数(配列で返却)
 * @obj    対象オブジェクト
 * @isKey    true          : キーでソート
 *           false OR null : 値でソート
 * @isNumber true          : 数値としてソート
 *           false OR null : 文字列としてソート
 * @isDesc   true          : 降順ソート
 *           false OR null : 昇順ソート
 * 返却値    [{key:(キー), value:(値)}, ・・・]形式の配列
 */
function sortObj(obj, isKey, isNumber, isDesc){
  var ary = new Array();
  for(var i in obj){
    ary.push({key:i, value:obj[i]});
  }
  return ary.sort(sortFunc);
  
  function sortFunc(left, right){
    var kv = (isKey) ? "key" : "value";
    var a = left[kv], b = right[kv];
    if(isNumber){
      a = parseFloat(a);
      b = parseFloat(b);
    }else{
      a = String(a);
      b = String(b);
    }
    if(isDesc){
      return a > b ? -1 : a < b ? 1 : 0;
    }else{
      return a < b ? -1 : a > b ? 1 : 0;
    }
  }
}

var hoge = new Object();
hoge['ringo'] = 200;
hoge['banana'] = 1300;
hoge['mikan'] = 130;
hoge['budou'] = 300;

var str = "";
var result = null;

str += "値 文字列 昇順>";
result = sortObj(hoge);
for(var i = 0; i < result.length; i++){
  str += result[i].key + ":" + result[i].value + ",";
}
str += "<br/>";

str += "キー 文字列 昇順>";
result = sortObj(hoge, true);
for(var i = 0; i < result.length; i++){
  str += result[i].key + ":" + result[i].value + ",";
}
str += "<br/>";

str += "値 数値 昇順>";
result = sortObj(hoge, false, true);
for(var i = 0; i < result.length; i++){
  str += result[i].key + ":" + result[i].value + ",";
}
str += "<br/>";

str += "値 文字列 降順>";
result = sortObj(hoge, false, false, true);
for(var i = 0; i < result.length; i++){
  str += result[i].key + ":" + result[i].value + ",";
}
str += "<br/>";
</script>
</head>
<body onload="document.getElementById('test').innerHTML = str;">
<div id="test"></div>
</body>
</html>

*1:prototype.jsのHashクラスsortメソッドも同様の動きをします。