世界線航跡蔵

Mad web programmerのYuguiが技術ネタや日々のあれこれをお送りします。

2015年03月22日

言語処理100本ノックを敢えてRubyで (2)

10. 行数のカウント

p $<.count

確認

wc hightemp.txt

$<の仕様はまさにこういう処理を書くために考えられている。 が、今回に関しては普通ならわざわざ書かずにwcを使う。

11. タブをスペースに置換

$<.each_line do |line|
  puts line.gsub("\t", ' ')
end

確認

sed "s/^I/ /g" hightemp.txt

12. 1列目をcol1.txtに,2列目をcol2.txtに保存

File.open("col1.txt", "w") {|f1|
  File.open("col2.txt", "w") {|f2|
    $<.each_line do |line|
      cols = line.split("\t")
      f1.puts cols[0]
      f2.puts cols[1]
    end
  }
}

確認

cut -f1 hightemp.txt
cut -f2 hightemp.txt

普通で面白くない。 今回はデータが小さいことが分かっているので、オンメモリに構築してからFile::writeで書いた方がインデントは少なくなって綺麗だろう。 複数のファイルを同じ寿命で開くことはままあるので、複数まとめて開いてまとめて閉じるラッパーメソッドを書いたことはある。

こうしてみるとgolangのdeferがちょとうらやましいなぁ。Object.instance_evalで似たようなのを構築するのは易しいが、selfがすり替わってしまうし完全ではない。

13. col1.txtとcol2.txtをマージ

File.open("col1.txt") {|f1|
  File.open("col2.txt") {|f2|
    f1.zip(f2).each do |col1, col2|
      puts "#{col1.chomp}\t#{col2}"
    end
  }
}

性能的に見るとEnumerable.zipArrayを返すのでメモリを食ってよろしくない。今回はデータが小さいから良いものの、lazyを使っても全般的にRubyはこの手の処理が苦手である。 この辺の失敗体験がStreemにつながっているんだろうか。

paste col1.txt col2.txt

14. 先頭からN行を出力

n = ARGV.shift.to_i
$<.each_with_index do |line, i|
  break if i >= n
  puts line
end

別解。こちらのほうが今風だ。

n = ARGV.shift.to_i
$<.lazy.take(n).each{|line| puts line }

確認

head -10 hightemp.txt

15. 末尾のN行を出力

class Ring
  include Enumerable
  ABSENT = Object.new.freeze
  def initialize(n)
    @buf = Array.new(n, ABSENT)
    @pos = 0
  end

  def add(item)
    @buf[@pos] = item
    @pos += 1
    @pos %= @buf.size
  end

  def each
    (0...@buf.size).each do |i|
      item = @buf[(i + @pos) % @buf.size]
      next if item == ABSENT
      yield item
    end
  end
end

n = ARGV.shift.to_i
ring = Ring.new(n)
$<.each(&ring.method(:add))
ring.each do |line|
  puts line
end

まじめに後方からバッファリングしつつEOLを探索すると面倒いので先頭からなめた。 tail(1)の実装は結構うまくできていて、まねしようとするとそれなりに難しい。

あと、&obj.method(:mid)は見慣れない割には簡潔でないので実務では避けるべき。

Ringもあまりまじめな実装ではない。

そういえば、Ring bufferが標準ライブラリに入っていて気軽に使えると割と便利なことがgolangの経験で分かった。 しかし、今からコンテナをRubyの標準添付ライブラリに足す線はなかなか無いだろうなぁ。

tail -10 hightemp.txt

16. ファイルをN分割する

n = ARGV.shift.to_i
lines = $<.to_a
per_chunk = if lines.size % n == 0
              lines.size / n
            else
              lines.size / n + 1
            end

suffix = 'aa'
lines.each_slice(per_chunk) do |chunk|
  File.open("x#{suffix}", "w") {|f|
    chunk.each do |line|
      f.puts line
    end
  }
  suffix.succ!
end

出力ファイル名はsplit(1)のデフォルトに合わせた。

split(1)で実現せよ」という設問については、BSDのsplitには-nが無いから難しいんだが。

gsplit -n 5 hightemp.txt

17. 1列目の文字列の異なり

puts $<.map {|line| line.split("\t")[0] }.uniq

確認

cut -f1 hightemp.txt | sort | uniq

18. 各行を3コラム目の数値の降順にソート

$<.map {|line| line.split("\t") }.sort_by {|_, _, temp, _|
  -temp.to_f
}.each do |data|
  puts data.join("\t")
end

確認

sort -rnk3 hightemp.txt

19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる

freq = Hash.new(0)
$<.map {|line|
  pref, *rest = line.split("\t")
  freq[pref] += 1
  [pref, *rest]
}.sort_by {|pref,| [-freq[pref], pref] }.each do |data|
  puts data.join("\t")
end

同じ出現頻度が多くて分かりづらい。あと、都道府県でグルーピングもしてみた。

shellコマンドでの確認指示については、こういう意図かなぁ?

cut -f1 hightemp.txt | sort | uniq -c | sort -rnk1 -t' '

トラックバック

http://yugui.jp/articles/886/ping

現在のところトラックバックはありません

コメント

blog comments powered by Disqus

ご案内

前の記事
次の記事

タグ一覧

過去ログ

  1. 2016年07月
  2. 2016年01月
  3. 2015年09月
  4. 2015年08月
  5. 過去ログ一覧

フィード

フィードとは

その他

Powered by "rhianolethe" the blog system