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.zip
は Array
を返すのでメモリを食ってよろしくない。今回はデータが小さいから良いものの、 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' '