• ベストアンサー

複数の変数を持つ値のsort

下記のようにx(\d+)y(\d+)z(\d)形式で構成されている値をもつリストがあった時に、期待値のように z x y の優先順でsortしたいのですが、よろしくお願いします。 @list = qw( x2048y2z3 x1024y2z5 x1024y4z2 x1024y4z3 x1024y2z2 ) ; 期待値の順 x1024y2z2 x1024y4z2 x1024y4z3 x2048y2z3 x1024y2z5

質問者が選んだベストアンサー

  • ベストアンサー
回答No.3

サブルーチンを持つsort関数を使うと複雑なソートも表現できます。 @newlist = sort{&compare($a,$b)} @list; sub compare { my($v1,v2) = @_; my($v1x,$v1y,$v1z) = split(/[x-z]/,$v1); my($v2x,$v2y,$v2z) = split(/[x-z]/,$v2); return $v1z <=> $v2z or $v1x <=> $v2x or $v1y <=> $v2y; } それぞれx,y,zの数字を分解して、z,x,yの順番に比較していきます。

renkado
質問者

お礼

sortの{}内にsubファンクションもいけるのですね。 この処理は多用するので、この記述はかなりうれしいです。 さっそくコピーして、モジュール化してしまおうと思ってます。

その他の回答 (5)

  • hrm_mmm
  • ベストアンサー率63% (292/459)
回答No.6

No4hrm_mmmです。間違いがありました、済みませんm(_ _)m。 split(/[xyz]/)では、xの前に何もないので先頭に、'' が一個入り、 さらにその先頭に元データを保持させているので、 ということは、sort文の配列添え字番号は1ずつずれますね。 sort{ $a->[4] <=> $b->[4] or $a->[2] <=> b->[2] or $a->[3] <=> $b->[3] }

renkado
質問者

お礼

わざわざご丁寧にありがとうございます。

  • yuuki0229
  • ベストアンサー率70% (33/47)
回答No.5

ややトリッキーになりますが、シュワルツ変換とハッシュを使った方法も回答しておきます。 print map { "x$$_{x}y$$_{y}z$$_{z}\n" } sort { $$a{z} <=> $$b{z} or $$a{x} <=> $$b{x} or $$a{y} <=> $$b{y} } map { { /([xyz])(\d+)/g } } @list; 解説: /([xyz])(\d+)/g これはxかyかzと、値の2項からなるリストを (gオプションによって)可能な限り展開します。 /^(x)(\d+)(y)(\d+)(z)(\d+)$/ でも構いません。 それを内側のブレース(無名ハッシュコンストラクタ)で受け、 { x => 1024, y => 2, z => 2 } こういった無名ハッシュを作成します。 こんな展開を@list回だけで済ませるのがシュワルツ変換です。

renkado
質問者

お礼

ありがとうございます。 こんなに短いコードでも書けるのですね。 自分ではなかなかこういうコードが書けませんが勉強になります。

  • hrm_mmm
  • ベストアンサー率63% (292/459)
回答No.4

http://www.din.or.jp/~ohzaki/perl.htm#SortMulti [ 複数の項目でソートする ] を参考にすると、こんな感じにもできます。 @list = qw( x2048y2z3 x1024y2z5 x1024y4z2 x1024y4z3 x1024y2z2 ) ; print "x:y:z\n" . join( "\n", map{ $_->[0] } sort{ $a->[3] <=> $b->[3] or $a->[1] <=> b->[1] or $a->[2] <=> $b->[2] } map{ [$_, split(/[xyz]/) ] } @list ) . "\n"; まあ、無理に1行につっこむ必要もないのだけど、perlだから出来る技ですね。

renkado
質問者

お礼

ありがとうごあざいます。 mapとsortの合わせ技なのですね。 勉強になります。

  • SHOO-3
  • ベストアンサー率56% (28/50)
回答No.2

つまりこういうことかと ----- my @list = qw( x2048y2z3 x1024y2z5 x1024y4z2 x1024y4z3 x1024y2z2 ) ; @list = sort { my @a = split(/[xyz]/, $a); my @b = split(/[xyz]/, $b); $a[3] <=> $b[3] ? $a[3] <=> $b[3] : $a[1] <=> $b[1] ? $a[1] <=> $b[1] : $a[2] <=> $b[2] ? $a[2] <=> $b[2] : 0; # 桁が解るなら # $a[3]*100000000 + $a[1]*100 + $a[2] <=> $b[3]*100000000 + $b[1]*100 + $b[2]; } @list; e @list;

renkado
質問者

お礼

ありがとうございます。

  • osamuy
  • ベストアンサー率42% (1231/2878)
回答No.1

算術比較演算子<=>は、同値のとき0を返し、Perlでは0はfalseなので、1つめの<=>で判定がつかない場合は、2つ目の<=>で大小を判定するよう、||でつないでいく形にすれば、ソートキーが複数の値からなる場合でも単一の式として表現できます。 これをシュワルツ変換にかければ、大抵ソートできます。 値の桁が事前に限定できるなら、単一の値を生成してキーにできます。こんな感じ: $_ = 'x1024y2z2'; my @k = split( /[xyz]/, $_ ); [ $_, $k[3]*100000000 + $k[1]*100 + $k[2] ];

renkado
質問者

お礼

ありがとうございます。 値の桁数は限定できるので使えそうです。 複雑なsortの具体的な書き方が分からなかったので勉強になりました。

関連するQ&A