安野光雅しりとりファンのブログ

絵本「しりとり」について書いてます

安野光雅しりとりファンのブログ3

前回のプログラムでそれっぽい結果が出たのを励みに、今度は別の方法で書きました。前回と同じ結果が出たら、確信を強固なものにできるからです。
一番最初にやろうとしてエラーで出来なかった方法でもう一度やってみました。
試行錯誤の末うまくいったのですが、やっぱりエラーで動かなかったものも残しておけばよかったな…と少し後悔しています。何が間違っていたか、今となっては分からなくなってしまったのです。エラーメッセージにheapとかmemoryとかあったので、自分のpcのメモリの限界なのかなと勝手に思ってましたが、ちょっと書き方を見直せば解決できた気もします。
前回との大きな違いは2つです。

★しりとりの答えを1単語1要素として、2次元配列に格納する
★同じ単語が違うページにある場合、番号を振って区別する(すいか、すいか2、すいか3と表記)

2次元配列に入れておけば、ページごとに抽出したり、特定の単語の出現率を調べたりが簡単にできます。
その際に、例えば6ページ、13ページ、14ページと3回出てくる「すいか」を全部同じ単語にカウントしては困るので、複数出てくるものは区別することにしました。

できたのがこれです。

import java.util.regex.*;
class Xysiritori{
	public static void main(String args[]){
		String[]def1={"さる","きびだんご","くま","しるこ","こあら","すもも","そば","ごま","せなか","あさひ","うし","えんどうまめ","いわし","おに","けんびきょう","かれーらいす"};
		String[]def2={"かじか","るーれっと","ばすけっと","すし","にんにく","めがね","ものほしざお","ごぼう","ます","らむね","しか","ひしもち","うみがめ","こなや"};
		String[]def3={"すもう","かけす","やじろべえ","くつ","おかめ","とうがらし","かめ","ねじりあめ","ちからこぶ","とけい","からす","ねずみ","うめ","しじみ","めぐすり"};
		String[]def4={"しんでれら","いぬ","うきわ","りんごのき","めんたいこ","めがね2","つるはし","ぶるどっぐ","めかくし","すずめ","みつば","えんま","するめ","めざし","みそ"};
		String[]def5={"ばす","そば2","ぐらんぷり","しらうお","まらかす","らっぱ","こだいこ","ねこ","しまうま","めだか","きうい","しらさぎ","わまわし","ぬすびとはぎ"};
		String[]def6={"しろ","こめ","いなほ","こま","ぎょうじ","すもう2","ばんぺい","かっぱ","りんご","おっとせい","かいこ","まき","すいか","ぎんぱい","ぱんけーき"};
		String[]def7={"じんちょうげ","いど","ほね","いか","めざし2","きもの","ぱんだ","ごりら","ろうそく","いも","かずのこ","きのこ","うぐいす","まさかり","こたつ","かっぱのこ"};
		String[]def8={"くびわ","すべりだい","つき","だんご","こんぶ","もも","のこぎり","げた","りす","かぶと","らっきょう","ねぎ","こうもりがさ","しま","どうだんつつじ"};
		String[]def9={"わかめ","たいこ","さい","じしゃく","ぶーつ","とかげ","ぎんか","きのこ2","うま","すずめ2","まんねんひつ","もみじ","りんどう","ごま2","いしがき"};
		String[]def10={"かに","じどうしや","めいろ","こがたな","つくえ","つきみそう","こすもす","げた2","まんとひひ","めじろ","きじ","くつ2","まんと","いけ","うずら"};
		String[]def11={"ひこうき","らっぱ2","にじ","じんべい","えもんかけ","すのこ","けむし","ろば","うき","とき","たいこ2","やし","ろうそく2","つきみ","なす"};
		String[]def12={"いす","す","こぶ","しない","くつべら","こもり","ばけつ","きんこ","きつつき","けいと","じしゃく2","みつばち","ぱーま","きつね","しらぎく"};
		String[]def13={"くものす","こおろぎ","きんのしゃちほこ","りす2","ちえのわ","すべりひゆ","ぶらんこ","ねこ2","とうもろこし","いんき","つぼ","らくご","まつ","すいか2","くじら"};
		String[]def14={"ゆうれい","すみ","わさび","すいか3","ぼんぼり","かたばみ","こいし","こども","らくだ","ぎんこう","ごみばこ","つきみそう2","こまつな","しい","きんもくせい"};
		String[]def15={"しめなわ","かものはし","うみう","みそさざい","うめぼし","だいがく","いのこづち","うずら2","こおり","ももたろう","なきむし","みちしるべ","びわ","いわ","りんご2","いも2"};
		String[]def16={"りぼん","らん","ちょうちん","もん","べーこん","わん"};
		xycomp xycomp=new xycomp();
		xycomp.comp(def1);
		int yy=xycomp.comp(def2,15,0);
		yy=xycomp.comp(def3,yy,1);
		yy=xycomp.comp(def4,yy,2);
		yy=xycomp.comp(def5,yy,3);
		yy=xycomp.comp(def6,yy,4);
		yy=xycomp.comp(def7,yy,5);
		yy=xycomp.comp(def8,yy,6);
		yy=xycomp.comp(def9,yy,7);
		yy=xycomp.comp(def10,yy,8);
		yy=xycomp.comp(def11,yy,9);
		yy=xycomp.comp(def12,yy,10);
		yy=xycomp.comp(def13,yy,11);
		yy=xycomp.comp(def14,yy,12);
		yy=xycomp.comp(def15,yy,13);
		yy=xycomp.comp(def16,yy,14);
		xycomp.print();
		xycomp.print2();
		xycomp.total(def1);
		xycomp.total(def2);
		xycomp.total(def3);
		xycomp.total(def4);
		xycomp.total(def5);
		xycomp.total(def6);
		xycomp.total(def7);
		xycomp.total(def8);
		xycomp.total(def9);
		xycomp.total(def10);
		xycomp.total(def11);
		xycomp.total(def12);
		xycomp.total(def13);
		xycomp.total(def14);
		xycomp.total(def15);
		xycomp.total(def16);
	}
}
class xycomp{
	String[][]answer=new String[1830][16];
	void comp(String[]a){
		int y=0;
		for(String s:a){
			answer[y][0]=s;
			y++;
		}
	}
	int comp(String[]b,int yy,int xx){
		int count=0;
		int s=yy;
		for(int i=0;i<=s;i++){
			String current=answer[i][xx];
			String bottom;
			int len=current.length();
			String tail=current.substring(len-1,len);
			if(hiragana(tail)==true){
				bottom=current.substring(len-1,len);
			}
			else{
				bottom=current.substring(len-2,len-1);
			}
			for(String next:b){
				String top=next.substring(0,1);
				int jud=bottom.compareTo(top);
				if((jud==0) && (count==0)){
					answer[i][xx+1]=next;
					count++;
				}
				else if((jud==0) && (count!=0)){
					for(int t=0;t<=xx;t++){
						answer[yy+1][t]=answer[i][t];
					}
					answer[yy+1][xx+1]=next;
					count++;
					yy++;
				}
				else continue;
			}
			count=0;
		}
	return yy;	
	}
	void print(){/*answerをすべて表示*/
		for(int y=0;y<1830;y++){
			if(answer[y][0]!=null){
				System.out.println();
				System.out.print(1+y);
			}
			for(int x=0;x<16;x++){
				if(answer[y][x]==null) break;
				else System.out.print(answer[y][x]+" ");
			}
		}
		System.out.println();
	}
	void print2(){/*ゴールできるパターンをすべて表示*/
		int number=0;
		for(int y=0;y<1830;y++){
			if(answer[y][15]==null) continue;
			else{
				System.out.print(1+number);
				number++;
			}
			for(int x=0;x<=15;x++) System.out.print(answer[y][x]+" ");
			System.out.println();
		}
	}
	void total(String[]def){/*def[]の単語がansに何個出現するかカウントして表示*/
		int[]total=new int[16];
		int n=0;
		for(String s:def){
			int c=0;
			for(int y=0;y<1830;y++){
				for(int x=0;x<16;x++){
					if(answer[y][15]==null) continue;
					int j=answer[y][x].compareTo(s);
					if(j==0) c++;
				}
			}
			total[n]=c;
			System.out.println(s+" "+total[n]);
			c=0;
			n++;
		}
	}
	boolean hiragana(String value){/*最後の文字がひらがなかチェック*/
		boolean result=false;
		if(value!=null){
			Pattern pattern=Pattern.compile("^[\u3040-\u309F]+$");
			result=pattern.matcher(value).matches();
		}
		return result;
	}

ちょっと長いですね。たった今気が付きましたが、def1からdef16も2次元配列にしてしまえばもっと短く書けそうです。それはまた今度やるとして…
各メソッドの役割を、実際のanswer配列の変化を見ながら説明します。

【void compメソッド】
受け取った単語リストをanswerの配列に格納します。
xycomp.comp(def1);
これが実行されるとanswerの中身はこうなります。

 

