Генерировать комбинации, упорядоченные по атрибуту - PullRequest
4 голосов
/ 07 февраля 2009

Я ищу способ создания комбинаций объектов, упорядоченных по одному атрибуту. Я не думаю, что я ищу лексикографический порядок ... Я попытаюсь привести пример. Допустим, у меня есть список объектов A, B, C, D со значениями атрибутов, которые я хочу заказать, будучи 3,3,2,1. Это дает объекты A3, B3, C2, D1. Теперь я хочу сгенерировать комбинации из 2 объектов, но их нужно упорядочить по убыванию:

  • A3 B3
  • A3 C2
  • B3 C2
  • A3 D1
  • B3 D1
  • C2 D1

Генерация всех комбинаций и их сортировка недопустимы, поскольку сценарий реального мира включает в себя большие наборы и миллионы комбинаций. (набор 40, порядок 8), и мне нужны только комбинации выше определенного порога.

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

РЕДАКТИРОВАТЬ - Мой первоначальный вопрос был не очень точным ... Мне не нужны эти комбинации, просто подумал, что это поможет выделить комбинации выше порога. Чтобы быть более точным, в приведенном выше примере, давая порог 5, я ищу информацию о том, что данный набор производит 1 комбинацию с суммой 6 (A3 B3) и 2 с суммой 5 (A3 C2, B3 C2). Мне на самом деле не нужны сами комбинации.

Я искал проблему с подмножеством сумм, но если я правильно понял данное динамическое решение, оно только даст вам информацию, есть ли заданная сумма или нет, а не количество сумм.

Спасибо

Ответы [ 6 ]

2 голосов
/ 07 февраля 2009

На самом деле, я думаю, вы хотите хотите лексикографический порядок, но скорее нисходящий, чем восходящий. Дополнительно:

  • Из вашего описания мне не ясно, что A, B, ... D играют какую-либо роль в вашем ответе (за исключением, возможно, в качестве контейнера для значений).
  • Я думаю, что ваш пример вопроса прост: «Для каждого целого числа не менее 5, вплоть до максимально возможной суммы двух значений, сколько различных пар из набора {3, 3, 2, 1} имеют суммы этого целого числа? «
  • Интересной частью является досрочное спасение, когда невозможно найти возможное решение (оставшиеся достижимые суммы слишком малы).

Я опубликую пример кода позже.

Вот пример кода, который я обещал, с несколькими замечаниями:

public class Combos {

    /* permanent state for instance */
    private int values[];
    private int length;

    /* transient state during single "count" computation */
    private int n;
    private int limit;
    private Tally<Integer> tally;
    private int best[][];  // used for early-bail-out

    private void initializeForCount(int n, int limit) {
        this.n = n;
        this.limit = limit;
        best = new int[n+1][length+1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= length - i; ++j) {
                best[i][j] = values[j] + best[i-1][j+1];
            }
        }
    }

    private void countAt(int left, int start, int sum) {
        if (left == 0) {
            tally.inc(sum);
        } else {
            for (
                int i = start;
                i <= length - left
                && limit <= sum + best[left][i];  // bail-out-check
                ++i
            ) {
                countAt(left - 1, i + 1, sum + values[i]);
            }
        }
    }

    public Tally<Integer> count(int n, int limit) {
        tally = new Tally<Integer>();
        if (n <= length) {
            initializeForCount(n, limit);
            countAt(n, 0, 0);
        }
        return tally;
    }

    public Combos(int[] values) {
        this.values = values;
        this.length = values.length;
    }

}

Предисловие:

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

Для краткости я взял несколько ярлыков, которые не являются хорошей практикой для "настоящего" кода:

  • Это не проверяет массив нулевых значений и т. Д.
  • Я предполагаю, что массив значений уже отсортирован в порядке убывания, необходимом для техники раннего спасения. (Хороший производственный код будет включать сортировку.)
  • Я помещаю временные данные в переменные экземпляра вместо того, чтобы передавать их в качестве аргументов среди частных методов, поддерживающих count. Это делает этот класс не потокобезопасным.

Пояснение:

Экземпляр Combos создается с массивом целых чисел (в порядке убывания) для объединения. Массив value устанавливается один раз для каждого экземпляра, но можно сделать несколько вызовов на count с различными размерами и ограничениями населения.

Метод count запускает (в основном) стандартный рекурсивный обход уникальных комбинаций n целых чисел из values. Аргумент limit дает нижнюю границу по интересующим суммам.

Метод countAt проверяет комбинации целых чисел из values. Аргумент left - это количество целых чисел, составляющих n целых в сумме, start - позиция в values, из которой нужно искать, а sum - частичная сумма.

Механизм раннего освобождения основан на вычислении best, двумерного массива, который задает «наилучшую» сумму, достижимую из данного состояния. Значение в best[n][p] является наибольшей суммой n значений, начинающихся в позиции p оригинала values.

рекурсия countAt заканчивается, когда накапливается правильная популяция; это добавляет текущий sum (n значений) к tally. Если countAt не достиг нижнего предела, он сместит values из позиции start, чтобы увеличить текущий частичный sum, пока:

  • достаточное количество позиций остается в values для достижения указанной совокупности, а
  • оставшийся best (самый большой) промежуточный итог достаточно велик, чтобы сделать limit.

Пример прогона с данными вашего вопроса:

    int[] values = {3, 3, 2, 1};
    Combos mine = new Combos(values);
    Tally<Integer> tally = mine.count(2, 5);
    for (int i = 5; i < 9; ++i) {
        int n = tally.get(i);
        if (0 < n) {
            System.out.println("found " + tally.get(i) + " sums of " + i);
        }
    }

