• ベストアンサー

2次元ハッシュ または 2次元配列をソートしたい

2次元ハッシュのソートをしたいです。 ハッシュは2つのキーを使用していて、 1つ目のキーは文字列、2つ目のキーは数字(0からの連番)です。 ハッシュの中身は文字列が入っています。 これを次のような表に見立てて、特定の列でソートしたいのです。 hash['a']['0'], hash['a']['1'], ..., hash['a']['50'], hash['q']['0'], hash['q']['1'], ..., hash['q']['50'], hash['c']['0'], hash['c']['1'], ..., hash['c']['50'], ... hash['d']['0'], hash['d']['1'], ..., hash['d']['50'], 例えば 6列目の値によってソートするということです。 以下のようにソートしようとしましたが、うまくいきません。 my @sorthash = sort { $a->[6] <=> $b->[6] } @hash; 何かヒントがあれば教えてください。

  • Perl
  • 回答数6
  • ありがとう数5

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

  • ベストアンサー
  • Werner
  • ベストアンサー率53% (395/735)
回答No.4

> ただ、perl の文法の誤りはどこにあるのか分からないので、教えていただけないでしょうか。 必ずしも文法エラーにはならないかもしれませんが、最初の > hash['a']['0'], hash['a']['1'], ..., hash['a']['50'], で、[ ] の中に文字列が入っています。 [ ] は配列要素のアクセスに使うので整数しか使えません。 (ついでに頭に$がついてない。) もしhashが名前の通りハッシュ(%hash)であるなら、 [ ] でアクセスするのはおかしいし、@hashもおかしいです。 前半の方はPerlコードではなくイメージを図示したものと考えることもできますが、 @hashの方は誤りと見なされても仕方ないですね。 (hashが配列であるなら正しいですが、あなたはハッシュと言っていましたから。 また、hashという名前の配列を作るのは文法上正しくても混乱を招くのであまりよろしくないですね。) > さて、実際のところは、配列を格納したハッシュにするのが一番適切かと思い、修正してみました。 要するにやりたいことは、 7列目(indexが6なので1から数えるなら7列目だよね?)をキーにして行をソートすることですよね。 それなら配列の配列(二次元配列)がもっとも適当だと思います。 二次元配列を使ったやり方はNo.3で書かれていますので試してみてください。 (疑問点があればまた聞いてください。) CSVから配列のハッシュに格納している部分ですが、 > @{$content{$values[6]}} = @values; ここで $values[6] をハッシュのキーにしてますから、 $values[6] (7列目) に同じ値が出現したときは 先に代入されたデータが消えてしまいます。 これは意図通りの動作ですか? ここの部分は、正直なぜ $values[6] をキーにしたハッシュにしているのかよく分からないです。 別のところで、7列目の値をキーに行全体を一発で取得したいというニーズが有ったのなら この構造にするのもうなずけるのですが、 そうでないなら二次元配列で良くありませんか? > for (my $i = 0; $i < $#sorted_keys; $i++) { $#sorted_keys は配列@sorted_keysの最後のインデックス番号を示します。 配列@sorted_keysの要素数ではありません。 よって、 $i < $#sorted_keys ではなく $i <= $#sorted_keys が正しいと思います。

palayo
質問者

お礼

> $#sorted_keys は配列@sorted_keysの最後のインデックス番号を示します。 > 配列@sorted_keysの要素数ではありません。 > よって、 $i < $#sorted_keys ではなく $i <= $#sorted_keys が正しいと思います。 おわっ。こんな間違いをしていたのですね。気づかなかった。 $i < @sorted_keys で解決しました。 ありがとうございました。

その他の回答 (5)

  • taknak08
  • ベストアンサー率50% (8/16)
回答No.6

おっと、  print OUT join(',', map { s/"/""/g ? qq{"$_"} : $_ } @$_), "\n"; ではなく  print OUT join(',', map { (s/"/""/g or /,/) ? qq{"$_"} : $_ } @$_), "\n"; でしたね、失礼しました・・・苦笑

  • taknak08
  • ベストアンサー率50% (8/16)
回答No.5

