Pythonコード添削道場にRubyで乱入w

Pythonコード添削道場 コード募集 とかいうおもしろいことが始まったので、Ruby で直接乱入……はちょっと気が引けるから、自分のblogに書き散らかしてみよう。

お題1:ファイルの同期

二つのディレクトリ下のタイムスタンプを比較して、同期を取るプログラムを作りたい。
二つのディレクトリ(フォルダ)を指定すると、そのディレクトリの下の全てのファイルについて「片方にしかないファイルはもう片方へコピーし、両方にあるけどもタイムスタンプ(更新時刻)の異なるファイルは新しい方で古い方を上書きする」という処理を行うプログラムを作りなさい。

両方見るというのがちょっと面倒。
全然動かしてみていないので、bugってるかも(笑)。って動かせよっ>俺

require 'fileutils'
def synccopy(dir1, dir2)
  hash={}
  dirs=[]
  Dir.foreach(dir1) do |file|
    next if file =~ /^\.{1,2}$/
    path = "#{dir1}/#{file}"
    hash[file] = FileTest.directory?(path) ? :directory : File.mtime(path)
  end
  Dir.foreach(dir2) do |file|
    next if file =~ /^\.{1,2}$/
    path = "#{dir2}/#{file}"
    if FileTest.directory?(path)
      dir = hash.delete(file)
      raise "can't synchronize between file '#{dir1}/#{file}' and directory '#{path}' " if dir.instanceof?(Time)
      Dir.mkdir("#{dir1}/#{file}") unless dir == :directory
      dirs << file
    else
      mtime = hash.delete(file)
      raise "can't synchronize between directory '#{dir1}/#{file}' and file '#{path}'" if mtime == :directory
      if mtime && mtime >= File.mtime(path)
        FileUtils.copy_file "#{dir1}/#{file}", path, true, false if mtime > File.mtime(path)
      else
        FileUtils.copy_file path, "#{dir1}/#{file}", true, false
      end
    end
  end
  hash.each do |file, info|
    if info == :directory
      Dir.mkdir "#{dir2}/#{file}"
    else
      FileUtils.copy_file "#{dir1}/#{file}", "#{dir2}/#{file}", true, false
    end
  end
  dirs.each do |dir|
    synccopy "#{dir1}/#{dir}", "#{dir2}/#{dir}"
  end
end

if ARGV.length>=2
  synccopy ARGV[0], ARGV[1]
else
  puts "usage:#{$0} [source directory] [destination directory]"
end

同じ相対パスでかたやディレクトリ、かたやファイルのときは例外を投げるようにしてみた。

お題2:単語数カウント

英文のテキストファイルを読み込んで単語の出現数を数えるプログラムを作れ。
(中略)
余力があれば出現頻度の多い順に出力するプログラムも書け。

標準入力に与えられたテキスト内の語数をカウント。これでちゃんと出現頻度の多い順で出力している。

