Java: комбинации массивов, х на массив - PullRequest
0 голосов
/ 11 октября 2018

У меня есть пул опций в группах, и я пытаюсь динамически генерировать комбинации для целей тестирования.Я хотел бы определить сегменты и иметь код, генерирующий все комбинации для подачи в мой тест TestNG через @DataProvider.Прямо сейчас у меня есть несколько случаев, жестко запрограммированных, но очевидно, что это не лучший способ сделать это для поддержки кода.

Я изо всех сил пытаюсь разобраться со случаем, когда у вас есть x "шариков" в y "ведрах", когдау> 2.

В простейшем случае предположим, что у вас есть следующий пример:

public static void main(String [] args){
   Object[][] combinations = getCombinations(
        new String[]
        {
          "1", "2"
        },
        new String[]
        {
          "3", "4"
        }/*,
        new String[]
        {
          "5", "6"
        }*/);
   for (Object[] combination : combinations)
   {
     System.out.println(Arrays.toString(combination));
   }
}

private Object[][] getCombinations(Object[]... arrays)
{
   if (arrays.length == 0)
   {
     return new Object[0][0];
   }

   List<Object[]> solutions = new ArrayList<>();
   Object[] array1 = arrays[0];
   for (Object o : array1)
   {
     for (int i = 1; i < arrays.length; i++)
     {
       for (Object o2 : arrays[i])
       {
         int count = 0;
         Object[] path = new Object[arrays.length];
         path[count++] = o;
         path[count++] = o2;
         solutions.add(path);
       }
     }
   }
return solutions.toArray(new Object[0][0]);
}

Вывод:

[1, 3]
[1, 4]
[2, 3]
[2, 4]

Добавление третьего броска "корзины"все в окне.

Решения будут следующие:

[1,3,5]
[1,3,6]
[1,4,5]
[1,4,6]
[2,3,5]
[2,3,6]
[2,4,5]
[2,4,6]

Есть идеи, как решить эту проблему?В идеале вы должны передать getCombination количество пиков на каждую корзину.

Хотя код решения будет приветствоваться, меня больше интересуют причины, стоящие за ним.

Обновление Для будущих посетителей вот отличный ответ Кевина Андерсона в общей форме:

Юнит-тест:

import static org.testng.Assert.assertEquals;

import java.util.Arrays;
import java.util.List;

import org.testng.annotations.Test;

public class CombinationNGTest
{
  @Test
  public void testCombinaitonOnePick()
  {
    List<List<Integer>> result
            = Combination.pickKfromEach((List<List<Integer>>) Arrays.asList(
                    Arrays.asList(1, 2),
                    Arrays.asList(3, 4)),
                    1);

    assertEquals(result.size(), 4, result.toString());

    result = Combination.pickKfromEach((List<List<Integer>>) Arrays.asList(
            Arrays.asList(1, 2),
            Arrays.asList(3, 4),
            Arrays.asList(5, 6)),
            1);

    assertEquals(result.size(), 8, result.toString());

    result = Combination.pickKfromEach((List<List<Integer>>) Arrays.asList(
            Arrays.asList(1, 2),
            Arrays.asList(3, 4),
            Arrays.asList(5, 6),
            Arrays.asList(7, 8)),
            1);

    assertEquals(result.size(), 16, result.toString());

    List<List<String>> result2= Combination.pickKfromEach((List<List<String>>) Arrays.asList(
                    Arrays.asList("A", "B"),
                    Arrays.asList("C", "D")),
                    1);

    assertEquals(result2.size(), 4, result.toString());
  }

  @Test
  public void testCombinaitonMultiplePicks()
  {
    List<List<Integer>> result
            = Combination.pickKfromEach((List<List<Integer>>) Arrays.asList(
                    Arrays.asList(1, 2, 3),
                    Arrays.asList(4, 5, 6)),
                    2);

    assertEquals(result.size(), 9, result.toString());
  }
}

Ответы [ 2 ]

0 голосов
/ 11 октября 2018

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

Вот более простое решение для случая с двумя корзинами, обобщенное и использующее List s вместо массивов:

// Find all 2-item combinations consisting of 1 item picked from 
// each of 2 buckets
static <T> List<List<T>> pick1From2(List<List<T>> in)
{
    List<List<T>> result = new ArrayList<>();
    for (int i = 0; i < in.get(0).size(); ++i) {
        for (int j = 0; j < in.get(1).size(); ++j) {
            result.add(Arrays.asList(in.get(0).get(i), in.get(1).get(j)));
        }
    }
    return result;
}

Внешний цикл работает над всеми элементами первого сегментаи для каждого элемента первого сегмента внутренний цикл проходит над элементами второго блока.

Для трех блоков вы можете просто добавить третий уровень вложенности цикла:

// Find all 3-item combinations consisting of 1 item picked from
// each of 3 buckets 
static <T> List<List<T>> pick1From3(List<List<T>> in)
{
    List<List<T>> result = new ArrayList<>();
    for (int i = 0; i < in.get(0).size(); ++i) {
        for (int j = 0; j < in.get(1).size(); ++j) {
            for (int k = 0; k < in.get(2).size(); ++k)
                result.add(Arrays.asList(in.get(0).get(i), in.get(1).get(j), in.get(2).get(k)));
        }
    }
    return result;
}

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

