Java: สุ่มหมายเลขยาวในช่วง 0 <= x <n


138

คลาสสุ่มมีวิธีการสร้าง int แบบสุ่มในช่วงที่กำหนด ตัวอย่างเช่น:

Random r = new Random(); 
int x = r.nextInt(100);

สิ่งนี้จะสร้างจำนวน int มากกว่าหรือเท่ากับ 0 และน้อยกว่า 100 ฉันต้องการทำเช่นเดียวกันกับ long number

long y = magicRandomLongGenerator(100);

คลาสสุ่มมีเฉพาะ nextLong () แต่ไม่อนุญาตให้กำหนดช่วง


ที่เกี่ยวข้องอาจเป็นประโยชน์: stackoverflow.com/questions/2290057/…
TJ Crowder

1
คุณคิดว่าจะสุ่มแบบยาวและรับช่วงดัดแปลงของคุณหรือไม่? (แน่นอนถ้าช่วงนั้นมีเพียง 100 ฉันจะสร้าง int สุ่มและร่ายให้ยาว)
Hot Licks

java.util.Randomใช้การแจกแจงแบบ 48 บิตเท่านั้น (ดูรายละเอียดการใช้งาน) ดังนั้นจึงไม่มีการแจกแจงแบบปกติ
Geoffrey De Smet

1
ในยุคปัจจุบันเราสามารถพิจารณาใช้ org.apache.commons.lang3.RandomUtils # nextLong
reallynice

คำตอบ:


156

เริ่มจากJava 7 (หรือ Android API ระดับ 21 = 5.0+) คุณสามารถใช้ได้โดยตรงThreadLocalRandom.current().nextLong(n)(สำหรับ 0 ≤ x <n) และThreadLocalRandom.current().nextLong(m, n)(สำหรับ m ≤ x <n) ดูรายละเอียดคำตอบของ@Alex


