メインコンテンツまでスキップ

「String」タグの記事が14件件あります

全てのタグを見る

人工無脳を作ってみる (1)入力文の末尾に文字列を追加

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

さて、最初は単純にユーザの入力文の末尾に予め定義されている文字列を付け足して応答するだけのものです。

ソースコード

package info.yukun.chatterbot;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class SimpleChatterBot {
private String botName = "chatterbot";
private String callStr = "なにかしゃべってよ(>_<)";
private String[] suffixResponses = {
"ってアレですね、わかります(^^)b",
"ですか?わかりません(>_<)",
"って何?日本語でおk('`)b"};
public void go() {
try {
BufferedReader stdReader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("INPUT : ");
String response;
String input;
while ((input = stdReader.readLine()) != null) { // ユーザの入力待ち
if (input.equals("")) { // 入力が無かった
response = callStr;
} else {
response = decorateInput(input) + getSuffixResponse();
}
System.out.print(botName + ": " + response);
System.out.print("\nINPUT : ");
}
stdReader.close();
System.out.println("\nPROGRAM END");
} catch (Exception e) {
e.getStackTrace();
System.exit(-1); // プログラムを終了
}
}
private String getSuffixResponse() {
int rand = (int) (Math.random() * suffixResponses.length);
return suffixResponses[rand];
}
private String decorateInput(String input) {
return "「" + input + "」";
}
public static void main(String[] args) {
SimpleChatterBot bot = new SimpleChatterBot();
bot.go();
}
}

getSuffixResponse()メソッド内で使われているMath.random()は、

static double random() 0.0 以上で、1.0 より小さい正の符号の付いた double 値を返します。

ですので、「Math.random() * 配列の長さ」 は「0~配列の長さ-1」 を意味します。

実行結果の例

