- ベストアンサー
複数のキーで配列をソートするには?
配列が複数あって、キーを3つでソートする場合には どのようにしたらよろしいでしょうか? @a = ("A","B","C","B","A" ); @b = ("Y","X","Z","X","Z" ); @c = (4,3,5,2,1); 結果として、 A,Y,4 A,Z,1 B,X,2 B,X,3 C,Z,5 のように表示したいのですが、2次元配列でないと無理でしょうか? ちょっと分かりにくいかもしれませんが、Excelなどで、3つのキーで 優先順位を設定してソートするようなイメージです。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
最初に、回答に書き間違いが2カ所あったことをお詫びします。 1) 上から5行目: $a->[2] <=> $b[2] ---> $a->[2] <=> $b->[2] 2) 下から3行目: $y[$a] cmp $y[$a] ---> $y[$a] cmp $y[$b] 配列のソートには要素そのものを並べ替えるほかに、 配列の添字 (インデックス) を並べ替えるやり方があります。 @x = ("A", "B", "C", "B", "A"); 1) @sorted = sort { $a cmp $b } @x; # { $a cmp $b } は省略可 2) @order = sort { $x[$a] cmp $x[$b] } 0 .. $#x; sort では、$a と $b は特別な意味を持っています。$a と $b には、 ソート対象のリストのうちの2つの要素がセットされます。grep, map の $_ と同じようなもので、sort では同時に2つの要素を処理するため このようになっているものと思います。 2) のソートブロック { $x[$a] cmp $x[$b] } の $x[$a], $x[$b] は、配列 @x の各要素を指しています。配列名に @a または @b を使うと、各要素を 指定する $a[$a], $a[$b], $b[$a], $b[$b] のブラケット外の $a, $b は配 列名とは解釈されません (なお、@c は差し支えありません)。 ソートを行っている部分は、<=> のところだけではなく cmp によっても 行っています。 { $a cmp $b } # 文字列比較 { $a <=> $b } # 数値比較 sort { $x[$a] cmp $x[$b] or $y[$a] cmp $y[$b] or $z[$a] <=> $z[$b] } 0 .. $#x; 上のソートブロックでは、3つの比較文を or で繋げています。まず最初に、 @x の各要素を文字列比較して、同じ場合は結果が偽になりますので、次に @y の同じ添字の要素を文字列比較して、それでも同じときには @z の同じ 添字の要素を数値比較せよ、という意味になります。 最後の質問ですが、質問を誤解しているかも知れません。 @v = (44, 33, 55, 22, 11); @w = ("a", "b", "c", "b", "a"); @x = ("A", "B", "C", "B", "A"); @y = ("Y", "X", "Z", "X", "Z"); @z = (4, 3, 5, 2, 1); @order = sort { $x[$a] cmp $x[$b] or $y[$a] cmp $y[$b] or $z[$a] <=> $z[$b] } 0 .. $#x; @v = @v[@order]; @w = @w[@order]; @x = @x[@order]; @y = @y[@order]; @z = @z[@order];
その他の回答 (6)
- kumoz
- ベストアンサー率64% (120/185)
みなさんと似たような回答ですが、 print join "\n", map { join(',', @$_) } sort { $a->[0] cmp $b->[0] or $a->[1] cmp $b->[1] or $a->[2] <=> $b[2] } map { [ $a[$_], $b[$_], $c[$_] ] } 0 .. $#a; print "\n"; 質問の配列名を変えれば、もう少し簡単になります。 配列名とソートのデフォルト変数の名前が衝突して、 ソートブロック内で配列の個々の要素を指定できない問題がある。 @x = ("A", "B", "C", "B", "A"); @y = ("Y", "X", "Z", "X", "Z"); @z = (4, 3, 5, 2, 1); foreach (sort { $x[$a] cmp $x[$b] or $y[$a] cmp $y[$a] or $z[$a] <=> $z[$b] } 0 .. $#x) { print "$x[$_],$y[$_],$z[$_]\n"; }
補足
皆様、お返事が大変遅くなりまして申し訳ありません。 かなり単純にできることが分かったのですが、いくつか追加質問をさせてください。 この例でいくと、@x,@y,@zで定義していますが、$a,$b,$cが必要になるのはどうしてでしょうか? あと、ソートしたい配列が5個になった場合(キーは3つ)は対応可能でしょうか? ソートを行っている部分は、<=>のところだと思われますが、キーにならない配列を同時に入れ替えることは可能ですか?
- okiyoshi
- ベストアンサー率34% (11/32)
# 忘れてました・・先頭に書きます。 use Data::Dumper;
- okiyoshi
- ベストアンサー率34% (11/32)
my @a = ("A","B","C","B","A" ); my @b = ("Y","X","Z","X","Z" ); my @c = (4,3,5,2,1); # 作業用 my @array; # ソートしたい配列達の同位置をだんご3兄弟でリファレンスにする push @array, [ $a[$_], $b[$_], $c[$_] ] for 0..$#a; # それをソートする @array = sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] } @array; # 確認する print Dumper( @array ); # 元の配列に戻す ($a[$_],$b[$_],$c[$_]) = @{$array[$_]} for 0..$#array; # 確認する print Dumper( @a,@b,@c ); # オンメモリなのでデータ容量が問題だったりして・・
- t140
- ベストアンサー率39% (59/150)
@a = ("A","B","C","B","A" ); @b = ("Y","X","Z","X","Z" ); @c = (4,3,5,2,1); for (sort{$a[$a] cmp $a[$b] or $b[$a] cmp $b[$b] or $c[$a] <=> $c[$b]} 0 .. $#a){ print "$a[$_],$b[$_],$c[$_]\n"; }
- bgbg
- ベストアンサー率53% (94/175)
こちらをご参考に。 http://www.din.or.jp/~ohzaki/perl.htm#SortMulti 結果的に、2次元配列に変換したほうが楽だと思います。 for($i = 0; $i < @a; $i++){ push(@newArray,[$a[$i],$b[$i],$c[$i]]); } これで2次元配列にできますから、あとは @newArray = sort{$a->[0] cmp $b->[0] or $a->[1] cmp $b->[1] or $a->[2] cmp $b->[2]} @newArray; というようにソートしたい項目順にorで区切ってsort関数に入れ込めば良いでしょう。
単純な方法では無理です。 俺はこれを実現するために、データベースのエンジンシステムを組みました(笑)(しかも Perl で!!) まぁ、そこまではいかずとも、2次元配列化して「最優先項目でまずソート」「キーが重複する要素だけ2番目の優先項目でソート」といったような処理が必要になるでしょう。
補足
やはり、単純には行きませんか… どうもC言語感覚で考えると、for文で3回入れ子にしないと できないかなぁ~と考えていたのですが、Perlだと思ってもみない 解決法がありそうで質問してみたのですが、重複する要素の位置を 把握してソートする必要がありますよね? このあたりがものすごく処理に時間がかかったりするのか不安です。 ちなみに、1配列あたりでデータは最大1000件くらいです。 キーは3つですが、配列自体は20個近くになるため、最初はmysqlに やらせようと思いましたが、group化が複雑になるので、perlで処理 できないものか考えていました。
お礼
ありがとうございます。 非常に参考になりました。 sortでは、$a,$bは予約変数になることは知りませんでした。 前回の回答で私なりに応用してやってみたのですが、最後の 質問部分は、連想配列に格納すれば、キー3つでソートする と、自動で他の部分もソートされることが分かりました。 perlでは2次元配列は非常に見にくいので連想配列は非常に 便利です。