Разделите массив на вложенные массивы так, чтобы ни один вложенный массив не содержал повторяющихся элементов - PullRequest
5 голосов
/ 09 мая 2019

У меня есть массив из 32 чисел [1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9, 10,10,11,12,13,13,14,14,15,16,17,17]

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

Во сколько я могу это сделать?Какое наиболее оптимальное решение для генерации всех перестановок и одной случайной перестановки.Порядок подмассивов не имеет значения.Также не порядок элементов внутри каждого подмассива.

Для моей первоначальной задачи мне не нужно генерировать все перестановки.Мне просто нужно генерировать случайную перестановку каждый раз, когда запускается моя программа.

Мой подход состоял в том, чтобы случайным образом перемешивать массив с помощью алгоритма Фишера-Йейтса и продолжать его перетасовку, пока я не получу все 8 подмассивов без дублирующих элементов.Конечно, это не лучший подход.

Как часть моего решения я перетасовываю массив, и из этого перетасованного массива начинаю добавлять элементы один за другим в подмассивы.Если у какого-либо подмассива уже был номер, то я продолжаю пропускать элементы из моего перетасованного массива, пока не достигну числа, которое не является моими подмассивами.Этот подход не работает в некоторых случаях.

Псевдокод того, что я пробовал

let shuffledArray = shuffle(originalArray);
let subArrays = [];
for (let i = 0; i < 8; i++) {
    subArrays[i] = [];
    for (let j = 0; j < 32; j++) {
        if (!subArrays[i].contains(shuffledArray[j]) && !shuffledArray[j].used) {
            subArrays[i].push(shuffledArray[j])
            shuffledArray[j].used = true;
        }
        if (subArrays[i].length == 4) {
            break;
        }
    }
}

 if subArrays has any sub array such that it has duplicate elements then repeat above steps
 else we have generated a random permutation

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

Я использую JavaScript, но ответы на любом языке приветствуются, если их можно преобразовать в JavaScript.

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

Это мой первый вопрос в SO.Не стесняйтесь редактировать / предлагать улучшения.

Ответы [ 4 ]

2 голосов
/ 10 мая 2019

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

Вот реализация JS:

function partition(arr, N){
    // group items together and sort by length
    // groups will be [[5, 5, 5, 5, 5], [4, 4, 4, 4], ...]

    let groups = Object.values(l.reduce((obj, n) =>{
        (obj[n] || (obj[n] = [])).push(n)
        return obj
    }, {})).sort((a,b) => b.length - a.length)

    let res = []
    while(groups.length >= N){
        let group = []
        let i = 0
        while (group.length < N){
           group.push(groups[i].pop())
           if (groups[i].length < 1) groups.splice(i, 1)
           else i++
        }
        res.push(group)
    }
    return res
}
let l = [1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9,10,10,11,12,13,13,14,14,15,16,17,17]


console.log(partition(l, 4).map(arr => arr.join(',')))

// with 5
console.log(partition(l, 5).map(arr => arr.join(',')))
1 голос
/ 10 мая 2019

В следующем коде Python используется простой метод для создания случайного разбиения при каждом запуске. Он перетасовывает список из 32 целых чисел (чтобы получить случайный результат), а затем использует метод первого соответствия + обратного отслеживания, чтобы найти первое расположение, которое следует из этого перемешивания. Эффективность. Используемая здесь случайная последовательность Фишера-Йейтса является алгоритмом O (n). Поиск первого расположения в случайном порядке может быть близок к O (n) или может быть намного хуже, в зависимости от исходных чисел и случайного перемешивания, как указано ниже.

Предостережения: в идеале наличие другой случайной последовательности должно приводить к другому разделу. Но этого не может быть, потому что существует намного больше разных перемешиваний, чем разных разделов (возможно, в 10 20 раз больше перемешиваний по сравнению с разделами). Также в идеале каждый возможный раздел должен иметь одинаковую вероятность создания. Я не знаю, так ли это здесь, и даже не знаю, охватывает ли этот метод все возможные разделы. Например, вполне возможно, что некоторые разделы не могут быть созданы методом первого соответствия + обратного отслеживания.