こんばんは。No.2のtaknak08です。  > ただ、perl の文法の誤りはどこにあるのか分からないので、教えていただけないでしょうか。 最初のご質問中の「hash['a']」は、Perlとしては文法の誤りとなります。 「$hash{'a'}」の誤りか、かなり無理な解釈をしても「$hash['a']」の誤りなように思われます。 (またPerlの場合、「ハッシュ」は $x{...}、「配列」は $x[...]、と、かなり気を遣って?区別して呼ばれています) しかし少なくともコードが動作しているのであれば、実際には文法の誤りは無いのだと思います。 補足で添付いただいたコードを試したわけではないのですが、  > # ソートしない場合には、カラム名の行も出力されますが、  > # ソートするとカラム名の行が出力されません。 の理由が、ぱっと見では私にはちょっと分かりませんでした。 純粋に「CSVデータのうち6列目の値でソートし、その結果を再びCSVで出力したい」ということでしたら、以下のようなコードではいかがでしょうか。 ハッシュは一切用いずに、CSVデータを純粋な二次元配列(=@values)へ格納し、6列目(=$values[...][5])でソートして、出力しています。 一度おためしいただければと思います。(インデントが取れてしまうので行頭の空白を全角に置換しています ご注意ください) #!/usr/bin/perl use strict; open DATA, '< src.csv' or die $!; my @values; while (<DATA>) {  s/[\r\n]*$/,/s;  my @v;  while (s/^("(?:[^"]|"")*"|[^",]*),//) {   my $v = $1;   $v =~ s/^"(.*)"$/$1/ and $v =~ s/""/"/g;   push @v, $v;  }  die 'Invalid format' if length;  push @values, [@v]; } close DATA; # Sort in 6th column. @values = sort { $a->[5] cmp $b->[5] } @values; open OUT, '> dest.csv' or die $!; for (@values) {  print OUT join(',', map { s/"/""/g ? qq{"$_"} : $_ } @$_), "\n"; } close OUT;

palayo
質問者

お礼

そもそも最初に配列の配列に格納していないのは、 ただ単にcsvを並び替えるだけでなく、csvの編集も行っているためです。 特定のセルを編集するに当たり、 セルの位置を ${$content{$name}}[24] のようにハッシュを使って取得したいからです。 最初のお礼のソースで ####### # ハッシュ %csv_content の編集 ###### と書いていた部分です。 でないと、毎度走査しなくてはならず、非常に非効率だからです。 まさに該当しそうな記事がありました。 http://codezine.jp/article/detail/1020?p=1 以上、ありがとうございました。

  • kumoz
  • ベストアンサー率64% (120/185)
回答No.3

> $csv_content{$values[6]}{$i} = $values[$i]; > ... > my @sortvals = sort { $a->[6] <=> $b->[6] } @vals; 上の sort 文自体は、対象が数値ならば問題がないように思います。%csv_content の作成時に $values[6] を使っていて、それが 'a' や 'q' に該当すると思うのですが? <=> の代わりに cmp を使うと結果はどうなるでしょうか? csv の内容がわからないので細かいことはわかりませんが、一度 %csv_content を作ってから @vals を作っているのは少し無駄なように思います。 my @vals; while (my $line =<DATA>) { ... push @vals, [@values]; } my @sortvals = sort { $a->[6] cmp $b->[6] } @vals; # csv にして出力

palayo
質問者

お礼

お返事ありがとうございます!! kumoz様のおっしゃるとおり、ハッシュを配列に置きなおすのは かなり無駄ですので、taknak08様の回答への補足、お礼に書いたように、 ハッシュ値に配列を使ってみました。 依然、問題がおきていますので、そちらの方にもご回答いただけたら、 ありがたいです。

  • taknak08
  • ベストアンサー率50% (8/16)
回答No.2

