Как сделать выборку без замены, используя только количество элементов каждого класса?

У меня есть список строк (["A", "B", ...]) и список размеров ([4,7,...]). Я хотел бы сделать выборку без замены из набора строк, где изначально строка в позиции i появляется sizes[i] раз. Я должен выполнить эту операцию k раз. Понятно, что если я выберу строку i, то sizes[i] уменьшится на 1. Текущее наивное решение, которое я разработал, состоит в том, чтобы сгенерировать весь входной набор, перемешать его и итеративно извлечь первый элемент массива. Это явно неэффективно, поскольку, если строка появляется 1 миллион раз, мне пришлось бы сгенерировать 1 миллион записей.

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, вы можете получить доступ к первым k элементам по их индексу (bag.get(i)). Каковы ожидания, когда понадобятся следующие k элементы? Следует ли восстановить оригинальную сумку?

Thiyagu 11.12.2020 09:59

Ты прав. Я мог просто получить доступ к первым k элементам. В конце концов, результат все равно тот же, я думаю

molfo 11.12.2020 10:07
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
138
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Предполагая, что сумка не должна быть постоянной или вообще использоваться, вы можете создать класс, который содержит ввод и частоту, например. вот так (упрощенно):

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 11.12.2020 10:42

@molfo верно, хотя это требование не вошло в ваш вопрос (или я его пропустил). Что вы могли бы сделать, так это суммировать все частоты и сгенерировать случайное число в этом диапазоне, например. rand.nextInt(10) для вашего примера. Наконец, вместо использования индекса вы перебираете элементы и суммируете их частоту, пока не дойдете до конца или сумма не станет больше или равна этому случайному числу + 1, и выберите этот элемент (так что, если вы получите 9, вы бы выбрали B, поскольку B имеет A (9) + B (1) = 10 (случайное (9) + 1)). Конечно, вам нужно будет корректировать верхнюю границу на каждой итерации.

Thomas 11.12.2020 13:38

Вы можете собрать эти два массива в карту:

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}

Предполагая, что длины этих двух массивов одинаковы, вы можете создать список записей карты, содержащих пары элементов из этих массивов, и перетасовать этот список:

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]

See also: How to sort an array with respect to another array if there are duplicates?

Если все, что вам нужно, это получить случайный элемент из bag, вам не нужно перемешивать bag. Вы можете использовать Random#nextInt(elems.length * размеры.длина), чтобы получить случайный int от 0 до elems.length * sizes.length - 1, и используя этот 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)));
        }
    }
}

Другие вопросы по теме