Хотя этот метод генерирует подавляющее большинство своих решений довольно быстро - например, менее чем за миллисекунду - он иногда застревает и тратит много времени из-за конфликтов, возникающих в начале рекурсии, которые не обнаруживаются до нескольких уровней Глубже. Например, время нахождения четырех наборов из 1000 различных решений было 96 с, 166 с, 125 с и 307 с, а время нахождения наборов из 100 различных решений - 56 мс, 78 мс, 1,7 с, 5 с, 50 s.

Некоторые программные примечания: В перемешанном списке s мы сохраняем 2 mn-k вместо k. Работа с данными в виде битовых масок вместо подсчета чисел ускоряет тесты на дубликаты. Экспонент mn-k (в 2 mn-k ) позволяет сортировать массив u так, чтобы вывод был в порядке возрастания. В python # вводит комментарии. Скобки с выражением for внутри представляют «понимание списка», способ представления списка, который может быть сгенерирован с помощью оператора for. Выражение [0]*nc обозначает список или массив nc элементов, изначально все 0.

from random import randint
A = [1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,
     9,10,10,11,12,13,13,14,14,15,16,17,17] # Original number list
ns = len(A)                     # Number of numbers
nc = 8                          # Number of cells
mc = 4                          # Max cell occupancy
rc = range(nc)                  # [0,1,...nc-1]
mn = max(A)                     # Max number 
s = [ 2**(mn-k) for k in A]

for i in range(ns-1,0,-1):
    j = randint(0,i)
    s[i], s[j] = s[j], s[i]     # Do a shuffle exchange

# Create tracking arrays: n for cell count, u for used numbers.
n = [0]*nc
u = [0]*nc

def descend(level):
    if level == ns:
        return True
    v = s[level]        # The number we are trying to place
    for i in rc:
        if (v & u[i] == 0) and n[i] < mc:
            u[i] |= v           # Put v into cell i
            n[i] += 1
            if descend(level+1):
                return True     # Got solution, go up and out
            else:
                u[i] ^= v       # Remove v from cell i
                n[i] -= 1
    return False                # Failed at this level, so backtrack

if descend(0):
    for v in sorted(u, reverse=True):
        c = [ mn-k for k in range(mn+1) if v & 2**k]
        print (sorted(c))
else:
    print ('Failed')

Пример некоторых выходных данных:

[1, 2, 5, 9]
[3, 4, 6, 14]
[4, 5, 6, 10]
[4, 5, 7, 17]
[4, 10, 15, 16]
[5, 7, 8, 17]
[5, 7, 11, 13]
[7, 12, 13, 14]

[1, 4, 7, 13]
[2, 5, 7, 8]
[3, 4, 5, 17]
[4, 5, 6, 14]
[4, 6, 7, 9]
[5, 10, 11, 13]
[5, 10, 12, 16]
[7, 14, 15, 17]
1 голос
/ 10 мая 2019

Вот демонстрация перечисления всех возможностей набора (не мультимножества, как в вашем примере), просто чтобы показать, как быстро количество комбинаций увеличивается. Количество комбинаций для разбиения 8 4-элементных частей будет огромным. Я не уверен, но вы можете адаптировать некоторые из этих идей для включения мультимножества или, по крайней мере, сначала провести частичное перечисление, а затем добавить случайные элементы случайным образом.

function f(ns, subs){
  if (ns.length != subs.reduce((a,b) => a+b))
    throw new Error('Subset cardinality mismatch');

  function g(i, _subs){
    if (i == ns.length)
      return [_subs];

    let res = [];
    const cardinalities = new Set();

    function h(j){
      let temp = _subs.map(x => x.slice());
      temp[j].push(ns[i]);
      res = res.concat(g(i + 1, temp));
    }

    for (let j=0; j<subs.length; j++){
      if (!_subs[j].length && !cardinalities.has(subs[j])){
        h(j);
        cardinalities.add(subs[j]);

      } else if (_subs[j].length && _subs[j].length < subs[j]){
        h(j);
      }
    }
    return res;
  }
  let _subs = [];
  subs.map(_ => _subs.push([]));

  return g(0, _subs);
}