STDIN.read.scan(/[A-Za-z']+/).inject(Hash.new(0)){|h,w|h[w.downcase]+=1;h}.to_a.sort{|a,b|b[1]<=>a[1]}.each{|a|puts"#{a[0]}: #{a[1]}"}

こういう書き方ができる Ruby が好きw。
メソッド呼び出しを連ねてプチワンライナーになっちゃってるが、特別な書き方という意識は実のところあまりない。
特に、Hash に対して 「 .to_a.sort{|a,b|b[1]<=>a[1]} 」とすれば Hash の値が多い順にソートできるのは Ruby では普通に結構よく使うと思うので。

お題3:シングルトン

Pythonでシングルトンを作れ。

うーん、スレッドセーフを基本意識しない言語でのシングルトンに益があるのかという問いはともかく、Javaっぽく実装してみた。
Ruby にはいわゆる「コンストラクタ」はないので、そこらへんは一工夫。

class Foo
  @@instance = Foo.new
  def self.getInstance; @@instance end
  def initialize
    raise "already instancificated." if @@instance
  end

  attr_accessor :bar
end

f = Foo::getInstance
f.bar = 3
puts f.bar  # => 3
g = Foo::getInstance
puts g.bar  # => 3
h = Foo.new # => error

お題4:入れ子リストの中身を順に表示

整数かリストが入っているような入れ子になったリストを考える。
(例:[1, [2, 3, 4], 5, [[[6], [7, 8], 9], 10])

  • 入れ子リストが与えられたときに中身を順に表示するような関数を作れ。
  • 与えられたオブジェクトが「整数とリストだけでできている」かどうかをチェックしてTrueかFalseを返す関数を作れ。

文字列の状態で '[' と ']' を削れば……と思ったが、その案は先客がいるらしいので、まじめにふつうに。

def extend_list(list)
  list.inject([]){|x,y|x+(y.instance_of?(Array)?extendlist(y):[y])}
end
def check_list(list)
  list.all?{|x|x.instance_of?(Array)?check_list(x):x.is_a?(Numeric)}
end
list=eval(STDIN.read)
puts extend_list(list).join("\n")
puts check_list(list)

書いてから http://www.nishiohirokazu.org/pwe2007/2007/06/post_3.html のコメント見たら、ほぼ Ruby-Python 逐語訳なコードがすでにあってガックリ。もっとおもしろい書き方したいけど、思いつかんなあ。

お題5:行列の回転

  • 行列(リストのリスト)が与えられたときに、それを90度時計回りに回転した行列を返す下のような関数を作れ。

(略)

  • 与えられた引数が長方形の行列であるか(リストの中のそれぞれのリストが全て同じ長さで、入っているのが数だけであるか)をチェックする関数を作れ。

zip を使えば簡単そうだが、それだとなんかおもしろみが足りないので。

def rotate(m)
  (0..m[0].length-1).inject([]){|r,i|r<<m.map{|row|row[i]}.reverse}
end
def check(m)
  m.map{|x|x.length}.uniq.length==1
end

check は、さりげにちょっぴり変なコードでお気に入りw。

お題6:名簿の並び替え

下のようなローマ字表記された名簿データがあるとする。実際は3人ではなく1万人分あるとする。ミドルネームなどはなく、名字と名前の間は半角スペース1個で区切られている。
(略)

  • 上のデータファイルからH. Nishioというイニシャル表記の名簿ファイルを作成せよ。
  • イニシャル表記で、名字でABC順にソートされた名簿ファイルを作れ。名字が同じ場合は名前のABC順になること。
puts STDIN.readlines.map{|s|"#{$2}. #{$1}" if s=~/^([A-Za-z]+)\s+([A-Z])[a-z]*$/}.join("\n")

Ruby書きが10人いたら10人ともこうなるんちゃう? という普通なコードになってしまった……

お題7:整数とビット列の相互変換

整数とビット列の間を相互変換できる **モジュール** を作れ。クラスでも、関数の集合でもかまわない。

問題文がよくわからなかった。「ビット型」なり「バイナリ型」なりのある処理系であればそこらへんかなと思うこともできるのだが、Rubyはともかく、Pythonにもそういうのは無いし……Boolean の配列? まさかねえ。
というわけで作って楽しそうな「10進表記の数値文字列と2進表記の数値文字列を相互変換するモジュール」をプリミティブに実装してみるという超拡大解釈をすることにした。昔、アセンブラでコーディングしてた頃はよくそういうのを書いたなあ。

module AdicConverter
  def self.decode(st, adic)
    (0..st.length-1).inject(0){|num,i|num*adic+(st[i]-0x30)}
  end
  def self.encode(num, adic)
    st = ""
    begin
      st.insert(0, (num % adic + 0x30).chr)
      num /= adic
    end while num>0
    st
  end
end

puts AdicConverter::encode( AdicConverter::decode("11011111", 2), 10) # 2-adic => 10-adic
puts AdicConverter::encode( AdicConverter::decode("12345678", 10), 2) # 10-adic => 2-adic

値の範囲チェックは手抜き。