生成N个3位唯一的随机整数,其中所有数字都是唯一的

时间:2020-01-17 09:26:50

标签: java java-8

下面是到目前为止我尝试过的代码,它可以正常工作:

  public static void main(String[] args) {
    Random random = new Random();
    Integer[] integers = random.ints(10, 100, 999).boxed().toArray(Integer[]::new);
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < integers.length; i++) {
      String[] split = integers[i].toString().split("");
      int a = 0, b = 0, c = 0;
      a = Integer.valueOf(split[0]);
      b = Integer.valueOf(split[1]);
      c = Integer.valueOf(split[2]);
      if ((a != b) && (a != c) && (b != c)) {
        list.add(Integer.valueOf(integers[i]));
      }
    }
    list.forEach(System.out::println);
  }

输出正确。

如果 abc 是整数,则a !=ba ! cb !=c, 使所有数字唯一。

我尝试在流中应用后面的部分,但没有得到预期的结果。有人可以指导我我要去哪里了吗?

Java-8版本:

public static void main(String[] args) {

    Random random = new Random();
    Integer[] integers = random.ints(10, 100, 999).boxed().toArray(Integer[]::new);
    String[] collect = Arrays.stream(integers).map(s -> {
      String[] split = s.toString().split("");
      return split;
    }).filter(
        k -> (Integer.valueOf(k[0]) != Integer.valueOf(k[1])) && (Integer.valueOf(k[0]) != Integer
            .valueOf(k[2])) && (Integer.valueOf(k[1]) != Integer.valueOf(k[2])))
        .toArray(String [] ::new);
    Arrays.stream(collect).forEach(System.out::println);



  }

4 个答案:

答案 0 :(得分:6)

将数字转换为字符串并调用split("")可能会最慢​​您可能想到的解决方案。

如果您有一个3位数字,并且想要3位数字,请使用除法和余数运算符:

int i = /*assign some non-negative number of at most 3 digits*/;
int d1 = i / 100;
int d2 = i / 10 % 10;
int d3 = i % 10;

如果您需要N个数字,则无法生成N个数字,然后丢弃其中的一些数字。这样会使您更少少于N数字。您必须先算出 后再丢弃错误的数字。

static int[] generate(int n) {
    // Numbers 100 and 101 contain duplicates, so lower limit is 102.
    // Upper limit is 987 (inclusive), since 988, 989, and 99x all contain duplicates.
    return new Random().ints(102, 988)
            .filter(Test::isThreeUniqueDigits)
            .limit(n)
            .toArray();
}
private static boolean isThreeUniqueDigits(int i) {
    int d1 = i / 100;
    int d2 = i / 10 % 10;
    int d3 = i % 10;
    return (d1 != d2 && d1 != d3 && d2 != d3);
}

或者使用lambda表达式代替方法参考:

static int[] generate(int n) {
    return new Random().ints(102, 988).filter(i -> {
                int d1 = i / 100, d2 = i / 10 % 10, d3 = i % 10;
                return (d1 != d2 && d1 != d3 && d2 != d3);
            }).limit(n).toArray();
}

样本结果

[416, 613, 401, 250, 507, 306, 179, 152, 850, 504]
[913, 304, 174, 874, 714, 245, 632, 890, 357, 382]
[618, 706, 946, 364, 209, 320, 690, 529, 824, 651]
[419, 386, 547, 471, 952, 917, 389, 469, 640, 285]
[120, 347, 549, 247, 619, 328, 814, 240, 984, 630]
[127, 174, 723, 287, 149, 329, 176, 964, 451, 617]
[539, 587, 768, 594, 296, 948, 157, 409, 952, 395]
[602, 392, 698, 761, 231, 764, 517, 147, 402, 841]
[194, 294, 923, 542, 362, 248, 352, 286, 407, 348]
[631, 502, 461, 439, 174, 278, 407, 394, 617, 370]
[754, 193, 539, 290, 504, 684, 921, 962, 724, 196]
[125, 586, 925, 857, 879, 761, 134, 620, 134, 723]
[457, 307, 524, 536, 249, 349, 901, 623, 247, 320]
[103, 903, 506, 645, 431, 802, 695, 761, 609, 867]
[569, 894, 608, 963, 681, 365, 162, 874, 452, 307]
[807, 178, 983, 837, 956, 273, 295, 527, 798, 406]
[157, 936, 398, 379, 618, 920, 957, 921, 430, 879]
[396, 280, 315, 569, 328, 138, 931, 623, 413, 926]
[987, 972, 518, 391, 138, 691, 372, 193, 402, 678]
[346, 328, 940, 768, 307, 419, 146, 950, 671, 530]

答案 1 :(得分:4)

您正在分割字符串,然后不合并分割数组。

.map(strings -> String.join("", strings))之前添加.toArray(String [] ::new);以解决问题。

答案 2 :(得分:3)

一个方面未得到处理:结果中出现重复的数字。

一个人也可以将结果收集到一个具有唯一编号的集合中。 我使用了LinkedHashSet,它按生成的数字顺序保留生成的数字, 没有专门按照HashSet(按hashCode排序)或TreeSet(按顺序)排序。

然后,由于重复项需要更多尝试,因此无法使用随机整数列表。

您的算法将变为:

    // Maximal different numbers 9*9*8 as different digits 10*9*8 and first digit not 0.
    final int MAX_N = 9*9*8; // 648
    int size = N;
    if (size > MAX_N) {
        throw new IllegalArgumentException("More than maximum " + MAX_N + ": " + size);
    }
    Set<Integer> result = new LinkedHashSet<Integer>();
    Random random = new Random();
    for (int i = 0; i < size; ++i) {
        int n = randomUnique(random);
        if (!result.add(n)) {
            --i; // Already added, take a new random int.
            // When size nears MAX_N the looping take enormous long!
        }
    }
    System.out.println(result);

