一意なテスト用文字列データを適当に生成する方法

プログラムを作成するときに、メッセージの文字列などを適当にそれっぽく生成したい場合があります。
そういうとき、とりあえずこういうプログラムが考えられます。

public static void main(String[] args){
    String[] users = {"nowokay", "higayasuo", "t_yano"};
    String[] pre = {"ところで", "じつは", "さて、", "なかんずく、"};
    String[] msg = {"こんにちは", "はらへ", "おはよう", "ねる", "きたく", "ビールのむ"};

    for(int i = 0; i < users.length * pre.length * msg.length; ++i){
        System.out.printf("|%s|%s%s|%n",
                users[i / (pre.length * msg.length)],
                pre[(i / msg.length) % pre.length],
                msg[i % msg.length]);
    }
}


そうすると、こんな感じのデータが生成されます。

nowokay ところでこんにちは
nowokay ところではらへ
nowokay ところでおはよう
nowokay ところでねる
nowokay ところできたく
nowokay ところでビールのむ
nowokay じつはこんにちは
nowokay じつははらへ
・・・ ・・・


これで一意のデータが生成されはするのですがいくつか問題があります。
一番問題なのは、ちょっと文字列を生成するにはロジックに頭を使いすぎることです。カッコを忘れたり整数演算での切り捨てがあったり、なんどか試行錯誤が必要になることもあります。
また、usersを求めるためにpreやmsgのデータ数が必要になっているように、他のデータ数に依存するので、データ数を増やすためにsuffixのような別のパターンを追加しようとするとすべての計算を考え直す必要があります。
さいごに、適当に生成したい割には同じデータが続きすぎてしまいます。一旦リストにいれてランダムにシャッフルしてもいいのですが、さらに手間が増えてしまいます。


ということで、解決法としては、パターン数がそれぞれ互いに素になるようにするといいです。つまり、パターン数同士の最大公約数が1になるようにするということです。ただ、最大公約数とかを考えるのはめんどうなので、パターン数が異なる素数になるようにするといいと思います。素数の累乗でもいいです。
さっきのプログラムだと、データ数がusers:3、pre:4、msg:6となって、usersとmsgの最大公約数が3、preとmsgの最大公約数が2になっています。
そこで、msgのメッセージ数を変えます。「コンビニ行く」というメッセージを付け加えて7パターンにしてもいいのですが、今回は健康を考えて「ビールのむ」を削って5パターンにしておきましょう。
そうすると、パターン数が3、4(=2の2乗)、5という異なる素数かその累乗ということになります。


このようにパターン数が互いに素になるようにすれば、プログラムは次のようなものになります。

public static void main(String[] args){
    String[] users = {"nowokay", "higayasuo", "t_yano"};
    String[] pre = {"ところで", "じつは", "さて、", "なかんずく、"};
    String[] msg = {"こんにちは", "はらへ", "おはよう", "ねる", "きたく"};//, "ビールのむ"};

    for(int i = 0; i < users.length * pre.length * msg.length; ++i){
        System.out.printf("|%s|%s%s|%n",
                users[i % users.length],
                pre[i % pre.length],
                msg[i % msg.length]);
    }
}

それぞれのパターンを選ぶときに、そのパターンのパターン数だけを使うようになってプログラムが簡単になっています。これで、3*4*5の60パターンが生成できます。


生成されるデータも次のようになんとなくバラバラ感があります。

nowokay ところでこんにちは
higayasuo じつははらへ
t_yano さて、おはよう
nowokay なかんずく、ねる
higayasuo ところできたく
t_yano じつはこんにちは
nowokay さて、はらへ
higayasuo なかんずく、おはよう
・・・ ・・・


パターンを増やしても、こんな感じで、ロジック部分の変更はストレート、つまり変更ぶんだけ変更する感じになります。最初のコードの場合は、users、pre、msgのパターン選択部分も書き換える必要があります。

public static void main(String[] args){
    String[] users = {"nowokay", "higayasuo", "t_yano"};
    String[] pre = {"ところで", "じつは", "さて、", "なかんずく、", ""};
    String[] msg = {"こんにちは", "はらへ", "おはよう", 
        "ねる", "きたく", "ビールのむ", "コンビニ行く"};
    String[] suffix = {"なり", "ごじます", "よ", ""};

    for(int i = 0; i < users.length * pre.length * msg.length * suffix.length; ++i){
        System.out.printf("|%s|%s%s%s|%n",
                users[i % users.length],
                pre[i % pre.length],
                msg[i % msg.length],
                suffix[i % suffix.length]);
    }
}


これでできるデータはこんな感じ。3*5*7*4で、420件のデータが生成できます。

nowokay ところでこんにちはなり
higayasuo じつははらへごじます
t_yano さて、おはようよ
nowokay なかんずく、ねる
higayasuo きたくなり
t_yano ところでビールのむごじます
nowokay じつはコンビニ行くよ
higayasuo さて、こんにちは
・・・ ・・・


ただし、こういった工夫は、プライベートなテストデータ生成プログラムにだけ使うべきで、複数の人が使うようなコードには使わないほうがいいし、ましてやプロダクトのコードに使うべきではないです。
たとえば、パターン数を増やそうとして、うっかりsuffixに「なう」を追加してしまうと、preと同期してしまって全部で105パターンしかあらわれなくなってしまいます。もうひとつsuffixに追加したとしても、userと同期するので210パターンです。
生成されるデータの一意性が、データ数が互いに素であることという、ソースコード上にあらわれない事柄で保証されているわけです。データの保証は明示的なロジックで行うべきなので、たとえデータ数が互いに素であったとしても、プロダクトでのコードは最初にあげたもののようにするべきだと思います。


あと、ここに現れるidのようなものは架空のもので、偶然そういうidの人がいるかもしれませんが実在の人物とは関係ないことをお断りしておきます。