Wernerさんが指摘されているとおり、Perlの文法に誤りがあるようですので、まずは入門書から紐解かれるとよいと思います。 (PHPご出身でしょうか? PHPとPerlはどちらも「$」や「[...]」を用いており、ぱっと見は似てはいますが、実際はかなり異なる言語です。) 「1つ目のキーは文字列、2つ目のキーは数字(0からの連番)です。  ハッシュの中身は文字列が入っています。」 をそのままPerlで書くと、下記のようになるでしょうか。 my %hash = ( 'a' => ['aiueo', 'kakiku', 'sasisu'], 'q' => ['zzzzz', 'yyyyyy', 'xxxxxx'], 'd' => ['a0123', 'b98765', 'c77777'], ); Perlでは、ハッシュに順番はありませんので、「%hashの中身を直接並び替える」ということはできません。 しかし「$hash{'...'}[1]='xxx' のように2列目(←1列目を[0]と数えています)の値(=この場合は'xxx')の大小で並び替えた%hashのキー一覧(=この場合は'...'の一覧リスト)を取り出す」ということはできます。 一度下記のコードをお試しください。 use strict; my %hash = ( 'a' => ['aiueo', 'kakiku', 'sasisu'], 'q' => ['zzzzz', 'yyyyyy', 'xxxxxx'], 'd' => ['a0123', 'b98765', 'c77777'], ); print "hash{'q'}[1] is '", $hash{'q'}[1], "'.\n\n"; # yyyyy print "Sorted by the string of 2nd column:\n"; my @sorted_key = sort { $hash{$a}[1] cmp $hash{$b}[1] } keys %hash; for my $sorted_key (@sorted_key) { print "hash{'", $sorted_key, "'}: "; for (my $i = 0; $i < 3; ++$i) { print "[", $hash{$sorted_key}[$i], "]"; } print "\n"; }

palayo
質問者

お礼

最初の質問で、ハッシュと書きながら配列表記にしてしまったのは 混乱を招いたようで、失礼しました。 ただ、perl の文法の誤りはどこにあるのか分からないので、教えていただけないでしょうか。 $i が0,1,2,3,... のような整数値の場合にも、$hash{$i} のようなハッシュって使えますよね? ですから、前のソースで書いている表記 $csv_data{$name}{$i} のような 書き方も文法上の誤りはないと思うのですが・・・。 実際のところ、ソート以外の部分はちゃんと動作してましたし。 2変数のハッシュとして書けば $csv_data{$name}{$i} となり、 2次元のハッシュ配列(なんていうの?)として書けば、 taknak08様の指摘するような $csv_data{$name}[$i] となるだけの違いかと・・・。 確かに、今回の条件では taknak08様の方法の方が適切ではありますが、 文法上はどちらも正しいのでは?? 私の認識違いでしたら、教えてください。 さて、実際のところは、配列を格納したハッシュにするのが一番適切かと思い、修正してみました。 補足のソースで一応動作しています。 ただ、ソートするとデータが1件(1行)だけ失われます。 元のcsvでカラム名に使われていた行なのですが・・・。 ソートせずに出力すると、ちゃんと出力されます。 (当然、先頭ではない行に出力されてしまいますが) なんとか修正したいのですが、原因が分かりません。 何か分かれば教えていただけないでしょうか?

palayo
質問者

補足

open(DATA, '<', $csv_file) or die "csv file does not exist."; while (my $line = <DATA>) { $line .= <DATA> while ($line =~ tr/"// % 2 and !eof(DATA)); $line =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; my @values = map {/^"(.*)"$/s ? scalar($_ = $1, s/""/"/g, $_) : $_} ($line =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); @{$content{$values[6]}} = @values; } close(DATA); # ソートしない場合には、カラム名の行も出力されますが、 # ソートするとカラム名の行が出力されません。 #my @sorted_keys = keys( %content ); my @sorted_keys = sort { $a cmp $b } keys( %content ); open(OUT, '>'. "$new_csv_file"); for (my $i = 0; $i < $#sorted_keys; $i++) { my $line = join ',', map {(s/"/""/g or /[\r\n,]/) ? qq("$_") : $_} @{$content{$sorted_keys[$i]}}; print OUT $line,"\n"; } close(OUT);

  • Werner
  • ベストアンサー率53% (395/735)
回答No.1

