オブジェクトをシャッフルする効率的な方法


20

クイズソフトウェアのプログラムを書いています。質問、回答、オプション、マーク、ネガティブマークのArrayListを含む質問クラスがあります。このようなもの:

class question
{
    private ArrayList<Integer> index_list;
    private ArrayList<String> question_list;        
    private ArrayList<String> answer_list;      
    private ArrayList<String> opt1_list;        
    private ArrayList<String> opt2_list;    
}

すべての質問をシャッフルしたいのですが、質問をシャッフルするには、すべてのオブジェクトをシャッフルする必要があります。私はこの方法でこの問題に取り組みました。

まず、このデザインを使用せずArrayList<String>、インスタンス変数としてStringではなく型を使用し、Collections.shuffleオブジェクトをシャッフルするメソッドを使用します。しかし、私のチームはこの設計を主張しています。

現在、質問へのエントリが作成されるにつれて、質問クラスには増加するArrayListが含まれています。今すぐ質問をシャッフルする方法は?


30
私は絶対に話すのは嫌いですが、もしあなたのチームがこのデザインを主張するなら、それは間違っています。それらを教えてください!私がそう言ったことを彼らに伝えてください(そして私はそれをインターネットで書いたので、私は正しくなければなりません)。
ヨアヒムザウアー

10
はい、この種のデザインは典型的な初心者の間違いだと言っている人がたくさんいることを伝えてください。
ドックブラウン

6
好奇心から:この設計であなたのチームはどのような利点がありますか?
ヨアヒムザウアー

9
Javaの命名規則は、クラス名の場合はCamelCase、変数名の場合はcamelCaseです。
Tulainsコルドバ

このひどい設計上の決定について、チームと対決する必要があると思います。彼らが主張し続けるなら、理由を見つけてください。頑固なだけなら、おそらく近い将来に新しいチームを見つけることを考え始めてください。この構造に理由がある場合は、そのメリットを考慮してください。
ベン・リー

回答:


95

あなたのチームは共通の問題に悩まされています:オブジェクトの拒否

すべての情報が関連付けられた単一の質問を保持するクラスの代わりに、単一のインスタンスですべての質問questionを保持するというクラスを作成しようとします。

それは間違った方法であり、あなたが何をしようとしているかが複雑になります!並列配列(またはリスト)の並べ替え(およびシャッフル)は厄介な仕事であり、通常はまったく避けたいという理由だけで、共通のAPIはありません。

次のようにコードを再構築することをお勧めします。

class Question
{
    private Integer index;
    private String question;        
    private String answer;      
    private String opt1;        
    private String opt2;    
}

// somewhere else
List<Question> questionList = new ArrayList<Question>();

このように、質問をシャッフルするのは簡単です(を使用Collections.shuffle()):

Collections.shuffle(questionList);

39
オブジェクトの拒否でさえありません

22

あなたはしません。インデックスの別のリスト/キューを作成し、シャッフルします。次に、他のコレクションの「シャッフル」順序を駆動するインデックスを繰り返します。

シナリオが分割されているシナリオ以外でも、個別の順序付けコレクションには多くの利点があります(並列性、元のコレクションを再配置するときの速度が高いなど)。


