【问题标题】:How to sample without replacement using only the number of elements of each class?如何仅使用每个类的元素数量进行抽样而不进行替换?
【发布时间】:2020-12-11 08:54:03
【问题描述】:

我有一个字符串列表(["A", "B", ...]) 和一个大小列表([4,7,...])。我想从最初位于i 位置的字符串出现sizes[i] 次的字符串集中进行采样而不进行替换。我必须执行此操作k 次。显然,如果我选择字符串i,那么sizes[i] 减1。我开发的当前简单的解决方案是生成整个输入集,打乱它,并迭代地弹出数组的第一个元素。这显然是低效的,因为如果一个字符串出现 100 万次,我将不得不生成 100 万个条目。

public static void main(String[] args) {
    String[] elems = { "A", "B", "C", "D", "E" };
    Integer[] sizes = { 10, 5, 4, 7, 3 };
    int k = 3;

    ArrayList<String> bag = new ArrayList<>();
    for (int i = 0; i < elems.length; i++) {
        for (int j = 0; j < sizes[i]; j++) {
            bag.add(elems[i]);
        }
    }

    Collections.shuffle(bag);
    for (int i = 0; i < k; i++) {
        System.out.println(bag.remove(0));
    }
}

是否有更好、更有效的方法来执行此操作?谢谢。

【问题讨论】:

  • 那你为什么要删除它?由于它是一个 ArrayList,因此您可以通过索引 (bag.get(i)) 访问第一个 k 元素。当需要下一个 k 元素时,期望是什么?原包应该恢复吗?
  • 你是对的。我可以访问第一个 k 元素。最后,结果还是和我想的一样

标签: java arrays sorting probability


【解决方案1】:

假设袋子不必是持久性的或根本不需要使用,您可以创建一个包含输入和频率的类,例如像这样(简化):

class SampleElement<T> {
  private T value;
  private int frequency;

  //constructors, getters, setters
}

然后根据您的输入构建这些元素的集合,例如(再次简化):

 List<SampleElement<String>> samples = Arrays.asList(new SampleElement<String>("A",10), ...);

最后循环直到该集合为空或您已完成k 次并选择一个随机元素。降低该元素的频率,如果它达到 0,则将其从集合中删除。示例(在我脑海中,因此可能包含错误):

Random rand = new Random();
int runs = k;
while(runs > 0 && !samples.isEmpty() ) {
  runs--;
  int index = rand.nextInt(samples.size());
  SampleElement<String> element = samples.get(index);

  System.out.println(element.getValue());

  element.decrementFrequency();
  if( element.getFrequency() <= 0 ) {
    samples.remove(index);
  }
}

【讨论】:

  • 我不完全确定你的回答能解决我的问题。让我们考虑“A”大小为 9 而“B”大小为 1 的极端情况。我想执行 k=1 个样本。选择 A 的概率是 9/10,而选择 B 的概率是 1/10。在您的解决方案中,我认为概率是 1/2 和 1/2。
  • @molfo 没错,尽管该要求并未成为您的问题(或者我错过了)。您可以做的是将所有频率相加并在该范围内生成一个随机数,例如rand.nextInt(10) 以您为例。最后,不是使用索引,而是遍历元素并对它们的频率求和,直到你到达末尾或者总和大于或等于该随机数 + 1 并选择该元素(所以如果你得到 9,你会选择 B 因为B 有 A(9) + B(1) = 10 (随机(9) + 1))。当然,您需要在每次迭代中调整上限。
【解决方案2】:

您可以将这两个数组收集到一个地图中:

String[] elems = {"A", "B", "C", "D", "E"};
Integer[] sizes = {10, 5, 4, 7, 3};

Map<String, Integer> map = IntStream.range(0, elems.length).boxed()
        .collect(Collectors.toMap(i -> elems[i], i -> sizes[i]));

System.out.println(map); // {A=10, B=5, C=4, D=7, E=3}

【讨论】:

    【解决方案3】:

    假设这两个数组的长度相同,您可以创建映射条目列表,其中包含来自这些数组的元素对,并打乱该列表:

    String[] elems = {"A", "B", "C", "D", "E"};
    Integer[] sizes = {10, 5, 4, 7, 3};
    
    List<Map.Entry<String, Integer>> bag = IntStream
            .range(0, elems.length)
            .mapToObj(i -> Map.of(elems[i], sizes[i]))
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toList());
    
    System.out.println(bag); // [A=10, B=5, C=4, D=7, E=3]
    Collections.shuffle(bag);
    System.out.println(bag); // [D=7, C=4, E=3, A=10, B=5]
    

    另见:How to sort an array with respect to another array if there are duplicates?

    【讨论】:

      【解决方案4】:

      如果您只想从bag 中获取一个随机元素,则无需随机排列bag。您可以使用Random#nextInt(elems.length * sizes.length) 获取从0elems.length * sizes.length - 1 的随机int,并使用此int 作为索引,您可以从bag 中获取一个元素。

      演示:

      import java.util.ArrayList;
      import java.util.Random;
      
      public class Main {
          public static void main(String[] args) {
              String[] elems = { "A", "B", "C", "D", "E" };
              Integer[] sizes = { 10, 5, 4, 7, 3 };
              int k = 3;
      
              ArrayList<String> bag = new ArrayList<>();
              for (int i = 0; i < elems.length; i++) {
                  for (int j = 0; j < sizes[i]; j++) {
                      bag.add(elems[i]);
                  }
              }
      
              Random random = new Random();
              int count = elems.length * sizes.length;
              for (int i = 0; i < k; i++) {
                  System.out.println(bag.get(random.nextInt(count)));
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-02-21
        • 2019-05-12
        • 1970-01-01
        • 1970-01-01
        • 2018-10-27
        • 1970-01-01
        • 2022-01-13
        相关资源
        最近更新 更多