Hatena::Groupperl

Press::Alt_R

2010-08-02

Perlわかば (アルパカ 2.1.1 grep)

grep EXPR, LIST
grep BLOCK LIST

EXPR/BLOCKは、LISTからの要素を $_ で受ける。

# 桁の合計が奇数になる数字だけを取り出す

my @input_numbers = (3 4 6 12 15 21 24 100 311);
my @odd_digit_sum = grep{
  # 最終的に真偽値を返す処理
  my $input = $_;               # このブロックのLISTからの受け皿は $_ (サブルーチンとの違いに注意)
  my @digits = split //, $input # 数字を空の正規表現で分割=1文字ごとに分割
  my $sum;
  $sum += $_ for @digits;       # foreach my $keta(@digits){$sum += $keta;}
  $sum % 2;                     # 奇数→2による剰余は1→真、偶数→偽
} @input_numbers

Perlわかば (アルパカ本 2.1.1 リストのフィルタリング)

リストとgrep

grep BLOCK LIST
grep EXPR, LIST

BLOCK/EXPRが真になったもののみをリストにして返す

BLOCK: {}で囲まれたブロック。通常の文を書いていくことができる
EXPR : 式(演算子や関数など、1命令で実行できるもの)。

perldoc -f grep すればわかります。

TasnimTasnim2014/05/06 18:16Way to use the internet to help people solve prbmoels!

2010-07-20

Perlわかば (サブルーチンのリファレンス)

例:ファイルを再帰的にたどる

#!perl
use strict;

# -- 自力で決め打ち関数を書くと

my_filefind("./");

# -- ファイルを再帰的に探索してprintする
sub my_filefind{
  my $filename = shift;
  # $filename maybe:
  #  a) file
  #  b) directory
  if ( -f $filename ){print $filename , "\n"; return;}

  return unless ( -d $filename);

  print "----opening dir: $filename\n";
  opendir my $DH , $filename || die("cannot open directory");
  my @filelist = readdir($DH);

  $filename =~ s/\/$//; #remove slash
  foreach my $f(@filelist){
    next if ($f =~ /^\./);
    my_filefind($filename . "/" . $f); # join with slash
  }
  print "----closing dir: $filename\n";
  return;
}

File::Findとcoderefを使う

#!perl
use strict;
use File::Find;

find(\&print_filename , "./");

sub print_filename{
  print $_ , "\n";
}

補足 - 深さのぶん先頭にspaceをとる

# -- 行頭に深さのぶんスペースをつける
sub my_filefind_with_depth{
  my $filename = shift;
  my $depth = shift;

  # $filename maybe:
  #  a) file
  #  b) directory
  if ( -f $filename ){print "  " x $depth , $filename , "\n"; return;}

  return unless ( -d $filename);

  print "  " x $depth , "--dir: $filename\n";
  $depth++;

  opendir my $DH , $filename || die("cannot open directory");
  my @filelist = readdir($DH);
  
  $filename =~ s/\/$//; #remove slash
  foreach my $f(@filelist){
    next if ($f =~ /^\./);
    my_filefind_with_depth( $filename . "/" . $f
                            ,$depth
                          ); # join with slash
  }
  return;
}

補足2: File::Findではディレクトリ探索スタックが使えず……?

各ディレクトリのファイルの数を数える。

counterをスタックにローカル変数として確保できれば、counterを足すだけでよいが、やりかたがわからないのでハッシュに入れている。

#!perl
use strict;
use File::Find;

our %counter = ();

find(
     {
      wanted=>\&print_filename,
      postprocess=>\&aggr_directory,
      preprocess=>\&prepare_directory,
     }
     , "./");

sub print_filename{
  print $_ , "\n";
  $counter{$File::Find::dir}++;
}

sub prepare_directory{
  print "-- dir is:" , $File::Find::dir , "\n";
  return @_; #given list of files
}

sub aggr_directory{
  print "-- " , $counter{$File::Find::dir} ,"\n";
}

2010-07-16

Perlわかば(シュウォーツ変換)

#!perl
use strict;

# Schwartzian transform


my @DATA = map{chomp;$_}<DATA>; # remove CRLF and store in ARRAY

my @sorted_data = 
  map{$_->[0]}
  sort{$a->[1] <=> $b->[1]}
  map{[$_, (split /:/, $_)[1] ]} @DATA;
;
print map{$_ . "\n"} @sorted_data;

__DATA__
Yamada:10:1
Sato:2:1
Kato:5:2
Furukawa:1:1
Suzuki:4:2
Ito:7:3
Yamaoka:3:3
Masuno:6:1
Mizuhara:8:3
Akatsuka:9:1

2010-07-14

Perlわかば (シュウォーツ変換のまえに 1)

  • 配列と配列のリファレンス
  • push ARRAY, LIST
    • ARRAY <==== LIST を追加
#!perl
use strict;
use Data::Dumper;

my @DATA = map{chomp;$_}<DATA>; # remove CRLF and store in ARRAY