หากคุณจะติดอยู่กับJava 6 (หรือ 4.x Android) คุณจำเป็นต้องใช้ห้องสมุดภายนอก (เช่นorg.apache.commons.math3.random.RandomDataGenerator.getRandomGenerator().nextLong(0, n-1)ดู@mawaldne 's คำตอบ) nextLong(n)หรือใช้ของคุณเอง

อ้างอิงจากhttps://docs.oracle.com/javase/1.5.0/docs/api/java/util/Random.html nextIntใช้เป็น

 public int nextInt(int n) {
     if (n<=0)
                throw new IllegalArgumentException("n must be positive");

     if ((n & -n) == n)  // i.e., n is a power of 2
         return (int)((n * (long)next(31)) >> 31);

     int bits, val;
     do {
         bits = next(31);
         val = bits % n;
     } while(bits - val + (n-1) < 0);
     return val;
 }

ดังนั้นเราอาจแก้ไขสิ่งนี้เพื่อดำเนินการnextLong:

long nextLong(Random rng, long n) {
   // error checking and 2^x checking removed for simplicity.
   long bits, val;
   do {
      bits = (rng.nextLong() << 1) >>> 1;
      val = bits % n;
   } while (bits-val+(n-1) < 0L);
   return val;
}

1
ฉันมีปัญหากับส่วน "การตรวจสอบ 2 ^ x" ความคิดใด ๆ ?
Vilius Normantas

@Vilius: การตรวจสอบ 2 ^ x ทำให้การสร้างเร็วขึ้นเนื่องจากการใช้โดยตรงrng.nextLong() % nจะให้ค่าที่สม่ำเสมอ (ถือว่าบิตทั้งหมดดี) คุณสามารถเพิกเฉยต่อส่วนนั้นได้หากต้องการ
kennytm

ถ้าฉันต้องการm <= x <= nคุณจะแก้ไขโซลูชันของคุณอย่างไร
BJ Peter DeLaCruz

6
@BJPeterDeLaCruz: จำนวนสุ่มระหว่างmและnสามารถรับได้กับจำนวนสุ่มระหว่าง0และแล้วเพิ่มn-m m
kennytm

85

ThreadLocalRandom

ThreadLocalRandomมีnextLong(long bound)วิธีการ

long v = ThreadLocalRandom.current().nextLong(100);

นอกจากนี้ยังมีnextLong(long origin, long bound)หากคุณต้องการจุดเริ่มต้นอื่นที่ไม่ใช่ 0 ส่งต้นทาง (รวม) และขอบเขต (เอกสิทธิ์)

long v = ThreadLocalRandom.current().nextLong(10,100); // For 2-digit integers, 10-99 inclusive.

SplittableRandomมีnextLongวิธีการเดียวกันและช่วยให้คุณสามารถเลือกเมล็ดพันธุ์ได้หากคุณต้องการลำดับตัวเลขที่ทำซ้ำได้


5
คำตอบนี้ง่ายกว่ามากและมีประโยชน์มากกว่าคำตอบที่ได้รับการโหวตมากที่สุด
yurin

2
สำหรับผู้ที่พัฒนาสำหรับ Android โปรดสังเกตว่าใช้ได้เฉพาะจาก API 21 (Lollipop, Android 5.0): developer.android.com/reference/java/util/concurrent/…
พัฒนา android

76

วิธีมาตรฐานในการสร้างตัวเลข (โดยไม่ใช้วิธียูทิลิตี้) ในช่วงคือเพียงแค่ใช้คู่กับช่วง:

long range = 1234567L;
Random r = new Random()
long number = (long)(r.nextDouble()*range);

จะให้ค่าความยาวระหว่าง 0 (รวม) และช่วง (พิเศษ) ในทำนองเดียวกันถ้าคุณต้องการตัวเลขระหว่าง x และ y:

long x = 1234567L;
long y = 23456789L;
Random r = new Random()
long number = x+((long)(r.nextDouble()*(y-x)));

จะให้ความยาวตั้งแต่ 1234567 (รวม) ถึง 123456789 (เฉพาะตัว)

หมายเหตุ: ตรวจสอบวงเล็บเนื่องจากการแคสต์เป็นแบบยาวมีลำดับความสำคัญสูงกว่าการคูณ


5
ความคิดแรกของฉันคือสิ่งนี้ แต่ดูเหมือนจะไม่สง่างามสักหน่อย และฉันกังวลเกี่ยวกับความสม่ำเสมอของการกระจาย (ไม่ใช่ว่าฉันต้องการจริงๆฉันแค่อยากทำให้ถูกต้อง)
Vilius Normantas

6
โปรดอย่าใช้สิ่งนี้ ผลผลิตไม่สม่ำเสมอเลย
Navin

2
ปัญหาใหญ่ที่สุดคือการปัดเศษจะทำให้บิตต่ำสุดไม่สม่ำเสมอ นอกจากนี้boundจะต้องน้อยกว่าจำนวนเต็มที่มากที่สุดที่สามารถเข้ารหัสเป็นสองเท่าได้ 2 ^ 53
Aleksandr Dubinsky

12

วิธีการข้างต้นใช้งานได้ดี หากคุณใช้ apache commons (org.apache.commons.math.random) ลองดู RandomData มันมีวิธีการคือ nextLong (long lower, long upper)

http://commons.apache.org/math/userguide/random.html

http://commons.apache.org/math/api-1.1/org/apache/commons/math/random/RandomData.html#nextLong(long,%20long)


3
สำหรับลูกหลาน: RandomData เลิกใช้งานใน 4.0 ใช้commons.apache.org/proper/commons-math/apidocs/org/apache/…
Michael Tontchev

11

ใช้ตัวดำเนินการ '%'

resultingNumber = (r.nextLong() % (maximum - minimum)) + minimum;

เมื่อใช้ตัวดำเนินการ '%' เราจะนำส่วนที่เหลือมาหารด้วยค่าสูงสุดของคุณ สิ่งนี้ทำให้เรามีเพียงตัวเลขตั้งแต่ 0 (รวม) ถึงตัวหาร (ไม่รวม)

ตัวอย่างเช่น:

public long randLong(long min, long max) {
    return (new java.util.Random().nextLong() % (max - min)) + min;
}

นี่เป็นสิ่งที่ดี แต่คุณควรตรวจสอบif (max == min)
khcpietro

และตรวจสอบด้วยif (nextLong() >= 0)
khcpietro

6
FYI: สิ่งนี้ไม่ได้ให้การกระจายที่สม่ำเสมอเสมอไปและมันก็แย่มากสำหรับช่วงใหญ่ ๆ ตัวอย่างเช่นถ้าmin = 0และmax = 2 * (MAX_LONG / 3)แล้วคุณสองเท่าแนวโน้มที่จะได้รับค่าในขณะที่คุณจะได้รับหนึ่งใน[0, MAX_LONG / 3] [MAX_LONG / 3, 2 * (MAX_LONG / 3)]
นิค

รหัสนี้ใช้ไม่ได้ หากnextLongส่งคืนค่าลบส่วนที่เหลือจะเป็นลบและค่าจะอยู่นอกช่วง
Arnaud

3

การปรับปรุงเพิ่มเติมคำตอบของ kennytm: การนำคลาสย่อยไปใช้งานจริงใน Java 8 จะเป็น:

public class MyRandom extends Random {
  public long nextLong(long bound) {
    if (bound <= 0) {
      throw new IllegalArgumentException("bound must be positive");
    }

    long r = nextLong() & Long.MAX_VALUE;
    long m = bound - 1L;
    if ((bound & m) == 0) { // i.e., bound is a power of 2
      r = (bound * r) >> (Long.SIZE - 1);
    } else {
      for (long u = r; u - (r = u % bound) + m < 0L; u = nextLong() & Long.MAX_VALUE);
    }
    return r;
  }
}

ฉันรู้ว่านี่เป็นคำตอบเก่าและไม่น่าจะใช้ได้ แต่ส่วนนี้แสดงให้เห็นว่าไม่ถูกต้อง: if ((bound & m) == 0) { r = (bound * r) >> (Long.SIZE - 1); } ประการแรกแสดงให้เห็นได้ง่ายด้วยการทดสอบหน่วยว่าสิ่งนี้ไม่ได้สร้างตัวเลขในช่วง [0, ขอบเขต) ประการที่สองมันซับซ้อนโดยไม่จำเป็น: r = r & mจะบรรลุผลลัพธ์ที่ต้องการและนั่นคือสิ่งที่การใช้งาน Java 8 ในปัจจุบันทำ เป็นไปได้ว่าการใช้งานจะแตกต่างกันเมื่อเขียนคำตอบนี้ แต่ไม่สามารถเป็นสิ่งที่แสดง
E. Bishop

3

หากคุณต้องการให้ pseudorandom ยาวกระจายสม่ำเสมอในช่วง [0, m) ให้ลองใช้ตัวดำเนินการโมดูโลและวิธีค่าสัมบูรณ์รวมกับnextLong()วิธีการดังที่แสดงด้านล่าง:

Math.abs(rand.nextLong()) % m;

randวัตถุสุ่มของคุณอยู่ที่ไหน

ตัวดำเนินการโมดูโลหารสองตัวเลขและส่งออกส่วนที่เหลือของตัวเลขเหล่านั้น ยกตัวอย่างเช่น3 % 2เป็น1เพราะที่เหลือของ 3 และ 2 เป็น 1

เนื่องจากnextLong()สร้าง pseudorandom ที่มีการกระจายอย่างสม่ำเสมอในช่วง [- (2 ^ 48), 2 ^ 48) (หรือที่ไหนสักแห่งในช่วงนั้น) คุณจะต้องใช้ค่าสัมบูรณ์ของมัน หากไม่เป็นเช่นนั้นโมดูโลของnextLong()เมธอดมีโอกาส 50% ที่จะส่งคืนค่าลบซึ่งอยู่นอกช่วง [0, m)

สิ่งที่คุณร้องขอในตอนแรกคือ pseudorandom ที่กระจายอย่างสม่ำเสมอในช่วง [0,100) รหัสต่อไปนี้ทำเช่นนั้น:

Math.abs(rand.nextLong()) % 100;

1
modulo มีความลำเอียงห้ามใช้เพื่อสุ่มstackoverflow.com/a/10984975/1166266
ไซเรน

2

แล้วสิ่งนี้ล่ะ:

public static long nextLong(@NonNull Random r, long min, long max) {
    if (min > max)
        throw new IllegalArgumentException("min>max");
    if (min == max)
        return min;
    long n = r.nextLong();
    //abs (use instead of Math.abs, which might return min value) :
    n = n == Long.MIN_VALUE ? 0 : n < 0 ? -n : n;
    //limit to range:
    n = n % (max - min);
    return min + n;
}

เหรอ?


ไม่เป็นไรยกเว้นส่วนที่เป็นของเฟรมเวิร์ก (ฉันเดา)
Damir Olejar

2

วิธีการด้านล่างนี้จะส่งคืนค่าระหว่าง 10000000000 ถึง 9999999999

long min = 1000000000L
long max = 9999999999L    

public static long getRandomNumber(long min, long max){

    Random random = new Random();         
    return random.nextLong() % (max - min) + max;

}

เมื่อฉันรีเซ็ต long min = 1L; ยาวสูงสุด = 10L; จำนวนสุ่มที่ได้นั้นเกินค่าสูงสุด!
Raj Rajen

ควรเป็นแบบสุ่มnextLong ()% (สูงสุด - นาที) + นาที;
Jay Jodiwal

2

จากJava 8 API

อาจเป็นเรื่องง่ายกว่าที่จะนำไปใช้งานจริงจากAPI doc https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#longs-long-long-long- ที่ พวกเขากำลังใช้เพื่อ สร้างสตรีม longs และต้นกำเนิดของคุณอาจเป็น "0" เหมือนในคำถาม

long nextLong(long origin, long bound) {
  long r = nextLong();
  long n = bound - origin, m = n - 1;
  if ((n & m) == 0L)  // power of two
    r = (r & m) + origin;
  else if (n > 0L) {  // reject over-represented candidates
    for (long u = r >>> 1;            // ensure nonnegative
         u + m - (r = u % n) < 0L;    // rejection check
         u = nextLong() >>> 1) // retry
        ;
    r += origin;
  }
  else {              // range not representable as long
    while (r < origin || r >= bound)
      r = nextLong();
  }
  return r;
}

1

จากหน้าบนRandom :

วิธีการ nextLong ถูกนำไปใช้โดยคลาส Random ราวกับว่า:

public long nextLong() {
   return ((long)next(32) << 32) + next(32);
}

เนื่องจากคลาส Random ใช้ seed ที่มีเพียง 48 บิตอัลกอริทึมนี้จะไม่ส่งคืนค่า long ที่เป็นไปได้ทั้งหมด

ดังนั้นหากคุณต้องการรับ a Longคุณจะไม่ได้รับช่วง 64 บิตเต็ม

ฉันขอแนะนำว่าหากคุณมีช่วงที่อยู่ใกล้ยกกำลัง 2 คุณควรสร้างช่วงLongดังกล่าวในตัวอย่างข้อมูลดังนี้:

next(32) + ((long)nextInt(8) << 3)

เพื่อรับช่วง 35 บิตตัวอย่างเช่น


2
แต่เอกสารระบุว่า "ค่า long ที่เป็นไปได้ทั้งหมด 2 ^ 64 สร้างขึ้นด้วยความน่าจะเป็นเท่ากัน เห็นได้ชัดว่าเมธอด nextLong () ควรคืนค่าที่เป็นไปได้ทั้งหมด .. Btw ความยาวของเมล็ดสัมพันธ์กับการกระจายค่าอย่างไร?
Vilius Normantas


0
public static long randomLong(long min, long max)
{
    try
    {
        Random  random  = new Random();
        long    result  = min + (long) (random.nextDouble() * (max - min));
        return  result;
    }
    catch (Throwable t) {t.printStackTrace();}
    return 0L;
}

1
คุณไม่ควรจะสร้างRandomอินสแตนซ์ที่นี้ที่คุณไม่ควรจับThrowableหรือข้อยกเว้นอื่น ๆ ถ้าไม่จำเป็นต้องคุณควรเข้าสู่ระบบข้อผิดพลาดกับชนิดของกรอบการเข้าสู่ระบบ (เช่น SLF4J) printStackTraceบางส่วนแทนการใช้
Boguś

0

หากคุณสามารถใช้สตรีม java ได้คุณสามารถลองทำสิ่งต่อไปนี้:

Random randomizeTimestamp = new Random();
Long min = ZonedDateTime.parse("2018-01-01T00:00:00.000Z").toInstant().toEpochMilli();
Long max = ZonedDateTime.parse("2019-01-01T00:00:00.000Z").toInstant().toEpochMilli();
randomizeTimestamp.longs(generatedEventListSize, min, max).forEach(timestamp -> {
  System.out.println(timestamp);
});

สิ่งนี้จะสร้างตัวเลขในช่วงที่กำหนดสำหรับ longs



-4

// ใช้เวลาของระบบเป็นค่าเมล็ดพันธุ์เพื่อให้ได้ตัวเลขสุ่มที่ดี

   Random random = new Random(System.currentTimeMillis());
              long x;
             do{
                x=random.nextLong();
             }while(x<0 && x > n); 

// วนซ้ำจนกว่าจะได้ตัวเลขที่มากกว่าหรือเท่ากับ 0 และเล็กกว่า n


1
สิ่งนี้อาจไม่เหมาะสมอย่างยิ่ง แล้วถ้าnเป็น 1 หรือ 2 ล่ะ? ลูปจะทำการวนซ้ำหลายครั้ง
Magnilex
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.