質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

乱数を発生させるときのseed指定の際にcurrentTimeMillisを使った場合と指定しない場合の違い

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 667
退会済みユーザー

退会済みユーザー

 前提・実現したいこと

複数人のじゃんけんプログラムです。
人数、回数の順に入力していただくと、じゃんけんの結果が表示されます。
※Judgemanクラスのjudgeメソッドの冒頭でentrySetを使って0, 1, 2が
何回出ているかを表示させてみましたが, currentTimeMillisを使うと
極端に一つの数字が多く発生する場合があることがわかりました。

 発生している問題・エラーメッセージ

エラーは出ません。
10人, 100人で100回じゃんけんをしたときの結果が
乱数を発生させるときに
Random r = new Random(System.currentTimeMillis());
を使うと引き分けが3割程度発生しますが
Random r = new Random();
を使うと引き分けが9割以上発生します。
本来ならば後者の結果になるらしいのですが, なぜ違いが発生するのかを教えていただきたいです。
というのも, 単純に乱数を発生させた場合(下のSmp.java)では前者後者どちらの
実行結果も変わりはないように思えるからです.

 該当のソースコード

/////Smp.javaです。これのみじゃんけんプログラムではありません。
import java.util.*;

public class Smp{
  public static void main(String[] args){
    int cnt0 = 0;
    int cnt1 = 0;
    int cnt2 = 0;
    //前者です
    //long seed = System.currentTimeMillis();
    //java.util.Random r = new Random(seed);
    //後者です
    //java.util.Random r = new Random();

    for(int i = 0; i < 100; i++){
      int result = r.nextInt(3);
      if(result == 0) cnt0++;
      else if(result == 1) cnt1++;
      else cnt2++;
    }

    System.out.println(cnt0);
    System.out.println(cnt1);
    System.out.println(cnt2);


  }
}

/////ここから下の4つのクラスがじゃんけんプログラムです/////
/////Main.javaです
import java.util.*;

public class Main{
  public static void main(String[] args){
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    int m = sc.nextInt();
    Player[] p = new Player[n];
    for(int i = 0; i < n; i++){
      p[i] = new Player(i);
    }
    Judgeman man = new Judgeman();
    Counter cnt = new Counter(n, m);

    for(int i = 0; i < m; i++){
      man.startGame(p, cnt);
      int result = man.judge();
      cnt.recordResultGame(result);
      man.clearMap();
    }

    cnt.showResult(p);

  }
}

/////Player.javaです
import java.util.*;

public class Player{
  public final int ROCK = 0;
  public final int PAPER = 1;
  public final int SCISSORS = 2;
  private static int tie_time = 0;
  private int id;
  private int rock_win = 0;
  private int paper_win = 0;
  private int scissors_win = 0;

  //前者です
  //long seed = System.currentTimeMillis();
  //java.util.Random r = new Random(seed);
  //後者です
  //java.util.Random r = new Random();

  public Player(int id){
    this.id = id;
  }

  public int getID(){
    return this.id;
  }

  public int showHand(){
    int hand = r.nextInt(3);
    return hand;
  }

  public static void updateTie(){
    Player.tie_time++;
  }

  public void updateRock(){
    this.rock_win++;
  }

  public void updatePaper(){
    this.paper_win++;
  }

  public void updateScissors(){
    this.scissors_win++;
  }

  public static int getTie_time(){
    return Player.tie_time;
  }

  public int getRockWin(){
    return this.rock_win;
  }

  public int getPaperWin(){
    return this.paper_win;
  }

  public int getScissorsWin(){
    return this.scissors_win;
  }

}

/////Judegman.javaです
import java.util.*;

public class Judgeman{
  public final int ROCK = 0;
  public final int PAPER = 1;
  public final int SCISSORS = 2;
  public final int TIE = -1;

  Map<Integer, Integer> map = new HashMap<>();

  public void startGame(Player[] p, Counter cnt){
    for(Player player : p){
      int result = player.showHand();
      map.put(result, map.getOrDefault(result, 0) + 1);
      cnt.recordHand(result, player.getID());
    }
  }

  public int judge(){
    System.out.println(this.map.entrySet());
    if(this.map.size() == 1 || this.map.size() == 3){
      return TIE;
    }
    if(!this.map.containsKey(ROCK)){
      return SCISSORS;
    }
    return map.containsKey(PAPER) ? PAPER : ROCK;
  }

  public void clearMap(){
    this.map.clear();
  }

}

/////Counter.javaです
import java.util.*;