# 1. 行データ → 行データ, 比較用数値 のセット
my @id_data=();
foreach my $line (@DATA){
  my @list = split ( ":" ,$line );
  push @id_data,[ ($line,$list[1]) ];
}

print Dumper @id_data;
# 2. 比較用数値でデータをソート

# 3. データセットから行データのみとりだし

__DATA__
Yamada:10:1
Sato:2:1
Kato:5:2
Furukawa:1:1
Suzuki:4:2
Ito:7:3
Yamaoka:3:3
Masuno:6:1
Mizuhara:8:3
Akatsuka:9:1

ポイント:構造的なデータをつくる

pushの説明が少し違っていたので補足します。

今回、@id_dataにそのまま「行データ」と「切り出したID」を入れると、このようになります。

('Yamada:10:1' , 10 , 'Sato:2:1' , 2 , 'Kato:5:2' , 5 .... )

これを後からソートすることは難しそうですね(偶数番目の要素だけを拾ってソートするとか……?)。

ここは、「行データ」と「切り出したID」を、ひとくくりにして、@id_dataに入れておきたいわけです。

( ('Yamada:10:1' , 10) , ('Sato:2:1' , 2) , ('Kato:5:2' , 5) .... )

しかし、これだけだと、そのまま入れるのと同じです。リストの中にリストを入れようとしても、Perlは最終的には、リストを単なる値の羅列と見ていますので、カッコをほどいてしまいます。

そこで、構造的にデータを表現する「データのまとまり」として、リファレンスが必要になるのです。

( ['Yamada:10:1' , 10] , ['Sato:2:1' , 2] , ['Kato:5:2' , 5] .... )

@id_data の中に、このように、「行データ」と「切り出したID」を含む配列のリファレンスを、一つのデータセットにして入れていくことになります

説明不足だった点

[ ] は、リストのリファレンスではなく、配列のリファレンスです。正確には「無名の配列」と呼ばれます(もっと正確には、無名配列コンストラクタ)。

my @arr = (10,20,30);
my $aref = \@arr;

配列を作ってリファレンスをとる、というのを、いきなり

my $aref = [10,20,30];

と書けるようにしてある、というところから、「無名配列」という語感を掴んでいただければと思います。[10,20,30]は、「(10,20,30)という値を含む配列へのリファレンス」ということになりますが、ここで言っている配列には、名前を与えていませんね。

メモ

2010-07-13

Perlわかば (ソート結果のキャッシュ)

#!perl
use strict;
use utf8;

# parse passwd-like textfile and sort lines

my @DATA = map{chomp;$_}<DATA>; # remove CRLF and store in ARRAY

# sort via field 1

# 「行データを : で分割したリストのうち、[1]番目の要素を比較して行をソート」

#my @sorted_data = sort{(split /:/, $a)[1] <=> (split /:/, $b)[1]} @DATA;
my @sorted_data = sort compare_field_2 @DATA;
print map{$_ . "\n"} @sorted_data;
printf ("total:%d times\n" , counter());

# ================ subs
# キャッシュを用いる場合、用いない場合
# (行切り出しは、別関数 get_item() を呼び出し)
sub compare_field_1{
  #(split /:/, $a)[1] <=> (split /:/, $b)[1];
  get_item($a) <=> get_item($b)
}


{my %cache;
sub compare_field_2{
  ($cache{$a} ||= get_item($a)) <=> ($cache{$b} ||= get_item($b));
}
}

# ================ subs (parse)
# 行切り出し

{
  my $counter = 0;
  sub get_item{
    $counter++; # 回数をカウント
    my $line = shift;
    return ((split /:/, $line)[1]);
  }
  sub counter{
    $counter = $_ || $counter;
    return $counter;
  }
}

__DATA__
Yamada:10:1
Sato:2:1
Kato:5:2
Furukawa:1:1
Suzuki:4:2
Ito:7:3
Yamaoka:3:3
Masuno:6:1
Mizuhara:8:3
Akatsuka:9:1

メモ

or/||を使った評価

$foobar ||= 100;

# $foobarが真 → $foobar;
# $fooberが偽 → $foobar = 100;

orのキャッシュを使ったソート

@sorted = sort {($cache{$a} ||= -M $a) <=> ($cache{$b} ||= -M $b)} @files;

# (....) <=> (...) で、通常のsortブロックの評価
# カッコ内の値の評価
# $cache{$a} が真(値が既にある)  → ($cache{$a})
# $cache{$b} が偽(初めて出てきた)→ ($cache{$a} = -M $a)

#   代入式は代入された値をそのまま返すので、
#   カッコの中からは結局 -M $a した値(か、そのキャッシュ)が返る

# 機能としては以下と全く同じ
@sorted = sort {-M $a <=> -M $b} @files;

todo

  • こんなことやらずに一次変数を使ったほうがいいんじゃない?→手を動かしてやってみる
  • Schwartzian transform