ハッシュのはずなのに書き方が配列になってますよ? ハッシュのプレフィクスは@じゃなくて%ですし、 値の参照も [ ] ではなく { } です。 もしhashが本当にハッシュならperlとしておかしいので 修正して補足してもらえますか? あとできればハッシュにテストデータを入れるコードも提示してくれると 分かりやすくて答えやすいです。 なお、文字列の比較に <=> 演算子を使うのは間違いです。 文字列の比較は cmp 演算子。 #おまけ use strict; use Data::Dumper; my %hash = ( 'a'=> [0,1,2,3,4,5], 'b'=> [12,13,14,15,16,17], 'c'=> [6,7,8,9,10,11], ); print Dumper(\%hash); my @ary = sort {$a->[3] <=> $b->[3]} values %hash; print Dumper(\@ary);

palayo
質問者

お礼

お返事ありがとうございます。 補足にUPした通り、CSVを読み込んで処理した後、 再びCSVで出力しようとしています。 ハッシュのままだと出力がよく分からなかったので、配列に変換しました。 ソートと出力がハッシュのままできるのであれば、ハッシュのままの方が 配列に変換する作業が省けて早いでしょうから、望ましいのでしょうが・・。 どちらでもいいので、どうしたらいいか教えていただけないでしょうか?

palayo
質問者

補足

my %csv_content = (); my @values; my $ncsvCols=0; open(DATA, '<', "src.csv") or die "csv not exist."; #正規表現によるCSVの読み込み while (my $line = <DATA>) { $line .= <DATA> while ($line =~ tr/"// % 2 and !eof(DATA)); $line =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @values = map {/^"(.*)"$/s ? scalar($_ = $1, s/""/"/g, $_) : $_} ($line =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); for (my $i = 0; $i< $#values; $i++) { $csv_content{$values[6]}{$i} = $values[$i]; } if ($#values > $ncsvCols) { $ncsvCols = $#values; } } close(DATA); ####### # ハッシュ %csv_content の編集 ###### # CSV の出力用配列作成 open(OUT, '>'. "dest.csv"); my @vals; my @csv_key = keys(%csv_content); for (my $i = 0; $i < $#csv_key; $i++) { for (my $j = 0; $j < $ncsvCols; $j++) { if ( exists $csv_content{$csv_key[$i]}{$j} ){ $vals[$i][$j] = $csv_content{$csv_key[$i]}{$j}; } } } ## ここでソートしたいが??? #my @sortvals = sort { $a->[6] <=> $b->[6] } @vals; # CSV にして出力 for (my $i = 0; $i < $#csv_key; $i++) { my $line = join ',', map {(s/"/""/g or /[\r\n,]/) ? qq("$_") : $_} @{$vals[$i]}; print OUT $line."\n"; } close(OUT); 1;