   [0]
[0]さる 
[1]きびだんご 
[2]くま 
[3]しるこ 
[4]こあら 
[5]すもも 
[6]そば 
[7]ごま 
[8]せなか 
[9]あさひ 
[10]うし 
[11]えんどうまめ 
[12]いわし 
[13]おに 
[14]けんびきょう 
[15]かれーらいす

【int compメソッド】
「answerのx軸の最後尾要素の最後の文字(bottom)」と「次に入り得るdefの先頭の文字(top)」を比較して、しりとりになっていればdef(next)をanswerのx軸最後尾に追加します。
int yy=xycomp.comp(def2,15,0);
3つの引数はそれぞれ、
(これから比較するdef、現在のy軸の最後尾の要素番号[15]、現在のx軸の最後尾の要素番号[0])です。
yyには実行後のy軸の最後尾の要素番号が戻ってきます。

これが実行されると、
さる と かじか
さる と るーれっと
さる と ばすけっと
の順に比較していき、配列の中身はこうなります。

 

   [0]		[1]
[0]さる		るーれっと 
[1]きびだんご	ごぼう 
[2]くま		ます 
[3]しるこ	こなや 
[4]こあら	らむね 
[5]すもも	ものほしざお 
[6]そば		ばすけっと 
[7]ごま		ます 
[8]せなか	かじか 
[9]あさひ	ひしもち 
[10]うし		しか 
[11]えんどうまめ	めがね 
[12]いわし	しか 
[13]おに		にんにく 
[14]けんびきょう	うみがめ 
[15]かれーらいす	すし

ここからが苦労した部分です。次のdef3には、

「と」ではじまる単語が2つ

「か」ではじまる単語が3つ

「ね」ではじまる単語が2つ

あるので、このページ以降はdefをx軸最後尾に追加するだけでは収まりません。

新しく分岐したものを、そこまでの過程も含めてy軸の最後尾に

[16][0]、[16][1]、[16][2]と追加していく必要があります。

yy=xycomp.comp(def3,yy,1);

これが実行されると配列の中身はこうなります。