выдаст указанные вами результаты:

found 2 sums of 5
found 1 sums of 6

Вот код Tally:

public static class Tally<T> {
    private Map<T,Integer> tally = new HashMap<T,Integer>();
    public Tally() {/* nothing */}
    public void inc(T key) {
        Integer value = tally.get(key);
        if (value == null) {
            value = Integer.valueOf(0);
        }
        tally.put(key, (value + 1));
    }
    public int get(T key) {
        Integer result = tally.get(key);
        return result == null ? 0 : result;
    }
    public Collection<T> keys() {
        return tally.keySet();
    }
}
1 голос
/ 25 сентября 2012

Я написал класс для работы с общими функциями для работы с биномиальным коэффициентом, к которому относится ваша проблема. Он выполняет следующие задачи:

  1. Выводит все K-индексы в хорошем формате для любого N, выбирающего K, в файл. K-индексы могут быть заменены более описательными строками или буквами. Этот метод делает решение проблемы такого типа довольно тривиальным.

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

  3. Преобразует индекс в отсортированной таблице биномиальных коэффициентов в соответствующие K-индексы.

  4. Использует Mark Dominus метод для вычисления биномиального коэффициента, который с гораздо меньшей вероятностью переполняется и работает с большими числами.

  5. Класс написан на .NET C # и предоставляет способ управления объектами, связанными с проблемой (если таковые имеются), используя общий список. Конструктор этого класса принимает значение bool, называемое InitTable, которое при значении true создаст общий список для хранения управляемых объектов. Если это значение равно false, таблица не будет создана. Таблицу не нужно создавать для выполнения 4 вышеуказанных методов. Методы доступа предоставляются для доступа к таблице.

  6. Существует связанный тестовый класс, который показывает, как использовать класс и его методы. Он был тщательно протестирован с 2 случаями, и никаких известных ошибок нет.

Чтобы прочитать об этом классе и загрузить код, см. Таблицу биномиального коэффициента .

0 голосов
/ 07 февраля 2009

Если вы используете C #, есть довольно хорошая библиотека обобщений здесь . Обратите внимание, что генерация некоторых перестановок не в лексикографическом порядке

0 голосов
/ 07 февраля 2009

Вот рекурсивный подход к подсчету количеству этих подмножеств: мы определяем функцию count(minIndex,numElements,minSum), которая возвращает количество подмножеств размера numElements, чья сумма не менее minSum, содержащая элементы с индексами minIndex или выше.

Как и в постановке задачи, мы сортируем наши элементы в порядке убывания, например, [3,3,2,1], и назовите первый индекс нулем, а общее количество элементов N. Мы предполагаем, что все элементы неотрицательны. Чтобы найти все 2 подмножества с суммой не менее 5, мы вызываем count(0,2,5).

Пример кода (Java):

int count(int minIndex, int numElements, int minSum)
{
    int total = 0;

    if (numElements == 1)
    {
        // just count number of elements >= minSum
        for (int i = minIndex; i <= N-1; i++)
            if (a[i] >= minSum) total++; else break;
    }
    else
    {
        if (minSum <= 0)
        {
            // any subset will do (n-choose-k of them)
            if (numElements <= (N-minIndex))
                total = nchoosek(N-minIndex, numElements);
        }
        else
        {
            // add element a[i] to the set, and then consider the count
            // for all elements to its right
            for (int i = minIndex; i <= (N-numElements); i++)
                total += count(i+1, numElements-1, minSum-a[i]);
        }
    }

    return total;
}

Кстати, я выполнил вышеупомянутое с массивом из 40 элементов и подмножествами размера 8 и последовательно получил результаты менее чем за секунду.

0 голосов
/ 07 февраля 2009

Мне очень жаль (после всех этих разъяснений в комментариях) сказать, что я не смог найти эффективного решения этой проблемы. Я пытался в течение последнего часа безрезультатно.

Причина (я думаю) в том, что эта проблема очень похожа на такие проблемы, как проблема коммивояжера. До тех пор, пока вы не попробуете все комбинации, невозможно узнать, какие атрибуты будут добавлены к порогу.

Кажется, нет хитрого трюка, который может решить этот класс проблем.

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

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

0 голосов
/ 07 февраля 2009

Проверьте этот вопрос в stackoverflow: Алгоритм возврата всей комбинации s

Я также использовал приведенный ниже код Java для генерации всех перестановок, но его можно легко использовать для генерации уникальной комбинации с указанным индексом.

public static <E> E[] permutation(E[] s, int num) {//s is the input elements array and num is the number which represents the permutation

    int factorial = 1;

    for(int i = 2; i < s.length; i++)
        factorial *= i;//calculates the factorial of (s.length - 1)

    if (num/s.length >= factorial)// Optional. if the number is not in the range of [0, s.length! - 1] 
        return null;

    for(int i = 0; i < s.length - 1; i++){//go over the array

        int tempi = (num / factorial) % (s.length - i);//calculates the next cell from the cells left (the cells in the range [i, s.length - 1])
        E temp = s[i + tempi];//Temporarily saves the value of the cell needed to add to the permutation this time 

        for(int j = i + tempi; j > i; j--)//shift all elements to "cover" the "missing" cell
            s[j] = s[j-1];

        s[i] = temp;//put the chosen cell in the correct spot

        factorial /= (s.length - (i + 1));//updates the factorial

    }

    return s;
}
...