Создайте множество ограниченных случайных перестановок списка.

StackOverflow https://stackoverflow.com/questions/93353

  •  01-07-2019
  •  | 
  •  

Вопрос

Мне нужно составить случайный список перестановок.Элементами может быть что угодно, но не предполагается, что это целые числа от 0 до x-1.Я хочу составить списки y, каждый из которых содержит элементы z.Правила заключаются в том, что ни один список не может содержать один и тот же элемент дважды и что во всех списках количество раз, когда каждый элемент используется, одинаково (или как можно ближе).Например, если мои элементы — 0,1,2,3, y — 6, а z — 2, то одно из возможных решений:

0,3
1,2
3,0
2,1
0,1
2,3

Каждая строка содержит только уникальные элементы, и ни один элемент не использовался более 3 раз.Если бы y было 7, то 2 элемента использовались бы 4 раза, остальные 3.

Это было полезно?

Решение

Это можно улучшить, но, похоже, оно справляется со своей задачей (Python):

import math, random


def get_pool(items, y, z):
    slots = y*z

    use_each_times = slots/len(items)
    exceptions = slots - use_each_times*len(items)


    if (use_each_times > y or
        exceptions > 0 and use_each_times+1 > y):
        raise Exception("Impossible.")


    pool = {}
    for n in items:
        pool[n] = use_each_times

    for n in random.sample(items, exceptions):
        pool[n] += 1

    return pool

def rebalance(ret, pool, z):
    max_item = None
    max_times = None

    for item, times in pool.items():
        if times > max_times:
            max_item = item
            max_times = times


    next, times = max_item, max_times

    candidates = []
    for i in range(len(ret)):
        item = ret[i]

        if next not in item:
            candidates.append( (item, i) )


    swap, swap_index = random.choice(candidates)

    swapi = []
    for i in range(len(swap)):
        if swap[i] not in pool:
            swapi.append( (swap[i], i) )


    which, i = random.choice(swapi)

    pool[next] -= 1
    pool[swap[i]] = 1
    swap[i] = next

    ret[swap_index] = swap

def plist(items, y, z):
    pool = get_pool(items, y, z)

    ret = []
    while len(pool.keys()) > 0:
        while len(pool.keys()) < z:
            rebalance(ret, pool, z)

        selections = random.sample(pool.keys(), z)

        for i in selections:
            pool[i] -= 1
            if pool[i] == 0:
                del pool[i]

        ret.append( selections )

    return ret


print plist([0,1,2,3], 6, 2)

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

Хорошо, один из способов приблизить это:

1 - перетасуйте свой список

2 — возьмите первые элементы y, чтобы сформировать следующую строку

4 — повторять (2), пока в списке есть номера

5 – если вам не хватает номеров, чтобы закончить список, перетасуйте исходный список и возьмите недостающие элементы, следя за тем, чтобы не пересдавать номера.

6. Начните заново с шага (2), пока вам нужны строки.

Я думаю, что это должно быть настолько случайно, насколько это возможно, и наверняка будет соответствовать вашим критериям.Плюс у вас очень мало тестов на дублирующиеся элементы.

Во-первых, вы всегда можете отсортировать список случайным образом, так что давайте не будем беспокоиться о «случайных перестановках» (сложно);и просто беспокойтесь о том, чтобы 1) сделать перестановки (легко) и 2) рандомизировать их (легко).

Если вам нужны «по-настоящему» случайные группы, вы должны признать, что рандомизация по своей природе не допускает ограничения «равномерного распределения» результатов — вы можете получить это или получить серию похожих результатов.Если вы действительно хотите равномерного распределения, сначала сделайте наборы равномерно распределенными, а затем рандомизируйте их как группу.

Нужно ли вам использовать каждый элемент множества x равномерно?Из правил не ясно, что я не мог просто сделать следующую интерпретацию:

Обратите внимание на следующее:«Во всех списках количество раз использования каждого элемента одинаково (или максимально близко)»

Основываясь на этом критерии и правиле z < x*, я постулирую, что вы можете просто перечислить все элементы во всех списках.Таким образом, вы автоматически составляете список y из элементов, пронумерованных до позиции z.Ваш пример не соответствует приведенному выше правилу так точно, как моя версия.Используя ваш пример x={0,1,2,3} y=6 и z=2, я получаю:0,1 0,1 0,1 0,1 0,1 0,1

Я не использовал 2 или 3, но вы не говорили, что я должен использовать их все.Если бы мне пришлось использовать их все и мне не хотелось бы доказывать, что я «настолько близок» к равномерному использованию, я бы просто перечислил все элементы в списках, вот так:0,1 2,3 0,1 2,3 0,1 2,3

Наконец, предположим, что мне действительно нужно использовать все элементы.Чтобы подсчитать, сколько раз может повторяться каждый элемент, я просто беру (y*z)/(количество x).Таким образом, мне не придется сидеть и беспокоиться о том, как разделить элементы в списке.Если есть остаток или результат меньше 1, то я знаю, что не получу точного количества повторений, поэтому в таких случаях не имеет большого значения пытаться тратить вычислительную энергию, чтобы сделать его идеальным.Я утверждаю, что самый быстрый результат — это просто перечислить, как указано выше, и использовать приведенные здесь расчеты, чтобы показать, почему был достигнут или не был достигнут идеальный результат.Можно было бы реализовать причудливый алгоритм, позволяющий определить из этого расчета, сколько позиций будет повторяться, но «он слишком длинный, чтобы поместиться здесь, на полях».

*Каждый список содержит одинаковое количество элементов z, поэтому будет невозможно создавать списки, в которых z больше x, и при этом соблюдать правило, согласно которому ни один список не может содержать один и тот же элемент дважды.Следовательно, это правило требует, чтобы z не могло быть больше x.

Судя по новым деталям в комментариях, решением может быть просто реализация стандартного алгоритма генерации случайных перестановок.Здесь подробно обсуждаются алгоритмы генерации случайных перестановок:

http://www.techuser.net/randpermgen.html

(Из поиска Google:генерация случайной перестановки)

Это работает в Ruby:

# list is the elements to be permuted
# y is the number of results desired
# z is the number of elements per result
# equalizer keeps track of who got used how many times
def constrained_permutations list, y, z
  list.uniq! # Never trust the user. We want no repetitions.
  equalizer = {}
  list.each { |element| equalizer[element] = 0 }

  results = []
  # Do this until we get as many results as desired
  while results.size < y
    pool = []
    puts pool
    least_used = equalizer.each_value.min
    # Find how used the least used element was
    while pool.size < z
      # Do this until we have enough elements in this resultset
      element = nil
      while element.nil?
        # If we run out of "least used elements", then we need to increment
        # our definition of "least used" by 1 and keep going.
        element = list.shuffle.find do |x|
          !pool.include?(x) && equalizer[x] == least_used
        end
        least_used += 1 if element.nil?
      end
      equalizer[element] += 1
      # This element has now been used one more time.
      pool << element
    end
    results << pool
  end
  return results
end

Пример использования:

constrained_permutations [0,1,2,3,4,5,6], 6, 2
=> [[4, 0], [1, 3], [2, 5], [6, 0], [2, 5], [3, 6]]
constrained_permutations [0,1,2,3,4,5,6], 6, 2
=> [[4, 5], [6, 3], [0, 2], [1, 6], [5, 4], [3, 0]]
enter code here
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top