StarDictの辞書アプリ

StarDictの辞書ファイル形式で調べたファイル形式に基づいて、簡単なコンソール辞書アプリを作ってみた。

実行結果はこんな感じ。なんか変な辞書だな、これ。

search: あい
あい
=> [アイ~あ行~]ใอ ไอ / คราม / ความชอบพอ, ความรัก, พิสมัย, ศฤงคาร, เสนหา, เสน่หา

search: あいし
あいしあう
あいしあえないでしょう?
あいしいゆうちりょうしつ
あいしているのにくちにだしていえない
あいしてる
あいしてるっていわなくてもいいわ
あいしてるとくりかえしていう
あいしてるよ、ちゅっちゅっ
あいしゃどう
あいしゃどうをぬる
あいしょう
あいしょうがいい
あいしょうがわるい
あいしんびいなす

search: あいして
あいしているのにくちにだしていえない
=> [愛しているのに口に出していえない。]ไม่กล้าพูดว่าผมรักคุณทั้งๆ ที่รัก
あいしてる
=> [愛してる]มีใจ
あいしてるっていわなくてもいいわ
=> [愛してるって言わなくてもいいわ。]ไม่ต้องมาพูดว่ารัก
あいしてるとくりかえしていう
=> [愛してると繰り返して言う]ย้ำถึงคำว่ารัก
あいしてるよ、ちゅっちゅっ
=> [愛してるよ、チュッチュッ。]รักกันจุ๊บ ๆ


ソースはこう。
工夫したのはインデックスのインデックスを最初に持っておくところくらいか。
インデックスの段階を増やせば、メモリの少ない環境でも動くようにできるはず。

def dictionary = new Dictionary()

def input = new BufferedReader(new InputStreamReader(System.in))
while (true) {
	print "search: "
	key = input.readLine()
	def resultList = dictionary.search(key)
	for (idx in resultList) {
		println idx.word
		if (resultList.size() <= 10) {
			println "=> " + dictionary.getDefinition(idx)
		}
	}
	println ""
}

class Dictionary {
	def indexSize = 3
	def indexes
	def Dictionary() {
		indexes = loadIndex(indexSize)
	}
	
	def search(originalKey) {
		def key = originalKey
		if (originalKey.length() > indexSize) key = originalKey.substring(0, indexSize)
		def start = searchStart(indexes, key, 0, indexes.size()-1)
		def end = searchEnd(indexes, key, 0, indexes.size()-1)
		
		def length = 0
		def count = 0
		for (idx in indexes.subList(start, end+1)) {
			length += idx.indexLength
			count += idx.count
		}
		def completeIndexes = loadIndex(-1, indexes.get(start).indexOffset, length)
		if (completeIndexes.size() > 0 && completeIndexes.get(0).word == originalKey) return [completeIndexes.get(0)]
		
		return completeIndexes.subList(
			searchStart(completeIndexes, originalKey, 0, completeIndexes.size()-1), 
			searchEnd(completeIndexes, originalKey, 0, completeIndexes.size()-1)+1)
	}
	
	def searchStart(list, key, start, end) {
		if (start == end || end < 0) return start
		def i = (int)Math.floor((start+end)/2)
/*		println "start:${start}, end:${end}, i:${i}"*/
		def wd = list.get(i).word
		if (key.length() < wd.length()) wd = wd.substring(0, key.length())
		def c = wd.compareTo(key)
/*		println "start:${start}, end:${end}, i:${i}, wd:${wd}, key:${key}, c:${c}, wd:${wd.getBytes()}, key:${key.getBytes()}"*/
		if (c >= 0) {
			return searchStart(list, key, start, i)
		} else {
			if ((end-start)==1) i = end
			return searchStart(list, key, i, end)
		}
	}
	def searchEnd(list, key, start, end) {
		// 見つからない場合はsearchStartの結果-1が返る。
		if (start == end || end < 0) return end
		def i = (int)Math.ceil((start+end)/2)
		if (i>end) i = end
		def wd = list.get(i).word
		if (key.length() < wd.length()) wd = wd.substring(0, key.length())
		def c = wd.compareTo(key)
/*		println "start:${start}, end:${end}, i:${i}, wd:${wd}, key:${key}, c:${c}"*/
		if (c <= 0) {
			return searchEnd(list, key, i, end)
		} else {
			if ((end-start)==1) i = start
			return searchEnd(list, key, start, i)
		}
	}
	
	def parseInt(byte b1, byte b2, byte b3, byte b4) {
		return (b1 & 0xff) * 256*256*256 + (b2 & 0xff) * 256*256 + (b3 & 0xff) * 256 + (b4 & 0xff)
	}

	def loadIndex(indexSize = 3, indexOffset = 0, indexLength = -1) {
/*		println "read ${indexOffset} - ${indexOffset + indexLength}"*/
		if (indexOffset < 0) indexOffset = 0
		def indexFile = new File("test.idx")
		if (indexLength == -1) indexLength = indexFile.length()
		def buf = new byte[indexLength]
		def fileInputStream = new BufferedInputStream(new FileInputStream(indexFile))
		fileInputStream.skip(indexOffset)
		fileInputStream.read(buf)
		fileInputStream.close()

		def i=0
		int offset = 0
		def indexes = new ArrayList();
		def wd = ""
		def prev = ""
		while (i<buf.length) {
			if (buf[i] == 0) {
				wd = new String(buf, offset, i-offset, "UTF-8")
				if (indexSize > 0) {
					wd = wd.substring(0, indexSize>wd.length() ? wd.length() : indexSize)
				}
				if (prev != wd) {
					indexes.add(new Index(
						word:wd
						, offset:parseInt(buf[i+1], buf[i+2], buf[i+3], buf[i+4])
						, length:parseInt(buf[i+5], buf[i+6], buf[i+7], buf[i+8])
						, indexOffset:offset
						, indexLength:i-offset+9))
				} else {
					def index = indexes.get(indexes.size()-1)
					index.addCount()
					index.addIndexLength(i-offset+9)
				}
				prev = wd
				offset = i+9
				i = i+8
			}

			i++
		}
		return indexes
	}
	
	def getDefinition(index) {
		def defIn = new FileInputStream("test.dict")
		def buf = new byte[index.length]
		defIn.skip(index.offset)
		defIn.read(buf)
		defIn.close()
		return new String(buf, "UTF-8")
	}
}
class Index {
	def word
	def offset
	def length
	def indexOffset
	def indexLength
	def count = 1

	def addCount() {count++}
	def addIndexLength(l) {indexLength += l}
	
	String toString() {
		return "[word: ${word},\toffset:${offset},\tlength:${length},\tindexOffset:${indexOffset},\tindexLength:${indexLength}\tcount:${count}]"
	}
}