public class Counter{
  public final int ROCK = 0;
  public final int PAPER = 1;
  public final int SCISSORS = 2;
  public final int TIE = -1;
  private int n;
  private int m;
  private int time = 0;
  private int[][] array;

  public Counter(int n, int m){
    this.n = n;
    this.m = m;
    makeArray(this.n + 1, this.m);
  }

  public void makeArray(int x, int y){
    this.array = new int[x][y];
  }

  public void recordHand(int hand, int id){
    array[id][this.time] = hand;
  }

  public void recordResultGame(int result){
    if(result == TIE){
      Player.updateTie();
    }
    array[this.n][this.time] = result;
    this.time++;
  }

  public void showResult(Player[] p){
    System.out.println("結果発表");
    for(Player player : p){

      for(int i = 0; i < this.m; i++){
        if(array[this.n][i] == array[player.getID()][i]){
          switch(array[player.getID()][i]){
            case ROCK:
              player.updateRock();
              break;
            case PAPER:
              player.updatePaper();
              break;
            case SCISSORS:
              player.updateScissors();
              break;
          }
        }
      }

      System.out.println("P" + (player.getID()+1) + "\t" + (player.getRockWin() + player.getPaperWin() +
      player.getScissorsWin()) + "勝" + " " + "グー" + player.getRockWin() + "勝" + ", " +
      "パー" + player.getPaperWin() + "勝" + ", " + "チョキ" + player.getScissorsWin() + "勝");
    }
    System.out.println("引分\t" + Player.getTie_time() + "回です");
  }

}

 補足情報(FW/ツールのバージョンなど)

java 10.0.2 2018-07-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.2+13)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+4

java.util.Randomのソースコードによると

/**
 * Creates a new random number generator. This constructor sets
 * the seed of the random number generator to a value very likely
 * to be distinct from any other invocation of this constructor.
 */
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}


↑のように引数無しコンストラクタ内部ではSystem.nanoTime()に依存する値を引数として引数有りコンストラクタを呼び出しています。
currentTimeMillisは1ミリ秒(1ナノ秒の1000倍)の間同じ値なので、取得した乱数が同じになりやすいのです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/20 10:52

    1ミリ秒(1ナノ秒の1000倍)←1ナノ秒の1000000倍が正解

    キャンセル

  • 2018/11/20 14:15

    回答ありがとうございます.
    貼っていただいたリンク先のソースコードやAPI等見ましたが, しばらく理解できそうにありません.
    とりあえず, currentTimeMillisはある時点を参照する時間が長くいので, 異なる乱数が欲しい時は
    nanoTimeを使えるRandomメソッドを使って乱数を発生させたほうがよい, という理解で妥協しておきます.

    キャンセル

+2

前提

seedに同じ値を与えると、常に同じ乱数列が生成されます。

import java.util.*;

class Main {
    public static void main(String[] args) {
        int seed = 42;        

        Random r1 = new Random(seed);
        Random r2 = new Random(seed);

        for(int i = 0; i < 10; ++i) {
            System.out.printf("%d %d\n", r1.nextInt(), r2.nextInt());
        }
    }
}

実行結果 Wandbox

2 2
0 0
0 0
2 2
0 0
1 1
2 2
2 2
1 1
2 2

これは、乱数列がseedに対して決定的に選ばれているためです。

本題

public class Player{
  ...
  long seed = System.currentTimeMillis();
  java.util.Random r = new Random(seed);

seedの値は、Playerインスタンスが生成されたときに決定されます。

ご提示のコードではPlayerインスタンスを一気に生成していますね。
短時間に複数のインスタンスが作られたとしたら、seed値が被ってしまう恐れがあります。

対策

ミリ秒を使わないことです。
これについては、既にあるtkturboさんの回答に詳しいです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/11/21 08:12

    回答ありがとうございます.
    「前提」欄の"seedに対して乱数列が決定的に選ばれる"というのは初めて知りました.
    実際に, Main.javaのPlayerクラスのインスタンスを生成している箇所で
    for(int i = 0; i < n; i++){
    p[i] = new Player(i);
    System.out.println(p[i].seed);
    }
    として, 10人のじゃんけんとして実行してみると
    1542754963526
    1542754963527
    1542754963527
    1542754963528
    1542754963528
    1542754963528
    1542754963528
    1542754963528
    1542754963528
    1542754963528
    という風にかなりの人数が同じseed値を持っていることがわかりました.
    これからはミリ秒を使わずナノ秒のRandom( )を使いたいと思います.

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • Javaに関する質問
  • 乱数を発生させるときのseed指定の際にcurrentTimeMillisを使った場合と指定しない場合の違い