INPUT : こんにちは
chatterbot: 「こんにちは」って何?日本語でおk('`)b
INPUT : え、
chatterbot: 「え、」ですか?わかりません(>_<)
INPUT : いえ、聞き返しただけです。
chatterbot: 「いえ、聞き返しただけです。」って何?日本語でおk('`)b
INPUT :
chatterbot: なにかしゃべってよ(>_<)
INPUT : 最近どうですか?
chatterbot: 「最近どうですか?」ってアレですね、わかります(^^)b
INPUT :
PROGRAM END

なんだかー、それはかとなくばかにしていますねf^^; 最初はJavaで書いていきますが、実装する機能の種類やリリースする環境によっては恐らく途中でOOをサポートする他の言語に変更するかも。

Java: ユーザからの(標準)入力を取得 - System.inとInputStreamReaderクラス

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

コマンドプロンプトからキーボード(標準)入力を取得するプログラムです。 肝心の標準入力を取得する手続きはSystem.inフィールドですが、これはバイトストリームでの読み込みを行うメソッドしか持たないので、InputStreamReaderクラスでラップすることでバイトストリームを文字列に変換出来るメソッドに任せます(read()メソッド)。 しかし、InputStreamReader#read()メソッドは一文字毎にしか読み込めず非効率な面があるので、最後にBufferedReaderクラスでラップすることで入力文字をバッファに格納し一行まとめて読み込めるようにします。(readLine()メソッド)。 このように、高水準のコンポーネントにSystem.inのような低水準のコンポーネントを渡して、その中で低水準メソッドを制御する手法はTemplate Methodパターンに通じるものがあるかも。The Open-Closed Principleですね。

ソースコード

import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ReadLineSample {
public static void main(String[] args) {
try {
BufferedReader stdReader =
new BufferedReader(new InputStreamReader(System.in));
System.out.print("INPUT : ");
String line;
while ((line = stdReader.readLine()) != null) { // ユーザの一行入力を待つ
if (line.equals("")) line = "<空文字>";
System.out.print("OUTPUT: " + line);
System.out.print("\nINPUT : ");
}
stdReader.close();
System.out.println("\nPROGRAM END");
} catch (Exception e) {
e.getStackTrace();
System.exit(-1); // プログラムを終了
}
}
}

実行結果

文字列を入力した後Enterキーを押すと入力した文字がOUTPUTされます。

INPUT : Hello World!
OUTPUT: Hello World!
INPUT : こんにちは。
OUTPUT: こんにちは。
INPUT :
OUTPUT: <空文字>
INPUT :
PROGRAM END

プログラムを終了する際は、プロンプト上でCtrl+CかCtrl+Zを押してください。それでreadLine()の戻り値はnullとなりwhileループを抜けます。ここでの注意は、単に文字を何も入力せずEnterキーだけ打つと空文字を返すことです(≠null)。 対して、C言語でこういった処理の場合は環境に合わせた改行文字も読み込こんでいます。たぶん、JavaではInputStreamReaderのreadのどこかの段階でその辺は上手く処理されているのかな?気が向いた時に確認してみようかな。

Python: 文字列の上位型であるシーケンス型の構文 - Sequence[X:Y:Z]

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

データの順序が存在するデータ型としてシーケンス型があり、文字列型の上位型となっています。このシーケンス型には文字列中の文字の抽出や操作を簡略化する分かりやすい構文がありますので、これを確認してみましょう。

String[X:] はString[X]から末尾までの文字を持つ文字列

#!/usr/bin/python
# coding: UTF-8
s = 'abcdefg'
print 's ', s
print 's[0:] ', s[0:]
print 's[1:] ', s[1:]
print 's[-1:] ', s[-1:]
print 's[-5:] ', s[-5:]

実行結果

s abcdefg
s[0:] abcdefg
s[1:] bcdefg
s[-1:] g
s[-5:] cdefg

String[:Y] は文字列の先頭からString[Y-1]までの文字を持つ文字列

s = 'abcdefg'
print 's ', s
print 's[:3] ', s[:3] # s[0] s[1] s[2]まで
print 's[:-1] ', s[:-1] # Yは-1よりs[(-1)-1]→s[-2]までの文字列
print 's[:-5] ', s[:-5]

実行結果

s abcdefg
s[:3] abc
s[:-1] abcdef
s[:-5] ab

String[::Z] は文字列の先頭から末尾まで Z 間隔で文字を抽出した文字列

s = 'abcdefg'
print 's ', s
print 's[::1] ', s[::1] # 1間隔なのでsと同じ
print 's[::2] ', s[::2]
print 's[::-1]', s[::-1] # 「-」を付けると逆順に辿っていきます

実行結果

s abcdefg
s[::1] abcdefg
s[::2] aceg
s[::-1] gfedcba

文字列の文字順序を逆順にする処理を構文で賄えるのは良いですね。汎用性があって。

上述の複合 - String[X:Y:Z]

s = 'abcdefg'
print 's ', s
print 's[1:3] ', s[1:3]
print 's[:-1:2] ', s[:-1:2]
print 's[-1::-2]', s[-1::-2]
print 's[1:4:2] ', s[1:4:2]

実行結果

s abcdefg
s[1:3] bc
s[:-1:2] ace
s[-1::-2] geca
s[1:4:2] bd

チュートリアル

リファレンス

Python: 10進数整数を2進数文字列に変換する関数

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

2進数文字列を10進数整数に変換する関数int()はありますが、

>>> int('1011', 2)
11

その逆の、10進数整数を2進数文字列に変換する関数が(Python2.5では)見当たらなかったので、書いてみました。

ソースコード

#!/usr/bin/python
# coding: UTF-8
import math, string
# 10進数整数を2進数文字列に変換する関数
# decimal : 10進数整数
# press : 上位桁の0を切り詰めるフラグ
def toBinary(decimal, press=True):
if decimal == 0: return '0'
bin_str = ""
i = 31
while i >= 0:
bi = int((decimal & int(math.pow(2, i))) >> i)
bin_str += str(bi)
i -= 1
if press:
try:
bin_str = bin_str[bin_str.index('1'):]
except ValueError:
print 'error'
bin_str = '0'
return bin_str
bin_arr = [toBinary(i) for i in range(21)]
print '10進数t2進数'
for i in range(len(bin_arr)):
print '%2dt%5s' % (i, bin_arr[i])

Pythonでは明示的に型変換する必要がありますので、所々int()、str()を用いて型直ししています。

実行結果

10進数 2進数
0 0
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111
16 10000
17 10001
18 10010
19 10011
20 10100

追記: 32bit以上の整数を扱う場合

参考: 10進数を2進数と16進数に変換する - gan2 の Ruby 勉強日記 ↑の記事で、データサイズにかかわらず処理できるRubyプログラムがありましたので、参考させていただきました。

def toBinary2(decimal):
if decimal == 0: return '0'
bin_str = ""
while decimal > 0:
bin_str += str(decimal % 2)
decimal >>= 1
return bin_str[::-1]

実行結果は上と同じです。

リファレンス

Python: リスト中の文字列を大文字⇔小文字に変換

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

文字列を比較する際に、大文字・小文字を区別したくない場合があります。その時は、比較する文字列を大/小文字列のどちらかに統一しておく、という手があります。Pythonでは大文字・小文字変換メソッドlower()、upper()はstringオブジェクトに組み込まれています。 今回は、その使い方と実際に使用する状況に近いデータ構造、ここでは変換対象文字列がリスト中の要素である場合を想定し、for文とリストコンプリヘンション(リスト内包表記)の両表記を以下に示します。

ソースコード

#!/usr/bin/python
# coding: UTF-8
# リスト中の文字列要素を大文字⇔小文字変換
str_atog = "ABCDEFG"
str_hton = "hijklmn"
# lower(), upper()メソッドの使い方
print "大文字(列) %s を小文字(列) %s に変換" % (str_atog, str_atog.lower())
print "小文字(列) %s を大文字(列) %s に変換" % (str_hton, str_hton.upper())
print
arr = ['And', 'Begin', 'Code', 'Double']
arr2 = ['end', 'flag', 'gem', 'halt']
# for文で小文字[大文字](列)を要素とするリストを生成
n_arr = []
for str in arr:
n_arr.append(str.lower())
print n_arr
n_arr2 = []
for str in arr2:
n_arr2.append(str.upper())
print n_arr2, 'n'
# リストコンプリヘンションで小文字[大文字](列)を要素とするリストを生成
print [str.lower() for str in arr]
print [str.upper() for str in arr2]

実行結果

大文字(列) ABCDEFG を小文字(列) abcdefg に変換
小文字(列) hijklmn を大文字(列) HIJKLMN に変換
['and', 'begin', 'code', 'double']
['END', 'FLAG', 'GEM', 'HALT']
['and', 'begin', 'code', 'double']
['END', 'FLAG', 'GEM', 'HALT']

List Comprehensions ならワンライナーで書けるってのは地味に良いですね。 話変わりますが、Rubyにもイテレータやブロックを用いた簡略記法がありましたね。アレはアレで、応用しやすいものです。

チュートリアル

リファレンス

Python: if/for文でのin演算子の各オブジェクト毎の評価

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

if/for文中で使われるin演算子の評価はオブジェクトごとに微妙に変化します。あやふやなままにしておくのもなんですし、ここで、以下のオブジェクトに対するif/for文中の評価を実際に確認してみましょう。

  • 文字列
  • リスト
    • 数値のリスト
    • 文字列のリスト
    • 辞書のリスト
  • 辞書

if/for文 + in 文字列

#!/usr/bin/python
# coding: UTF-8
str1 = "abcdefghijklmn" # 文字列
# if 「検索する文字列」 in 「検索される文字列」:
elem = 'def'
if elem in str1:
print '文字列 "%s" に "%s" は存在する。' % (str1, elem)
print
# for 「要素(文字)」 in 「文字列」:
for ch in str1:
print ch, # str1の先頭の文字から順に繰り返す

実行結果

文字列 "abcdefghijklmn" に "def" は存在する。
a b c d e f g h i j k l m n

if/for文 + in リスト

#!/usr/bin/python
# coding: UTF-8
nums = [1, 2, 3, 4, 5] # 数値のリスト
chs = ['a', 'b', 'c', 'd'] # 文字列のリスト
dics = [{'one':1}, {'two':2}, {'three':3}, {'four':4}] # 辞書のリスト
# if 「検索する要素」 in 「検索されるリスト」:
elem = 3
if elem in nums:
print 'リスト %s に要素の %d は存在する。' % (nums, elem)
elem = 'b'
if elem in chs:
print 'リスト2 %s に要素の %s は存在する。' % (chs, elem)
elem = {'two':2}
if elem in dics:
print 'リスト3 %s に要素の %s は存在する。' % (dics, elem)
print
# for 「リストの要素」 in 「リスト」:
for e_num in nums:
print e_num,
print
for e_ch in chs:
print e_ch,
print
for e_dic in dics:
print e_dic,

実行結果

リスト [1, 2, 3, 4, 5] に要素の 3 は存在する。
リスト2 ['a', 'b', 'c', 'd'] に要素の b は存在する。
リスト3 [{'one': 1}, {'two': 2}, {'three': 3}, {'four': 4}] に要素の {'two': 2} は存在する。
1 2 3 4 5
a b c d
{'one': 1} {'two': 2} {'three': 3} {'four': 4}

if/for文 + in 辞書

#!/usr/bin/python
# coding: UTF-8
dic = {'one':1, 'two':2, 'three':3, 'four':4} # 辞書
# if 「検索するキー」 in 「検索される辞書」:
elem = 'three'
if elem in dic: # 辞書のキーの検索
print '辞書 %s に要素の「キー」 %s は存在する。' % (dics, elem)
print
# for 「辞書のキー」 in 「辞書」:
for key in dic: # for key in dic.keys(): と同じ
print key,
print
for v in dic.values(): # 値を要素として繰り返す
print v,
print
for (k, v) in dic.items(): # (キー, 値)のタプルを要素として繰り返す
print "%s:%s, " % (k, v),

実行結果

辞書 [{'one': 1}, {'two': 2}, {'three': 3}, {'four': 4}] に要素の「キー」 three は存在する。
four three two one
4 3 2 1
four:4, three:3, two:2, one:1,

チュートリアル

リファレンス

Python: 文字列の検索 - index()、reindex()メソッド

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

ソースコード

#!/usr/bin/python
# coding: UTF-8
# 文字列の検索 | index(), reindex()の使い方
s1 = 'Hello, Jan !'
# 引数(パターン)が1文字の文字列
try:
i = s1.index('l') # 引数で与えられた文字列を先頭から探索した場合の出現位置を返す
except ValueError:
i = None # 存在しない場合は例外ValueErrorがなげれられる
print i, s1[i]
try:
i = s1.rindex('l') # 末尾から探索した場合の最初の出現位置を返す
except ValueError:
i = None
print i, s1[i]
print
# 引数(パターン)が2文字以上の文字列
str = 'lo'
try:
i = s1.index(str)
except ValueError:
i = None
print i, s1[i]
try:
i = s1.rindex(str)
except ValueError:
i = None
print i, s1[i]
print
# 引数で与えられた文字列(パターン)が存在しない場合
try:
i = s1.index('Max')
except ValueError:
i = None
print i

実行結果

2 l
3 l
3 l
3 l
None

リファレンス

チュートリアル

Java: 文字列の先頭・末尾の文字を削除するstrip()メソッド

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

テキストマイニングを行う際、文書を単語集合に区切ったのはいいけれど、単語の先頭・末尾に以下のような文字が入っている場合は辞書に格納する際に削除したいですね。

Hello!
page."
"Hi,

単語の前後に複数の記号((ピリオドやクォーテーション、カンマやコロンなど))が入り混じった文字列から、それらを刈り取る方法を採り上げます。0からゴリゴリ文字の条件判定文を書いたり、正規表現でマッチしたときに刈り取りしたりするのも良いですが、オープンソースで今回のケースにマッチしたクラスメソッドがありますので、その使い方と仕組みを簡単に紹介します。

StringUtils#strip(String str, String stripChars)

ソース・バイナリ共にApache CommonsLang Downloadsのページからダウンロードできます。

StringUtils#strip(String str, String stripChars)メソッド引数は、
+第一引数:刈り取り対象文字列(テキスト)
+第二引数:刈り取る文字
例えば、

str = "Hi,]";
stripChars = "],";
result = strip(str, stripChars);

の時のresultの中の文字列はHiとなります。

stripメソッドは内部でstripStartとstripEndメソッドを実行しています。前者は文字列の先頭部分の刈り取りを、後者は末尾部分の刈り取りを行っています。
以下に、stripEndのソースを引用します。

public static String stripEnd(String str, String stripChars) {
int end;
if (str == null || (end = str.length()) == 0) {
return str;
}
if (stripChars == null) {
while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
end--;
}
} else if (stripChars.length() == 0) {
return str;
} else {
while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
end--;
}
}
return str.substring(0, end);
}