但是可以立即正确选择随机的唯一数字:

static int randomUnique(Random random) {
    // 1-9
    int d2 = 1 + random.nextInt(9);
    int n = d2;

    // 0-9 without d1
    int d1 = random.nextInt(9); // As index in those digits.
    if (d1 >= d2) {
        ++d1;
    }
    n = 10 * n + d1;

    // 0-9 without d1 and d2
    int d0 = random.nextInt(8); // As index in those digits.
    if (d0 >= d1 || d0 >= d2) {
        ++d0;
        if (d0 >= d1 && d0 >= d2) {
            ++d0;
        }
    }
    n = 10 * n + d0;
    return n;
}

如前所述,该算法将在大小接近MAX_N时尝试大量的randomUnique调用。

更好的方法是取100-999之间的所有数字,然后随机取一个合适大小的子集。

这似乎是一种困惑,是家庭作业,只是一些指向更好,通常更快的算法的指针:

BitSet uniqueNumbers = new BitSet(1000);
for (int num = 100; num < 1000; ++num) {
    uniqueNumbers.set(num, isUnique(num));
}
... take N elements

boolean isUnique(int num) {
    ...
}

答案 3 :(得分:2)

您可以首先生成有效数字,而不是生成随机数字来测试它们并丢弃无效的数字。

只需逐位生成它们。第一个在数百个位置,必须在1…9范围内且没有其他限制,因此我们可以直接生成它。第二个必须在0…9范围内,但不能等于第一个,因此我们可以在1…9范围内生成一个数字,如果等于第一个,则将其替换为零。同样,最后一个位置的数字在2…9范围内生成,如果等于第一个数字,则替换为零;如果等于第二个数字,则替换为一个。然后,我们有一个有效的号码,无需重复该过程。

作为一个简单的循环,看起来像

if(N > 648) throw new IllegalArgumentException("There can't be "+N+" unique numbers");
ThreadLocalRandom r = ThreadLocalRandom.current();

Set<Integer> result = new LinkedHashSet<>(N);
while(result.size() < N) {
    int hundreds = r.nextInt(1, 10);
    int tens = r.nextInt(1, 10);
    if(tens == hundreds) tens = 0;
    int ones = r.nextInt(2, 10);
    if(ones == hundreds) ones = 0;
    if(ones == tens) ones = 1;
    result.add(hundreds * 100 + tens * 10 + ones);
}

通过使用Set并测试大小而不是使用计数循环,我们确保生成N唯一编号。


或者,我们可以先创建一个所有有效数字的可重用列表,然后将任务更改为“从列表中选择N个项目”,也可以在其他情况下重用。

生成所有有效数字很简单,遍历所有数字然后跳过无效的数字,然后计算该数字,这比遍历所有数字并测试它们要简单,而这需要昂贵的数字提取。

List<Integer> validNumbers = new ArrayList<>();
for(int h = 1; h < 10; h++) {
    for(int t = 0; t < 10; t++) {
        if(t == h) continue;
        for(int o = 0; o < 10; o++) {
            if(o != t && o != h) validNumbers.add(h * 100 + t * 10 + o);
        }
    }
}

然后,我们可以选择N个唯一元素:

if(N > validNumbers.size()) throw new IllegalArgumentException();

// copying, so validNumbers can be reused
List<Integer> result = new ArrayList<>(validNumbers);

if(N == result.size()) {
    Collections.shuffle(result);
}
else {
    for (int i = 0; i < N; i++) {
        Collections.swap(result, i, r.nextInt(i, result.size()));
    }
    result.subList(N, result.size()).clear();
}

第一个分支,当N == validNumbers.size()时,我们只需要对数字进行混洗并具有N个有效的随机元素。当N较小时,替代方法基本上与shuffle在内部相同,但是省略了我们未选择的元素的工作,最后删除了它们。


我们可以使用Stream API表达相同的逻辑,但这并不总是成功。

第一个变体可能是

List<Integer> result = IntStream.generate(() -> {
        ThreadLocalRandom r = ThreadLocalRandom.current();
        int h = r.nextInt(1, 10), t = r.nextInt(1, 10), o = r.nextInt(2, 10);
        if(t == h) t = 0;
        if(o == h) o = 0;
        if(o == t) o = 1;
        return h * 100 + t * 10 + o;
    })
    .distinct().limit(N).boxed()
    .collect(Collectors.toList());

如果.boxed().collect(Collectors.toList())数组结果足够,则可以将toArray()替换为int[]

对于第二种方法,我们可以使用

int[] validNumbers = IntStream.range(1, 10)
    .flatMap(h -> IntStream.range(0, 10).filter(t -> t != h)
        .flatMap(t -> IntStream.range(0, 10).filter(o -> o != t && o != h)
            .map(o -> h * 100 + t * 10 + o)))
    .toArray();

获取有效数字和

List<Integer> result = ThreadLocalRandom.current()
    .ints(0, validNumbers.length)
    .distinct().limit(N)
    .mapToObj(ix -> validNumbers[ix])
    .collect(Collectors.toList());

选择N个不同的元素(可能比shuffle方法更昂贵)。同样,当数组.mapToObj(ix -> validNumbers[ix]) .collect(Collectors.toList())足够时,可以用.map(ix -> validNumbers[ix]) .toArray()替换int[]