単語リストプログラム その12
ようやく、メインの単語リスト作成まで到達。基本のスクリプトは、単語頻度を数えるにあるものを使うんだけなので、たいしたことはしない。前回 awakeFromNib に加えた ArrayController にデータを追加する2行は消しておく。
まず、次のようなメソッドを定義して、ib_action にして、Interface Builder でこの Action を2つの Start ボタンに割り当てる(App Controller の Received Actions に出てくるので、それを結びつける)。
def createWordList(sender)
p sender.alternateTitle.to_i
end
ib_action :createWordList
Tag を使った場合は、
p sender.tag.to_i
実行すると、左の Start ボタンをクリックすると 0 が、右のボタンだと 1 がコンソールに表示されるはず。ちゃんと表示されれば、ボタンがちゃんとこのメソッドとつながったことがわかる。
それでは、このメソッドの中に、単語リスト作成の処理を書いていく。
参考までに、Ruby で書いた単語リスト作成のスクリプトのメイン部分をコピーしてきた。
words = Hash.new(0)
Find.find(directory) do |path|
if /\.txt/i =~ File.extname(path)
File.open(path,"r") do |file|
file.read.downcase.split(/\W+/).each do |word|
words[word] += 1
end
end
end
end
これでは、Find.find(directory) でパスを読み込んで、テキストファイルかどうかを確認して、という作業をしているが、このサンプルでは、もうファイルパスが File Array Controller にあるはずなので、それを読み込む。
@fileAryCtl に File Array Controller が配列として入っていて、0 なら左側のテーブル用、1 なら右側のテーブル用の Array Controller になるので、上の sender.alternateTitle.to_i (sender.tag.to_i)を aryID に入れて処理していく。@fileAryCtl[aryID] に対して arrangedObjects で要素を配列として取り出して、ブロックで処理して、fullPath に入っているファイルのパスを表示させてみる。これもコンソールに表示される。
aryID = sender.alternateTitle.to_i # Tag を使う場合は aryID = sender.tag.to_i
@fileAryCtl[aryID].arrangedObjects.each do |file|
p file["fullPath"]
end
このファイルパスを使って、ファイルの中身のテキストを読み込む。これには単語リストプログラム その8で定義しておいた readTextFromFile(filePath,encode) を使う。このメソッドの返り値はファイルの中身のテキストで、NSString になっている。これを to_s で Ruby の String オブジェクトに変換してから上の処理をする。encode には File Array Controller の encoding に入っている情報を使う。
words = Hash.new(0)
aryID = sender.alternateTitle.to_i # Tag を使う場合は aryID = sender.tag.to_i
@fileAryCtl[aryID].arrangedObjects.each do |file|
text = readTextFromFile(file["fullPath"],file["encoding"]).to_s
text.downcase.split(/\W+/).each do |word|
words[word] += 1
end
end
あと、Token と Type を表示させるので、それもここで数える。Token は述べ単語数なので、token という変数を作り、words[word] += 1 と同じところで1ずつ増やしていく。Type は単語の種類の下図なので、words の長さを求めればいい。
words = Hash.new(0)
aryID = sender.alternateTitle.to_i # Tag を使う場合は aryID = sender.tag.to_itoken = 0
@fileAryCtl[aryID].arrangedObjects.each do |file|
text = readTextFromFile(file["fullPath"],file["encoding"]).to_s
text.downcase.split(/\W+/).each do |word|
words[word] += 1
token += 1
end
end
type = words.length
これで、words というハッシュに単語を key として、頻度が入っている。これを単語リストを表示するための WC Array に入れていく。
まず、WC Array Controller も File Array Controller と同じように、aryID でどちらのテーブルの Array Controller かを区別できるように、awakeFromNib で @wcAryCtl というインスタンス変数を作っておく。
def awakeFromNib
@wcAryCtl = [@wcAryCtl1, @wcAryCtl2]
end
そして、wordList という配列を用意しておいて、words をブロックでして、word と freq に値を入れていく。最後にこの配列を addObjects で Array Controller に加える。
wordList = Array.new
words.each do |key,value|
wordList << {"word" => key, "freq" => value}
end
@wcAryCtl[aryID].addObjects(wordList)
ただ、単に加えると問題が起きるので、このメソッドが呼ばれたらすぐに WC Array Controller を空にして、最後に並べ替え処理を加える。検索窓を使ったことですべての単語が表示されていない場合があるので、setFilterPredicate(nil) で全部を表示させてから removeObjectsAtArrangedObjectIndexes(indexSet) で arrangedObjects の長さ分、つまり全部を指定して削除する。
@wcAryCtl[aryID].setFilterPredicate(nil)
@wcAryCtl[aryID].removeObjectsAtArrangedObjectIndexes(NSIndexSet.indexSetWithIndexesInRange([0,@wcAryCtl[aryID].arrangedObjects.length]))
並べ替え処理は、NSSortDescriptors を使う。まず、NSSortDescriptor オブジェクトの配列を作り、それを setSortDescriptors で Array Controller で割当て、rearrangeObjects で並べ替えている。頻度で並べ替える場合は、大きい方から並べたいので、"freq" の ascending は false にする。次に、同じ頻度の場合は、単語をアルファベット順にするために、"word" を ascending を true にして加える。
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("freq",false),NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
@wcAryCtl[aryID].setSortDescriptors(descriptors)
@wcAryCtl[aryID].rearrangeObjects
ただ、並べ替えは、Frequency と Alphabetical で選べるようにしてあるので、それを組み込む。そのためには、Pop Up Button の情報を得る訳だけど、これも2つあるので、Array Controller と同じように、aryID で区別できるように、@sortChoice というインスタンス変数を作る。
def awakeFromNib
@sortChoice = [@sortChoice1, @sortChoice2]
end
Alphabetical の並べ替えのときは、上の場合の2つ目の NSSortDescriptor オブジェクトだけでいいので、@sortChoice[aryID] の選ばれている項目のインデックスを得て、それで判断して処理を変える。
case @sortChoice[aryID].indexOfSelectedItem
when 0
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("freq",false),NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
when 1
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
end
@wcAryCtl[aryID].setSortDescriptors(descriptors)
@wcAryCtl[aryID].rearrangeObjects
最後に、order に値を入れる。これは、上から数えて何番目、というものにとりあえずするということで、Array Controller に入っている配列のインデックスに1を加えたものを入れていく。
まず、arrangedObjects で配列として取り出して、each_with_index で処理していく。
@wcAryCtl[aryID].arrangedObjects.each_with_index do |item,idx|
item["order"] = idx + 1
end
この並べ替えの処理は、Sort ボタンをクリックしたときにも Array Controller に対して行うので、単語リストプログラム その9でもやったように、NSArrayController クラスにメソッドとして加えることにする。その際に、並べ替えを選択する Pop Up Button がどちらのものかがわかるように、それを指定するようにする。
class OSX::NSArrayController
include OSX
def sortWordList(sortChoice)
case sortChoice.indexOfSelectedItem
when 0
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("freq",false),NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
when 1
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
end
self.setSortDescriptors(descriptors)
self.rearrangeObjects
self.arrangedObjects.each_with_index do |item,idx|
item["order"] = idx + 1
end
end
end
この処理をするために、 createWordList のメソッドの最後に次の一行を加える。
@wcAryCtl[aryID].sortWordList(@sortChoice[aryID])
Sort ボタンをクリックしたときにも並べ替えができるようにしたいので、resortWordList というメソッドを作り、これを ib_action にして、Sort の Action にする。Sort ボタンにも Alt. Title (もしくは tag)が割り当ててあるので、それを使って、sortWordList を実行する。
def resortWordList(sender)
@wcAryCtl[sender.alternateTitle.to_i].sortWordList(@sortChoice[sender.alternateTitle.to_i])
end
ib_action :resortWordList
Tag の場合は、
@wcAryCtl[sender.tag.to_i].sortWordList(@sortChoice[sender.tag.to_i])
最後に、type と token を表示させる処理を加える。Token と Type を表示する TextField も2つあるので、それぞれ @tokenField、@typeField というインスタンス変数を awakeFromNib でつくる。
def awakeFromNib
@tokenField = [@tokenField1, @tokenField2]
@typeField = [@typeField1, @typeField2]
end
そして、setIntegerValue で表示させる。
@tokenField[aryID].setIntegerValue(token)
@typeField[aryID].setIntegerValue(type)
今回の部分のスクリプトを最後にまとめる。
def awakeFromNib
@wcAryCtl = [@wcAryCtl1, @wcAryCtl2]
@sortChoice = [@sortChoice1, @sortChoice2]
@tokenField = [@tokenField1, @tokenField2]
@typeField = [@typeField1, @typeField2]
end
def createWordList(sender)
words = Hash.new(0)
aryID = sender.alternateTitle.to_i # Tag の場合は、aryID = sender.alternateTitle.to_i
@wcAryCtl[aryID].setFilterPredicate(nil)
@wcAryCtl[aryID].removeObjectsAtArrangedObjectIndexes(NSIndexSet.indexSetWithIndexesInRange([0,@wcAryCtl[aryID].arrangedObjects.length]))
token = 0
@fileAryCtl[aryID].arrangedObjects.each do |file|
text = readTextFromFile(file["fullPath"],file["encoding"]).to_s
text.downcase.split(/\W+/).each do |word|
words[word] += 1
token += 1
end
end
type = words.length
wordList = Array.new
words.each do |key,value|
wordList << {"word" => key, "freq" => value}
end
@wcAryCtl[aryID].addObjects(wordList)
@wcAryCtl[aryID].sortWordList(@sortChoice[aryID])
@tokenField[aryID].setIntegerValue(token)
@typeField[aryID].setIntegerValue(type)
end
ib_action :createWordList
def resortWordList(sender)
@wcAryCtl[sender.alternateTitle.to_i].sortWordList(@sortChoice[sender.alternateTitle.to_i])
# Tag の場合は、@wcAryCtl[sender.tag.to_i].sortWordList(@sortChoice[sender.tag.to_i])
end
ib_action :resortWordLis
これに加えて、NSArrayController に sortWordList というメソッドを加える。
class OSX::NSArrayController
include OSX
def sortWordList(sortChoice)
case sortChoice.indexOfSelectedItem
when 0
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("freq",false),NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
when 1
descriptors = [NSSortDescriptor.alloc.initWithKey_ascending("word",true)]
end
self.setSortDescriptors(descriptors)
self.rearrangeObjects
self.arrangedObjects.each_with_index do |item,idx|
item["order"] = idx + 1
end
end
end
これで、とりあえず、単語リストを作るアプリケーションの基本部分が出来上がった。メニューバーの整理とかあるけど、その辺りは気が向いたら。次回にはここまでのスクリプトをまとめておいて、その続きはおまけの機能の追加ということにする。
単語リストプログラム その13 - ここまでのスクリプトのまとめ