ここで重要な処理部分はソース最後の、

while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
end--;
}
}
return str.substring(0, end);

の部分です。
刈り取り対象文字列の末尾から順に文字を調べていきますが、その文字を指すパラメータにendを、その初期値はテキストの文字列長でループごとにデクリメントされていきます。そのendが指す文字がstripCharsに含まれているか否かを判定しているところが、

stripChars.indexOf(str.charAt(end - 1)) != -1

ですね。最終的にsubstringメソッドで部分文字列を取得することで刈り取っています。
これ書いた人上手いなー。indexOfやcharAtの使い道の幅が広がった感じがします。

検索エンジンを実装 (4)AND演算

· 約7分
Yu Sasaki
Enterprise Security Manager / Advisor

AND演算処理の概要

AND演算結果 上の図から、ある2つの語の転置インデックスリストをA, Bとします。ここで、要素をそれぞれa, b(整数)とし演算結果を格納するリストをCとするとき、AND演算は主に以下の処理内容を繰り返します。

  1. if a < b then aの次の要素をaに代入
  2. if a = b then 要素aをCの末尾に追加しA, Bが指す要素を一つ進める

プログラムの主な処理内容

  1. 検索対象テキストを単語に分割。
  2. 単語を転置インデックスに登録。ここで、1単語あたりに格納する情報は、その単語の出現頻度とその文書ID。転置インデックスのデータ構造はTreeMapを使用しkeyに単語、valueはIndexRecordでputします。
  3. ユーザからの標準入力をパースしAND演算(Intersectメソッドで実現しています)。