Но этот подход ограничен тем фактом, что необходимая глубина вложенности цикла напрямую связана с количеством сегментов, которые нужно обработать: Конечно, вы можете добавить четвертый, пятый и т. Д. Уровень циклагнездо для обработки четырех, пяти или более ведер.Тем не менее, основная проблема остается: вы должны продолжать модифицировать код, чтобы он соответствовал постоянно увеличивающемуся количеству сегментов.

Решение дилеммы - единый алгоритм, который учитывает любое число, N блоков, эффективно имитируя циклы for, вложенные в уровни N.Массив из индексов N заменит управляющие переменные цикла N из вложенных for операторов N:

// Find all `N`-item combinations consisting 1 item picked from 
// each of an `N` buckets
static <T> List<List<T>> pick1fromN(List<List<T>> s)
{
    List<List<T>> result = new ArrayList<>();
    int[] idx = new int[s.size()];
    while (idx[0] < s.get(0).size()) {
        List<T> pick = new ArrayList(s.size());
        for (int i = 0; i < idx.length; ++i) {
            pick.add(s.get(i).get(idx[i]));
        }
        result.add(pick);
        int i = idx.length - 1;
        while (++idx[i] >= s.get(i).size() && i > 0) {
            idx[i] = 0;
            --i;
        }
    }
    return result;
}

Все индексы начинаются с нуля, и каждый из них выводитсяпо достижении размера соответствующего ведра.Для перехода к следующей комбинации (внутренний цикл while) последний индексный индекс увеличивается;если он имеет maxxed out, он сбрасывается в ноль, и следующий более высокий индекс увеличивается.Если следующий более высокий индекс также достигает максимума, он сбрасывается и вызывает увеличение следующего индекса и так далее.idx[0] никогда не сбрасывается после увеличения, поэтому внешний while может определить, когда idx[0] достиг максимума.

Сбор k предметов из каждого ведра - это в основном тот же процесс, за исключением наборов k-комбинаций ведер, заменяющих исходные ведра:

// Find all `N * k`-item combinations formed by picking `k` items
// from each of `N` buckets
static <T> List<List<T>> pickKfromEach(List<List<T>> sets, int k)
{
    List<List<List<T>>> kCombos = new ArrayList<>(sets.size());
    for (List<T> ms : sets) {
        kCombos.add(combinations(ms, k));
    }
    ArrayList<List<T>> result = new ArrayList<>();
    int[] indices = new int[kCombos.size()];
    while (indices[0] < kCombos.get(0).size()) {
        List<T> pick = new ArrayList<>(kCombos.size());
        for (int i = 0; i < indices.length; ++i) {
            pick.addAll(kCombos.get(i).get(indices[i]));
        }
        result.add(pick);
        int i = indices.length - 1;
        while (++indices[i] >= kCombos.get(i).size() && i > 0) {
            indices[i] = 0;
            --i;
        }
    }
    return result;
}

static <T> List<List<T>> combinations(List<T> s, int k) throws IllegalArgumentException
{
    if (k < 0 || k > s.size()) {
        throw new IllegalArgumentException("Can't pick " + k
            + " from set of size " + s.size());
    }
    List<List<T>> res = new LinkedList<>();
    if (k > 0) {
        int idx[] = new int[k];
        for (int ix = 0; ix < idx.length; ++ix) {
            idx[ix] = ix;
        }
        while (idx[0] <= s.size() - k) {
            List<T> combo = new ArrayList<>(k);
            for (int ix = 0; ix < idx.length; ++ix) {
                combo.add(s.get(idx[ix]));
            }
            res.add(combo);
            int ix = idx.length - 1;
            while (ix > 0 && (idx[ix] == s.size() - k + ix))
               --ix;
            ++idx[ix];
            while (++ix < idx.length)
                idx[ix] = idx[ix-1]+1;
        }
    }
    return res;
}

Как и в процедуре выбора, метод combinations использует массив индексов для перечисления комбинаций.Но индексы управляются немного по-другому.Индексы начинаются с {0, 1, 2, ..., k -1_} и достигают максимума, когда достигают значений { n - k , n - k + 1, ..., n }.Чтобы перейти к следующей комбинации, последний индекс, который еще не достиг максимума, увеличивается, а затем каждый следующий индекс сбрасывается до значения, указанного выше, плюс один.

0 голосов
/ 11 октября 2018

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

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

public static Object[] permuteAll(Object[] objs1, Object[][] objs2) {
    if(objs2.length == 1){
        return permuteAll(objs1, objs2);
    }else{
        return permuteAll(objs2[0], objs2[/*The rest of the objs[][]*/]]);
    }
}

public static Object[] permuteAll(Object[] objs1, Object[] objs2) {
    return ... //Your Code for 2 buckets goes here
}

Я бы также рекомендовал использовать Generics вместо класса Object, но в зависимости от того, как вы комбинируете свои объекты, вы можете не получить никакой реальной выгоды от этого ...

...