Rubyでの実装
以下がRubyでの実装になります。
require 'rexml/document' require 'set' class WordCount # read-only attributes attr_reader :word, :total_count def initialize(word) @word = word @total_count = 0 end # Use a mixin of Comparable module to get comparisons for free # we must define <=> include Comparable def <=>(other) return @total_count <=> other.total_count end def add(filename) @total_count += 1 end def file_occurrences return [] end end class WordAndFileCount < WordCount # read-only attributes attr_reader :file_hash def initialize(word) super(word) @file_hash = Hash.new end def add(filename) @total_count += 1 v = @file_hash[filename] @file_hash[filename] = (v == nil) ? 1 : v + 1 end def file_occurrences return @file_hash.sort { |x,y| ?(x[1]<=>y[1]) } end end class WordCounter def initialize(directory_name) @directory_name = directory_name @word_count = Hash.new end def count_words pwd = Dir.pwd Dir.chdir @directory_name Dir.foreach(".") { |filename| count_words_in_file filename } Dir.chdir pwd end def dump_results root = REXML::Element.new "counts" @word_count.values.sort.reverse.each { |wc| e = root.add_element "word", {"occurences"=>"#{wc.total_count}"} e.add_text wc.word wc.file_occurrences.each { |pair| f = e.add_element "file", {"occurences"=>"#{pair[1]}"} f.add_text pair[0] } } doc = REXML::Document.new doc << REXML::XMLDecl.new doc.add_element root doc.write $stdout end private def count_words_in_file(filename) return if File.directory? filename File.open(filename) { |file| file.each_line { |line| words = line.split(/[^a-zA-Z]/) words.each { |w| next if w.size == 0 @word_count[w] = $count_gen.call(w) if @word_count[w] == nil @word_count[w].add filename } } } end end if ARGV.include?("--no-file-info") then ARGV.delete("--no-file-info") $count_gen = lambda { |word| WordCount.new word } else $count_gen = lambda { |word| WordAndFileCount.new word } end counter = WordCounter.new ARGV[0] counter.count_words counter.dump_results
クラスと変数の基本
両バージョンとも、次の3つのクラスを定義します。
- 全ファイルでのワードの出現総数を表すクラス(word_count/WordCount)
- 上のクラスから派生し、ファイルごとの各ワードの出現を保持する拡張クラス(word_and_file_count/WordAndFileCount)
- ファイルの読み込みとパース、ワードカウントの作成と更新、およびXMLファイルの出力を行うカウンタクラス(word_counter/WordCounter)
1つ目のクラスは、リスト1では22行目、リスト2では4行目で定義されています。両実装とも、文字列wordとtotal_count変数を保持しています。Rubyでは、インスタンス変数の先頭には「@」が付くため、リスト2には、@wordと@total_countがあります。ローカル変数にはプレフィックスは付きません。一方、グローバル変数には「$」というプレフィックスが付きます。
C++コードでは、structを使用してこのクラスを宣言します。したがって、word変数とtotal_count変数は既定ではパブリックです。しかし、Rubyでは、オブジェクトの外部からはインスタンス変数にアクセスできず、すべての変数がプライベートになっています。アクセスコントロールについては後でもっと詳しく解説しますが、ここでは、必要なアクセッサメソッドを追加することに焦点をあてて説明します。リスト1の7行目のステートメントを見るとわかりますが、このようなアクセッサメソッドは簡単に追加できます。attr_readerの後に変数をリストすれば、必要なgetアクセッサメソッドを自動的に生成できます。
また、このクラスではどちらの実装でも、ワード文字列を取得するコンストラクタと、カウンタをインクリメントするaddメソッド、さらにファイルごとの情報を保持するデータ構造を返すfile_occurrencesメソッドを定義します。リスト2の9行目で示すように、Rubyのクラスコンストラクタには、initializeという名前が付いています。
Rubyコード中のinclude Comparableの部分を除けば、この基本クラスの実装はどちらの言語でもかなりわかりやすいものになっています。