10
私はこれに賛成票を投じたがりません。この設計が実際に修正された場合、次善の解決策になりますが、この設計を主張することは非常に間違っているので、私はそれについて何も提案したくありません。(とにかく、賛成;
ヨアヒムザウアー

3
@joachimSauer-私は同意しますが、元のコレクションを静的なままにする必要があり、それらを通るパスを変える必要がある他の(攻撃的ではない)シナリオがたくさんあります。
テラスティン

4
はい、知っています。そして、インデックスのコレクションをシャッフルすることは、これらの状況に対する正しいアプローチです。私の唯一の恐れは、OPチームがこれを受け入れて、デザインを再確認せずに「十分」と言うことです。
ヨアヒムザウアー

1
この回答は、基礎となるコレクションのクラスまたは構造を修正/再コーディングする自由がない場合、たとえば、OSが管理するコレクションのAPIを使用する必要がある場合に特に役立ちます。インデックスをシャッフルすることは優れた洞察であり、基礎となるデザインをやり直すほどの洞察ではありませんが、それだけで成り立っています。
ハードマス

@Joachim Sauer:実際にインデックスをシャッフルすることは、述べられているように必ずしも問題に対する最善の解決策ではありません。代替案については私の答えをご覧ください。
マイケルボルグワード

16

正しい解決策は適切なオブジェクトモデルを使用することであるという他の回答に同意します。

ただし、実際には複数のリストを同じ方法でシャッフルするのは非常に簡単です。

Random rnd = new Random();
long seed = rnd.nextLong();

rnd.setSeed(seed);
Collections.shuffle(index_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(question_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(answer_list, rnd);
...

それは...それを行うためのきちんとした方法です!さて、「ソート」の場合、この方法で適用されたときにソートされたリストを生成するシードを見つけるだけで、このシードですべてのリストをシャッフルします!
ヨアヒムザウアー

1
@JoachimSauer:まあ、ソートは問題の一部ではありませんでした。ただし、特定のRNGの種を見つける体系的な方法があるかどうかは興味深い質問です。
マイケルボルグワード

2
@MichaelBorgwardtあなたは単に48ビットのJavaランダムの用途(log_2(17)= 48.33!)で可能シャッフルの量を表現することはできません17の質問乗り越えたら
ラチェットフリーク

@ratchetfreak:私にとって本当の問題のようには聞こえません。また、必要に応じてSecureRandomを代わりに使用するのは簡単です。
マイケルボルグ

4
@Telastyn:インデックスのリストはIMOであり、ソリューションを概念的に複雑にする間接化のレイヤーです。パフォーマンスが向上するかどうかは、シャッフル後にリストにアクセスする頻度によって決まります。しかし、人間が答えるクイズの現実的なサイズを考えると、パフォーマンスの違いはわずかです。
マイケルボルグワード

3

クラスを作成しますQuestion2

class Question2
{
    public Integer index_list;
    public String question_list;        
    public String answer_list;      
    public String opt1_list;        
    public String opt2_list;    
}

次に、にマッピングする関数を作成し、その結果に使用questionArrayList<Question2>、にマッピングするCollection.Shuffleための2番目の関数を作成ArrayList<Question2>questionます。

その後、チームに移動して、ArrayList<Question2>代わりにを使用するとquestionコードが大幅に改善されることを確信させます。なぜなら、不必要な変換の多くを節約できるからです。


1
これは良い考えですが、アプリオリのデザイン変更の試みが失敗した後にのみです。
セバスチャンレッド

@SebastianRedl:コードでソリューションを示すだけで、人々に優れたデザインを納得させることが容易になる場合があります。
ドックブラウン

1

私の元々の素朴で間違った答え:

(少なくとも)n乱数を作成し、リストごとにforループ内のアイテムn とアイテムを交換iするだけです。

擬似コードで:

for (in i = 0; i < question_list.length(); i++) {
  int random = randomNumber(0, questions_list.length()-1);
  question_list.switchItems(i, random);
  answer_list.switchItems(i, random);
  opt1_list.switchItems(i, random);
  opt2_list.switchItems(i, random);

}

更新:

コーディングの恐怖の記事を指摘してくれたthe_lotusに感謝します。とにかくジェフ・アトウッドは、フィッシャー・イェーツのアルゴリズムを使用してそれを正しく行う方法も示しています

for (int i = question_list.Length - 1; i > 0; i--){
  int n = rand.Next(i + 1); //assuming rand.Next(x) returns values between 0 and x-1
  question_list.switchItems(i, n);
  answer_list.switchItems(i, n);
  opt1_list.switchItems(i, n);
  opt2_list.switchItems(i, n);
}

ここでの主な違いは、各要素が一度だけスワップされることです。

そして、他の答えはオブジェクトモデルに欠陥があることを正しく説明していますが、それを変更する立場にないかもしれません。したがって、Fisher-Yatesアルゴリズムは、データモデルを変更せずに問題を解決します。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.