大重美幸の「これ見落としてませんか? ActionScript 3.0」
第3回 配列の値の並べ替えをマスターする
今月号の記事
今回のテーマは配列の値の並べ替えです。配列は複数の値を管理したいときに便利な機能です。CS4 からは配列を扱うクラスに Vector クラスが加わりましたが、基本的には Array クラスと同じです。配列を使うときに、よく分からなくて、頭の中が混乱していませんか? 配列を使いこなせてこそ一人前。配列を使ってスマートなスクリプトを書きましょう。
サンプルファイル : edge_oshige_03_sample_fla.zip (942KB)
配列の値をソートしよう
配列の値を並べ替えることをソートと言います。メソッドはsort()です。ここで配列を扱うとき注意しなければならないことがあります。さっそく例を見てみましょう。
[sample] Array_sort_1.fla
配列colorsの値を昇順、降順にソートします。
var colors:Array=["green","red","white","blue"];
var alist:Array =colors.sort();
trace("昇順にソート:",alist);
var blist:Array =colors.sort(Array.DESCENDING);
trace("降順にソート:",blist);
出力結果:
昇順にソート: blue,green,red,white 降順にソート: white,red,green,blue
ソートでは値を小→大と並べる昇順と逆に大→小に並べる降順があります。配列に対してsort()を実行すると値は昇順に並びます。colors のように値が文字列の場合は ABC 順が昇順です。降順に並べるにはsort(Array.DESCENDING)のようにオプションを付けて実行します。
これで何も問題ないように見えますが、次のように alist、blist をtrace()する行を変更すると alist、blist の値がどちらも降順にソートした結果になっています。念のために colors 配列の値を確認すると、これも降順に並び変わっています。こうなる原因は配列がリファレンス型だからです。3個の配列を扱っているようで、実際には1個の同じ配列に3つの名前を付けて操作しているわけです。
[sample] Array_sort_2.fla
trace()するタイミングを変えてみるとすべて同じ配列だとわかります。
var colors:Array=["green","red","white","blue"];
var alist:Array =colors.sort();
var blist:Array =colors.sort(Array.DESCENDING);
trace("昇順にソート:",alist);
trace("降順にソート:",blist);
trace("colorsの結果:",colors);
出力結果:
昇順にソート: white,red,green,blue 降順にソート: white,red,green,blue colorsの結果: white,red,green,blue
したがって、スクリプトのムダを無くそうとあらかじめソートした配列を変数に入れておいても、これではうまくいかないことがわかります。このような場合には、配列を複製してソートを行います。配列を複製するにはslice()が利用できます。
[sample] Array_sort_3.fla
複製した配列をソートします。
var colors:Array=["green","red","white","blue"];
var alist:Array =colors.slice().sort();
var blist:Array =colors.slice().sort(Array.DESCENDING);
trace("昇順にソート:",alist);
trace("降順にソート:",blist);
trace("colorsの結果:",colors);
出力結果:
昇順にソート: blue,green,red,white 降順にソート: white,red,green,blue colorsの結果: green,red,white,blue
文字でのソートと数値でのソート
値をソートする場合には値の種類によって並び順の大小の判断の基準が違ってきます。先の例でも示したようにsort()では文字を昇順に並べますが、次のスクリプトでは、大文字小文字を区別して、大文字が先に並ぶのがわかります。
[sample] sort_ABab.fla
大文字小文字を区別して昇順に並べます。
var colors:Array=["green","Gray","Red","white","Blue"]; var alist:Array =colors.slice().sort(); trace(alist);//出力結果:Blue,Gray,Red,green,white
英文字の大文字小文字を区別せずに並べたい場合には、次のようにArray.CASEINSENSITIVEをsort()のオプションに指定します。
[sample] sort_AaBb.fla
大文字小文字を区別せずに昇順に並べます。
var colors:Array=["green","Gray","Red","white","Blue"]; var alist:Array =colors.slice().sort(Array.CASEINSENSITIVE); trace(alist);//出力結果:Blue,Gray,green,Red,white
次の例では配列 months には数値が入っていますが、これをソートした結果を見ると数値の大きさではなく、文字列として大きさを比較して並べてしまっています。
[sample] sort_num_1.fla
数値ではなく、文字列として値をソートしています。
var months:Array=[4,3,11,10,9,12,2,1,7,8]; var alist:Array =months.slice().sort(); trace(alist);//出力結果:1,10,11,12,2,3,4,7,8,9
配列の値を数値として比較して並べたい場合には、ソートオプションにArray.NUMERICを指定します。これで数値が昇順に並びます。
[sample] sort_num_2.fla
配列の値を数値として比較して昇順にソートします。
var months:Array=[4,3,11,10,9,12,2,1,7,8]; var alist:Array =months.slice().sort(Array.NUMERIC); trace(alist);//出力結果:1,2,3,4,7,8,9,10,11,12
では、数値として降順に並べるにはどうすればよいでしょうか? その答は次のように2つのソートオプションを演算子の|で区切って指定します。
[sample] sort_num_3.fla
配列の値を数値として比較して降順にソートします。
var months:Array=[4,3,11,10,9,12,2,1,7,8]; var blist:Array =months.slice().sort(Array.NUMERIC | Array.DESCENDING); trace(blist);//出力結果:12,11,10,9,8,7,4,3,2,1
書式が揃っていない日付を並べるには?
次の例の配列 datelist には "2008.12.09" や "2009/9/21" あるいは "2009年6月15日" といった日付が入っています。これらの値を日付順に並べるにはどうすればよいでしょうか?
問題は書式が揃っていないところです。書式が揃っていれば文字列として比較してソートできます。そこで正規表現を使って桁数と区切りを yyyy/mm/dd の書式に揃える関数 adjustDateString() を作り、これを使って配列のすべての日付データの書式を揃えます。AS3 ではこのように正規表現を利用できます。
配列の値を順に変換するには、forステートメントなどを使って配列から1個ずつ値を取り出して関数を実行する方法がありますが、Arrayクラスには map() という便利なメソッドがあります。map()を使えば、配列のすべての値を引数で指定した関数で変換できます。日付の桁数と書式が揃ったならば、あとは文字列としてソートすれば日付順に並びます。
[sample] dateSort.fla
配列に入っている日付の書式を揃えた後でソートします。
var datelist:Array=["2008.12.9","2009年6月15日","2008-04-08","2009/9/21","2009/03/05"];
//datelistのすべての値をadjustDateString()で変換する
var newDatelist:Array=datelist.map(adjustDateString);
trace(newDatelist);
//出力結果:2008/12/09,2009/06/15,2008/04/08,2009/09/21,2009/03/05
//日付をソートする
trace(newDatelist.sort());
//出力結果:2008/04/08,2008/12/09,2009/03/05,2009/06/15,2009/09/21
//日付の書式を整える関数
function adjustDateString(date:String, index:int, arr:Array):String{
var pattern:RegExp=/(\d+).(\d+).(\d+)/;
var obj:Object=pattern.exec(date);
var yyyy:String=obj[1];
var mm:String=String(100+int(obj[2])).substr(1,2);
var dd:String=String(100+int(obj[3])).substr(1,2);
date=yyyy+"/"+mm+"/"+dd;
return date;
}
プロパティの値で並べる
オブジェクトが入っている配列では、オブジェクトのプロパティの値でソートすることができます。メソッドはsort()ではなくsortOn()を使います。次の例では配列 books に mc、date、price、star というプロパティをもったオブジェクトが入っています。そして、ラジオボタンでソート順を選ぶと本の並びが変わります。
ソート順を選ぶと本の並びが変わります。
スクリプトでは、まず最初に配列 books に本データのオブジェクトを入れています。本のムービークリップのインスタンスはリンケージ書き出し設定がしてあるシンボルの Book1~Book5 のインスタンスを作っています。次に3個のラジオボタンを作って sortGrp としてグループ化し、ラジオボタンの選択が変化したならば changeHandler() が呼ばれるようにイベントの設定をしています。
changeHandler() では、選択されたラジオボタンに応じて sortOn() のソートオプションを選び、配列 books をソートします。たとえば、btn1 が選ばれたならば
books.sortOn("price", Array.NUMERIC);
を実行してオブジェクトの price プロパティの値で配列をソートします。注目は btn3 のようにソートフィールドが複数ある場合です。この場合はソートフィールドとソートオプションをそれぞれ配列に入れて指定します。
books.sortOn(["star","price"], [0, Array.NUMERIC | Array.DESCENDING]);
なお、star は文字列で昇順にソートしますが、文字列、昇順を示す定数がArrayクラスで定義してありません。この場合は0を指定します。
[sample] sortOn_book.fla
オブジェクトのプロパティの値でソートして本を並べます。
import fl.transitions.Tween;
import fl.transitions.easing.*;
import fl.controls.RadioButton;
import fl.controls.RadioButtonGroup;
//本のインスタンスを作る
var mc1:MovieClip = addChild(new Book1()) as MovieClip;
var mc2:MovieClip = addChild(new Book2()) as MovieClip;
var mc3:MovieClip = addChild(new Book3()) as MovieClip;
var mc4:MovieClip = addChild(new Book4()) as MovieClip;
var mc5:MovieClip = addChild(new Book5()) as MovieClip;
//本データのオブジェクトを配列に入れる
var books:Array=new Array();
books[0] = {mc:mc1,date:"2007.08",price:3800,star:"★★"};
books[1] = {mc:mc2,date:"2008.07",price:3200,star:"★"};
books[2] = {mc:mc3,date:"2009.08",price:3500,star:"★★★"};
books[3] = {mc:mc4,date:"2009.11",price:2980,star:"★★"};
books[4] = {mc:mc5,date:"2009.09",price:2800,star:"★★"};
//ラジオボタンを作る
var btn1:RadioButton = addChild(new RadioButton()) as RadioButton;
var btn2:RadioButton = addChild(new RadioButton()) as RadioButton;
var btn3:RadioButton = addChild(new RadioButton()) as RadioButton;
btn1.label = "価格順";
btn2.label = "年月順";
btn3.label = "★昇順・価格降順";
btn3.width = 200;
btn1.move(30,30);
btn2.move(30,50);
btn3.move(30,70);
//ラジオボタンのグループ設定
var sortGrp:RadioButtonGroup = new RadioButtonGroup("sortgroup");
btn1.group = btn2.group = btn3.group = sortGrp;
sortGrp.addEventListener(Event.CHANGE, changeHandler);
//最初は価格でソートする
btn1.selected = true;
books.sortOn("price", Array.NUMERIC);
//ラジオボタンの選択の変更イベントのリスナー関数
function changeHandler(eventObj:Event):void {
switch (sortGrp.selection) {
case btn1 :
//priceを数値ソート
books.sortOn("price", Array.NUMERIC);
break;
case btn2 :
//dateを文字列ソート
books.sortOn("date");
break;
case btn3 :
//starを文字列・昇順ソートし、さらにpriceを数値・降順ソートする
books.sortOn(["star","price"], [0, Array.NUMERIC | Array.DESCENDING]);
break;
}
doLayout();
}
//mcをソート順に配置する
function doLayout():void {
for (var i:int=0; i < books.length; i++) {
var mc:MovieClip = books[i].mc;
mc.y = 200;
mc.tw = new Tween(mc,"x",Regular.easeInOut,mc.x,70 * i + 60,1,true);
}
}
reverse() を利用して文字列を逆順に並べ替える
配列の値を逆順に並べ替えるメソッドはreverse()です。たとえば、次のように使います。
[sample] Array_reverse.fla
配列の値を逆順に並べ替えます。
var colors:Array=["green","red","white","blue"]; colors.reverse(); trace(colors);//出力結果:blue,white,red,green
このreverse()とストリングを配列に分割するsplit()、逆に配列をストリングに連結するjoin()を組み合わせることで、文字列の並びを逆順に並べ替えることができます。str.split("")では、配列に分割するための区切り文字が空なので、文字列は1文字ずつに分かれて配列に入ります。
[sample] String_reverse.fla
文字列を逆順に並べ替えます。
var str:String="ActionScript";
var strArray:Array=str.split("");
strArray.reverse();
str=strArray.join("");
trace(strArray);//出力結果:t,p,i,r,c,S,n,o,i,t,c,A
trace(str);//出力結果:tpircSnoitcA
値をシャッフルする
最後に配列の値をシャッフルする方法を紹介しましょう。シャッフルする方法はいろいろありますが、よく利用されるのは Fisher-Yates アルゴリズムで、配列からランダムに値を選んで、後ろから順に詰め替えていく方法です。これは席替えでみんなで抽選して後ろの席から順に座る位置を決めていく感じです。席が決まった人は次の抽選には参加せず、残りの席を残りの人たちだけで繰り返し抽選します。
[sample] shuffle_fisher-1.fla
配列の値を Fisher-Yates アルゴリズムでシャッフルします。
var colors:Array = ["green","red","white","blue"];
shuffle(colors);
trace(colors);//出力結果の例:white,red,blue,green
//Fisher-Yates
function shuffle(list:Array):void{
var i:uint=list.length;
while(i){
var j = Math.floor(Math.random()*i);
var tmp:* = list[--i];
list[i] = list[j];
list[j] = tmp;
}
}
ところで、この関数を使って席替えを行うと元と同じ席になってしまう人がまれにあります。そのほうがラッキーという人もいるでしょうが、それではつまらないという人もいるでしょう。では、元と同じ席には続けて当たらないようにするにはどうすればよいでしょうか? その答は次回までの宿題にしましょう。
関連情報
- 大重美幸の「これ見落としてませんか? ActionScript 3.0」
- Adobe Flash CS4 Professional 製品ページ
- Adobe Flash CS4 Professional 体験版 ダウンロード
大重美幸
(おおしげよしゆき)
日立情報システムズ、コミュニケーションシステム研究所を経て独立。株式会社ロクナナ顧問。執筆、講師、ソフトウェア開発を行う。趣味はサーフィンとジョギング。茅ヶ崎在住。著著は約50冊。twitter @oshige、@as3note
近著:Adobe Flash CS4 詳細!ActionScript 3.0入門ノート[完全改訂版]、ActionScript3.0辞典[FlashPlayer10/9対応]
