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
値の範囲チェックは手抜き。