  [0]		[1]		[2]
[0]さる		るーれっと	とうがらし 
[1]きびだんご	ごぼう		うめ 
[2]くま		ます		すもう 
[3]しるこ	こなや		やじろべえ 
[4]こあら	らむね		ねじりあめ 
[5]すもも	ものほしざお	おかめ 
[6]そば		ばすけっと	とうがらし 
[7]ごま		ます		すもう 
[8]せなか	かじか		かけす 
[9]あさひ	ひしもち		ちからこぶ 
[10]うし		しか		かけす 
[11]えんどうまめ	めがね		ねじりあめ 
[12]いわし	しか		かけす 
[13]おに		にんにく		くつ 
[14]けんびきょう	うみがめ		めぐすり 
[15]かれーらいす	すし		しじみ 
[16]さる		るーれっと	とけい 
[17]こあら	らむね		ねずみ 
[18]そば		ばすけっと	とけい 
[19]せなか	かじか		かめ 
[20]せなか	かじか		からす 
[21]うし		しか		かめ 
[22]うし		しか		からす 
[23]えんどうまめ	めがね		ねずみ 
[24]いわし	しか		かめ 
[25]いわし	しか		からす

[16]〜[25]が分岐で増えた分ですね。
どこが苦労したかというと、yyの使い方です。いわば現在地を表すyyを、比較する際のfor文の中と、配列に追加する際の要素位置の指定の両方に使ってしまっていたのです。for(int i=0;i<=yy;i++)のように書いていたのですね。ところが要素位置のyyは実行中にどんどん増えますから、for文から抜けられなくなっていたんですね。
これを解決したのがint compの
int count=0;
int s=yy; です。
countは、現在の単語(current)につながる単語が次のdef(next)にあった場合、それがdefの中で何個目なのかをカウントしています。
例えば
[8][1]かじか につながる単語「かけす」が見つかると、このとき(count==0)なので、「かけす」は[8][2]に格納します。
どこかに格納するとcount++して1になります。(count!=0)になるので、これ以降の「かめ」「からす」はy軸方向に新しく増やすことになります。
[19]せなか    かじか        かめ 
[20]せなか    かじか        からす 
の列を見るとここに入ったのが分かりますね。

また、yyをそのまま使わずsにコピーして、
 for文の中ではs
 要素位置の指定にはyy
と使い分けることにしました。これでfor文を抜けられます。
今思うと簡単なことなのですが、なかなか出来ませんでした。変数を増やすほど、抽象的になって自分で読んでても分からなくなってくるんですよね。

こんな感じでdef16まで続けると、ゴールです。
1ページから16ページまで順番に結果が出るので、前回のプログラムよりも実際のしりとりの手順に近いです。

その他のメソッド4つです。
【printメソッド】
answerを表示します。要素がある限り全部表示しますので、15ページでしりとりが終わってしまった場合も含めて表示します。
【print2メソッド】
answerの中でも16ページに要素があるもの、つまりゴールできたパターンだけを表示します。
【totalメソッド】
answerに各ページの単語が何個出現するかカウントします。
【hiraganaメソッド】
すいか3やめがね2など、ひらがな以外のものを含んでいたらfalseを返します。
このひらがなチェックの部分は
https://medium-company.com/javaで数値チェックを実装する方法/
を参考にしました。分かりやすくて、大変ありがたいです。

print2メソッドで、ゴールできたパターンだけを表示すると、897通りの結果が出ました。
897!!!
前回と一緒の数字なんです。うれしい。やってよかった。

長くなってしまったのでここまでにします。
次回はこの結果をもとに、どの単語が一番ゴールしやすいのかなどを分析しようと思います。