// https://oeis.org/A025035
let N = 12;
let K = 3;

for (let n=K; n<=N; n+=K){
  let a = [];
  for (let i=0; i<n; i++)
    a.push(i);
  let b = [];
  for (let i=0; i<n/K; i++)
    b.push(K);
  console.log(`\n${ JSON.stringify(a) }, ${ JSON.stringify(b) }`);

  let result = f(a, b);
  console.log(`${ result.length } combinations`);

  if (n < 7){
    let str = '';
    for (let i of result)
    str += '\n' + JSON.stringify(i);
    console.log(str);
  }

  console.log('------');
}
1 голос
/ 09 мая 2019

Вы можете использовать битовую маску для этой проблемы. Начнем с генерации всех 17-битных чисел, для которых ровно 4 бита установлены в 1. Эти числа будут представлять возможные элементы в одной группе таким образом, что если i-й бит номера установлен, i + 1 является частью этого группа.

Теперь из этих сгенерированных чисел ваша задача состоит в том, чтобы просто выбрать 8 чисел, многократно удовлетворяющих частотным ограничениям каждого элемента, что можно легко сделать.

Я вернусь к вам, если найду другой подход.

РЕДАКТИРОВАТЬ: В качестве альтернативы, вы можете использовать рекурсию следующим образом: начать с 8 чисел, все изначально установлены на 0, начать с установки (a [i] -1) 'бит 1 в ровно одно из тех чисел, которые это бит установлен в 0 и общее количество битов в этом числе меньше 4.

Когда вы достигнете листа в рекурсии, у вас будет 8 чисел, представляющих битовые маски, как описано выше. Вы можете использовать их для раздела.

Вы можете использовать этот подход, создав, скажем, 100 наборов из 8 чисел первоначально и вернуться из рекурсии. Как только все эти 100 будут использованы, вы можете снова запустить эту рекурсию, чтобы создать двойные наборы, сформированные на предыдущем шаге, и т. Д.

#include<bits/stdc++.h>

using namespace std;

int num=0;
vector<vector<int> > sets;

void recur(int* arr, vector<int>& masks, int i) {
    if(num == 0)
        return;
    if(i==32){
        vector<int> newSet;
        for(int j=0; j<8; j++)
            newSet.push_back(masks[j]);
        sort(newSet.begin(), newSet.end());
        int flag=0;
        for(int j=0; j<sets.size(); j++){
            flag=1;
            for(int k=0; k<8; k++)
                flag = flag && (newSet[k] == sets[j][k]);
            if(flag) break;
        }
        if(!flag){
            sets.push_back(newSet);
            num--;
        }
        return;
    }
    for(int ii=0; ii<8; ii++) {
        if(__builtin_popcount(masks[ii]) < 4 && (masks[ii] & (1 << (arr[i]-1))) == 0){
            masks[ii] = masks[ii] ^ (1<<(arr[i] - 1));          
            recur(arr, masks, i+1);
            masks[ii] = masks[ii] ^ (1<<(arr[i] - 1));
        }
    }
}

int main() {
    num = 100;
    int num1 = num;
    vector<int> masks;
    for(int i=0; i<8; i++)
        masks.push_back(0);
    int arr[] = {1,2,3,15,16,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9,10,10,11,12,13,13,14,14,17,17};
    recur(arr, masks, 0);
    for(int j=0; j<num1; j++){
        for(int i=0; i<8; i++){
            //ith group
            printf("%d group : ",i);
            for(int k=0; k<17; k++){
                if(sets[j][i] & (1<<k))
                    printf("%d ",k+1);
            }
            printf("\n");
        }
        printf("\n\n\n======\n\n\n");
    }
    return 0;
}

Это то, что вы ищете?

...