以下に、ソースコードと実行結果を示します。

IndexRecord.java

1つのトークン(単語)に対するインデックス情報(docIDリストや出現頻度情報)

import java.util.ArrayList;
/*
* 1つのトークン(単語)に対するインデックス情報
*/
public class IndexRecord {
// 出現文書IDリスト(通常ソートの必要あり)
private ArrayList<integer> posts;
// 出現頻度(今回は同一doc内の頻度はカウントしない)
private int freq;
public IndexRecord(int id) {
posts = new ArrayList<integer>();
posts.add(id);
freq = 1;
}
/** docIDをリストに追加(今回は同一docIDのtermはカウントしない) */
public void addDocID(int id) {
if (!existDocID(id)) {
posts.add(id); // docIDの追加
freq++; // 出現頻度のカウントアップ
}
}
/** docIDが既にリスト中に存在するか否か */
public boolean existDocID(int id) {
return (posts.indexOf(id) != -1) ? true : false;
}
public String toString() {
String str = freq + ", "+ posts;
return str;
}
public ArrayList<integer> getPosts() {
return posts;
}
}

BooleanTest.java (AND演算のテストプログラム)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.TreeMap;
/**
* 検索エンジンのAND演算処理
* Web page: https://yukun.info/
* license GPL
*/
public class BooleanTest {
// 検索対象テキスト
static String doc0 = "It is meaningless only to think my long further aims idly. "+
"It is important to set my aims but at the same time I should confirm my present condition. "+
"Unless I set the standard where I am in any level, I'll be puzzled about what I should do from now on. It's in my case.";
static String doc1 = "Today, I enjoyed playing with friends daytime. "+
"After enjoying, I got back to my daily life with an vigorous power. "+
"I should think so, but why did I feel touch of uncertainty and regret? "+
"I wanna enjoy myself and another tremendously during the day when I've played. "+
"Well, As well as I commit play to quality, I'll choose such kinds of play.";
static String doc2 = "I'll manage the limited time in a day. "+
"I think that I divide the time into some intervals such as 5 minutes, "+
"15 minutes and more than one hour and so on. I'll make use of this character of the interval.";
public static void main(String[] args) {
ArrayList<string> docIDlist = new ArrayList<string>();
// 文書を格納
docIDlist.add(doc0);
docIDlist.add(doc1);
docIDlist.add(doc2);
StringTokenizer st[] = new StringTokenizer[3];
String stripChars = ".,:;?!"'[]{}()"; // 除外文字
// 文字列を空白で区切るよう設定
for (int i = 0; i < st.length; i++) {
st[i] = new StringTokenizer(docIDlist.get(i), " ");
}
// 転置インデックス用のMap
TreeMap<string, IndexRecord> termMap = new TreeMap<string , IndexRecord>();
// 分割されたトークンを取得
for (int i = 0; i < st.length; i++) {
// ここでのパラメータiはdocIDを指すことと同じ
while (st[i].hasMoreTokens()) {
// 文字列トークンの先頭・末尾の文字をフィルタリング
// org.apache.commons.lang.StringUtilsクラスを使用
// http://commons.apache.org/proper/commons-lang/
String term = StringUtils.strip(st[i].nextToken(), stripChars);
//System.out.println("値 : " + term);
if(termMap.containsKey(term)) {
// 登録されているtermならdocIDの追加とカウントアップ
IndexRecord ir = termMap.get(term);
ir.addDocID(i);
termMap.put(term, ir);
} else {
// termMapに登録されていないtermならdocIDと合わせて登録
termMap.put(term, new IndexRecord(i));
}
}
} // for loop ends
// termMapのデバッグプリント
System.out.println("単語 freq, docID");
for (String part : termMap.keySet()) {
System.out.printf("%-12s : %sn", part, termMap.get(part));
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String words = "";
while (true) {
ArrayList<arrayList<integer>> postsSet = new ArrayList<arrayList<integer>>();
System.out.print("検索語: ");
try {
// ユーザからの標準入力を受付
words = br.readLine();
if (words.equals("quit")) break;
// 入力文字列をパース
StringTokenizer parser = new StringTokenizer(words, " ");
while (parser.hasMoreTokens()) {
String term = StringUtils.strip(parser.nextToken(), stripChars);
// termMapに登録されている単語か否か
if (termMap.containsKey(term)) {
postsSet.add(termMap.get(term).getPosts());
} else {
postsSet = null;
break;
}
}
// AND演算処理
ArrayList<integer> result = intersect(postsSet);
System.out.print("結果 :");
if (result == null || result.size() == 0)
System.out.println("文書中に存在しません。");
else
System.out.println("文書ID "+ result +"に存在します。");
} catch (IOException e) {
e.printStackTrace();
}
}
} // main() ends
// AND演算処理メソッド
public static ArrayList<integer> intersect(ArrayList<arrayList<integer>> postsSet) {
if (postsSet == null) return null;
int len = postsSet.size();
if (len == 0) return null;
else if (len == 1) return postsSet.get(0);
// postsSetを昇順にソート(演算回数の削減)
Collections.sort(postsSet, new FreqComparator());
ArrayList< Integer > result = postsSet.get(0);
for (int i = 1; i < len; i++) {
result = intersect(result, postsSet.get(i));
}
return result;
}
public static ArrayList<integer> intersect(ArrayList<integer> p1, ArrayList<integer> p2) {
ArrayList<integer> answer = new ArrayList<integer>();
int len1 = p1.size();
int len2 = p2.size();
for (int i=0, j=0; i< len1 && j < len2; ) {
if (p1.get(i) == p2.get(j)) {
answer.add(p1.get(i));
i++; j++;
} else if (p1.get(i) < p2.get(j)) {
i++;
} else {
j++;
}
}
return answer;
}
}

FreqComparator.java (ArrayList要素のソート用)

import java.util.ArrayList;
import java.util.Comparator;
public class FreqComparator implements Comparator<object>{
public int compare(Object o1, Object o2){
return ((ArrayList<integer>) o1).size() - ((ArrayList<integer>) o2).size();
}
}

実行結果

単語 freq, docID
15 : 1, [2]
5 : 1, [2]
After : 1, [1]
As : 1, [1]
I : 3, [0, 1, 2]
<中略>
set : 1, [0]
should : 2, [0, 1]
so : 2, [1, 2]
some : 1, [2]
standard : 1, [0]
such : 2, [1, 2]
than : 1, [2]
that : 1, [2]
the : 3, [0, 1, 2]
think : 3, [0, 1, 2]
this : 1, [2]
time : 2, [0, 2]
to : 2, [0, 1]
touch : 1, [1]
<中略>
検索語: think that
結果 :文書ID [2]に存在します。
検索語: wanna play to
結果 :文書ID [1]に存在します。
検索語: the java
結果 :文書中に存在しません。
検索語: should so
結果 :文書ID [1]に存在します。
検索語: time to
結果 :文書ID [0]に存在します。
検索語: well only
結果 :文書中に存在しません。
検索語: the time
結果 :文書ID [0, 2]に存在します。
検索語: quit

おー、なんだか楽しくなってきましたね。

文字列の区切り方

今回は検索対象テキストを英文に絞ったため、テキスト中の空白文字で区切ることでトークンを抽出できました。対して、日本語テキストの場合は区切り記号等は無い為、n-gramか形態素辞書などを用いてトークンに区切ることで実現できます。日本語文の区切り方は色々ありますが、中でも簡単な方法は、文字種(英文字、記号、ひらがな、カタカナ、漢字)の違いを区切りの境界と捉える方法です。 余談ですが、ブラウザやエディタ等で文字の上でダブルクリックするとカーソル下の文字列が選択状態になりますが、その範囲を決定する際に上述の方法が応用されているようです。ソフトによってはトリプルクリックするとカーソル下の行全体が選択状態になります(使うと編集が楽です)。

JavaとRubyで文字列の終端の扱いの違い

· 約2分
Yu Sasaki
Enterprise Security Manager / Advisor

RubyのコードをJavaに書き直す際に注意する相違点が幾つかあったので、そのうちの一つを挙げてみます。特に文字列関係は色々やりにくいです。

a = "4321"
p a[4] #=> nil

Rubyでは文字を[]で指すとき終端文字の次の添え字を指すとnilを返します。これをCで言う文字列の終端文字'\0'のように考え、if文などの判定に用いることが出来ます。

対して、Javaで同じような書式で書こうものなら、

public class Test {
public static void main(String[] args) {
String str = "abcdef";
char[] arr = str.toCharArray(); // String型をchar型の配列に変換
System.out.println(arr);
System.out.println(str.length()+ " == " + arr.length);
try {
System.out.println(arr[6]);
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
System.out.println("キャッチ:" + e);
- }
try {
System.out.println(str.charAt(6));
} catch (java.lang.StringIndexOutOfBoundsException e) {
System.out.println("キャッチ:" + e);
}
// 最後の文字を知るためには
System.out.println(str.charAt(str.length() - 1));
}
}

のように例外でキャッチしなければなりません。

実行結果

6 == 6
abcdef
キャッチ:java.lang.ArrayIndexOutOfBoundsException: 6
キャッチ:java.lang.StringIndexOutOfBoundsException: String index out of range: 6
f

まぁ、str.length()を用いて文字列長でcharAtで指す文字が文字列の末尾かどうかを判定することで制御すればよいまでの話かもしれません。

今回はとあるアルゴリズムを移植上、気づきが色々あったのでこうした機会に言語仕様に対する理解を深めていきたいです。