関連するQ&A

  • 二次元ハッシュ

    hash[3][3]=9; 上記のように、2次元のハッシュを使いたいです。 1次元のハッシュは hash1= new Object(); と定義して、 hash['key']=1; などとアクセスすればよいのは調べたのですが、2次元の場合はどのように定義するのか教えてください。 ※オーサリングツール『はParaFla!』ActioScitptは1.0相当です。 よろしくお願いいたします。

    • ベストアンサー
    • Flash
  • ハッシュのハッシュのソート

    rubyでハッシュのソート方法についてはいくつか情報のサイトを見つけられました。 ですが今やりたいのは、ハッシュのハッシュのソートなのですが、うまいやり方がわかりませんでした。 具体的には、 h1 = {"user1"=>{"a"=>10, "b"=>20, "c"=>30"}, "user2"=>{"d"=>5, "e"=>8}, "user3"=>{"f"=>10, "g"=>5, "h"=>10} } というようなハッシュのハッシュを想定しています。ユーザごとに案件ごとの必要工数(時間)をハッシュとして持たせ、全工数が多いユーザ順にソートしたいのです。 上記の場合だと、 {"user1"=>{"a"=>10, "b"=>20, "c"=>30"}, "user3"=>{"f"=>10, "g"=>5, "h"=>10}, "user2"=>{"d"=>5, "e"=>8} } というようにソートしたいのですが、何かやり方がありましたらご教授いただけますでしょうか。

    • ベストアンサー
    • Ruby
  • 二次元ハッシュの引き出し方について

    2次元のハッシュをObjectを用いて作り、以下の様に第一キー固定で第二キーを取り出したいです。 ----------------------------------------------------------- hash = new Object; hash[3] = new Object; hash[3][1] = 3; hash[3][2] = 6; hash[3][3] = 9; for (key in hash[3]){  _root.debug+=key; } ----------------------------------------------------------- 上記は上手く動かないので、以下の様に修正しました。 ----------------------------------------------------------- hash = new Object; hash[3] = new Object; hash[3][1] = 3; hash[3][2] = 6; hash[3][3] = 9; tmp=hash[3]; for (key in tmp){  _root.debug+=key; } ----------------------------------------------------------- いったんtmpに代入する事で期待通りの結果が得られましたが、スッキリしません。 Perl等の他言語では、「無名ハッシュ」という概念を表記化することができるので、Actionscriptでも良い表記方法がありましたら教えてください。 ※オーサリングルールはParaFla! ActionSctiptは1.0相応です。

  • Ruby 2次元のハッシュ

    Rubyで2次元のハッシュを扱いたいです。 perlで書くと以下のような感じです。(最近perlに疎遠なので自信無いですが^^;) hash{$key1}{$key2}=$value; foreach $key1 (keys %hash){ foreach $key2 (keys %{$hash{$key1}}){ print "$hash{$key1}{$key2}\n"; } } Rubyだとどんな感じになりますか?

  • 二次元配列のハッシュ版の記述方法?

    二次元配列のハッシュ版を実現させたいのですが、 以下の記述で正しいでしょうか? 構文に疎いので教えてください。 #!/usr/bin/perl %HASH_TEST = ( 'あ'=>{'A'=>'1', 'B'=>'2', 'C'=>'3'}, 'い'=>{'A'=>'4', 'B'=>'5', 'C'=>'6'}, ); print $HASH_TEST{'い'}{'B'}; #=> 5が表示されます。 exit; __END__

    • ベストアンサー
    • Perl
  • 【javascript】ハッシュのキーをソートして取り出したい

    ハッシュのキーをソートして取り出したいです。 perlで表現すると、以下の様なかんじです。 foreach $key (sort keys %hash) { ... } そこで、prototype.jsを使って以下の様に書いてみました。 <html> <head> <script type="text/javascript" src="prototype.js"></script> </head> <body > <script > var table = {c:'C', b:'B', a:'A'}; $H(table).keys().sort().each(function(key){ alert(key + ' ' + table[key]); }); </script> </body> </html> 他に良い方法ありましたら、教えてください。

  • 多次元配列のソート

    過去に同様の質問があったのですが、未回答でしたので質問させていただきます。 多次元配列のうちの一つの列の値でソートし、その他の列にも結果を連動させたいのですが方法が分かりません。 たとえば、a[n][m]という配列で a[0][0]=C a[0][1]=う         a[0][0]=A a[0][1]=あ a[1][0]=A a[1][1]=あ   →    a[1][0]=B a[1][1]=い a[2][0]=B a[2][1]=い         a[2][0]=C a[2][1]=う というように、n列でソートしm列でもその結果で並べ替えたいです。 恐らくComparatorインタフェースを使用すると可能かと思うのですが、方法を教えて頂けないでしょうか。

    • ベストアンサー
    • Java
  • 静的ハッシュの配列のキーに対応する値の数の多さ順で表示させたい

    ハッシュのキーに対応する値の数の多さ順で表示させたいと考え、下記の所まで試行錯誤しておりますが、どうにも思ったようにソートできずにおります。 #!/usr/bin/perl use strict; my(%a, $i, $j ,$allarray ,@keys ,@keys2 ,%hash ,%files ,$a_mumei_ref ,$key ,$value ,@value ,$x ,$files); # ハッシュの配列を静的に作る %a = ( '0' => [ qw(0) ], '1' => [ qw(1 1) ], '3' => [ qw(3 3 3) ], '7' => [ qw(7 7 7) ], '2' => [ qw(2) ], '4' => [ qw() ], '5' => [ qw() ], '6' => [ qw() ], '8' => [ qw(8 8) ], '9' => [ qw(9) ], ); @keys = sort { $hash{$b} <=> $hash{$a} || length($b) <=> length($a) || $a cmp $b } keys %a; #ハッシュのキーを数字順で表示 foreach (@keys){ print $_ ."\n"; } # 静的に作ったハッシュの配列を取り出してみる foreach $i (sort keys %a) { for ($j = 0; $j <= scalar(@{$a{$i}})-1; $j++) { print '$a{'. $i. '}['. $j. ']='. $a{$i}[$j]. ' '; } $allarray=scalar(@{$a{$i}})-1; print "No$i:kosuu:$allarray"; print "\n"; #配列の値の個数を調べその配列を作成 my($a_mumei) = $allarray; $a_mumei_ref = \$a_mumei; $files{"$i"}=($i,$a_mumei_ref); } #each関数で%filesの中身を表示 while ( ( $key , $value ) = each %files ){ print "key:$key value:$$value\n" ; } #試行錯誤 foreach $x (sort { $files{$b} <=> $files{$a} } keys %files){ print "$x => $files->{$x}\n"; } @keys2 = sort {$hash{$a} <=> $hash{$b}} keys %files; #@keys2 = sort { $hash{$b} <=> $hash{$a} || length($b) <=> length($a) || $a cmp $b } keys %files; #@keys2 = sort { $hash{$a} cmp $hash{$b} } keys %files; print "@keys2\n"; print "\n"; __END__; 私のイメージしておりますのは、ソートした結果がハッシュのキーに対応する値の数の多さ順で下記のように表示させたいのですが、 どのようにすれば可能でございますか、ご教授願えませんでしょうか key:3 value:2・・・この場合valueは配列の個数 key:7 value:2 key:8 value:1 key:1 value:1 key:9 value:0 key:2 value:0 key:0 value:0 key:6 value:-1 key:4 value:-1 key:5 value:-1

    • ベストアンサー
    • Perl
  • ハッシュのハッシュを値とキーでソートする方法

    %array = ( 'A' => {   'a' => 7,   'b' => 3,   'c' => 9,   'd' => 1, }, 'B' => {   'a' => 3,   'b' => 8,   'c' => 3, },); のようなハッシュがあったとして、値の降順、1つ目のキー昇順、2つ目のキー昇順でソートし、下のような形で出力したいのですが、どのようにすればよいのでしょうか。 A  c  9 B  b  8 A  a  7 A  b  3 B  a  3 B  c  3 A  d  1

    • ベストアンサー
    • Perl
  • 動的ハッシュを作って取り出したいのですが・・・

    お世話になります。 フォームから送られてくるデータを動的に作ったハッシュで参照出来るように取り組んでるんですが、思ったように出来ず思い悩んでおります。 どうすれば、意図した形でデータを取り出すことが出来ますでしょうか my %FORM = ( 'd01' => 'あ', 'd02' => 'い', 'd03' => 'う', 'd04' => 'え', 'd05' => 'お', 'd06' => 'か', 'd07' => 'き', 'd08' => 'く', 'd09' => 'け', 'd10' => 'こ', ); for(sort { $FORM{$a} cmp $FORM{$b} } keys %FORM){ print "$_ = $FORM{$_} \n"; } $list="d01,d02,d03,d04,d05,d06,d07,d08,d09,d10,"; $i=-1; foreach (split/,/,$list){ $i++; $hash{$_}=$i; } for(sort { $hash{$a} <=> $hash{$b} } keys %hash){ print "$_ = $hash{$_} \n"; $view = ${"FORM$_"}; print "$view\n"; }; 最後のprint "$view\n";箇所で、 $list="d01,d02..." を split/,/,$list したので、 $FORM{d01} $FORM{d02} となるようにして、 「あ い う え お」と取り出したいのです。 ご教授のほど、よろしくお願い致します。

    • ベストアンサー
    • Perl

専門家に質問してみよう