Оптимизация итеративного кода целочисленного раздела

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

public class Summands {
    private static HashMap<Integer, HashSet<List<Integer>>> results;
    private static HashMap<Integer, HashSet<String>> recursiveResults;

    private static void sort(int[] a) {
        //Radix sort for int array
        int i, m = a[0], exp = 1, n = a.length;
        int[] b = new int[n];
        for (i = 1; i < n; i++) {
            if (a[i] > m) {
                m = a[i];
            }
        }
        while (m / exp > 0) {
            int[] bucket = new int[n];

            for (i = 0; i < n; i++)
                bucket[(a[i] / exp) % n]++;
            for (i = 1; i < n; i++)
                bucket[i] += bucket[i - 1];
            for (i = n - 1; i >= 0; i--)
                b[--bucket[(a[i] / exp) % n]] = a[i];
            for (i = 0; i < n; i++)
                a[i] = b[i];
            exp *= n;
        }
    }

    private static void generateResults(int n) {
        //iterative partitioning
        results.put(n, new HashSet<>());
        results.get(n).add(new ArrayList<>());
        for (List<Integer> list : results.get(n)) {
            list.add(n);
        }
        for (int i = 1; i <= Math.floorDiv(n, 2); i++) {
            //get all 2 summands partitions
            int a = n - i;
            results.get(n).add(Arrays.asList(i, a));
        }
        if (n > 1) {
            //Get the rest of the partitions
            HashSet<List<Integer>> set = new HashSet<>(results.get(n));
            for (List<Integer> equ : set) {
                if (equ.size() > 1) {
                    if (equ.get(1) > 1) {
                        HashSet<List<Integer>> temp = results.get(equ.get(1));
                        for (List<Integer> k : temp) {
                            List<Integer> tempEquList = new ArrayList<>(k);
                            tempEquList.add(equ.get(0));
                            int[] tempEqu = tempEquList.stream().mapToInt(Integer::intValue).toArray();
                            sort(tempEqu);
                            results.get(n).add(Arrays.stream(tempEqu).boxed().collect(Collectors.toList()));
                        }
                    }
                }
            }
        }
    }

    private static void recursivePartition(int n) {
        //recursively partition
        recursiveResults.put(n, new HashSet<>());
        partition(n, n, "", n);
    }

    private static void partition(int n, int max, String prefix, int key) {
        //recursive method for partitioning
        if (n == 0) {
            recursiveResults.get(key).add(prefix);
            return;
        }

        for (int i = Math.min(max, n); i >= 1; i--) {
            partition(n - i, i, prefix + " " + i, key);
        }
    }

    public static void main(String[] args) {
        //get number of partitions to get
        int target = Integer.valueOf(args[0]);
        //time the iterative version
        long time1 = System.currentTimeMillis();
        results = new HashMap<>(target);
        //loop until done
        for (int i = 1; i <= target; i++) {
            System.out.println(i);
            generateResults(i);
        }
        //time both methods
        long time2 = System.currentTimeMillis();
        recursiveResults = new HashMap<>(target);
        for (int i = 1; i <= target; i++) {
            //loop until done
            System.out.println(i);
            recursivePartition(i);
        }
        long time3 = System.currentTimeMillis();
        System.out.println("Iterative time: " + String.valueOf(time2 - time1));
        System.out.println("Recursive time: " + String.valueOf(time3 - time2));
        /*for(Integer key : results.keySet()){
        //for ensuring proper amount of partitions for lower numbers. Primarily for testing
            System.out.println(key + ": " + results.get(key).size());
        }*/
    }
}

person Justin Covell    schedule 24.03.2016    source источник
comment
Я думаю, вам придется предоставить некоторый контекст. Что вы подразумеваете под целочисленным разделом? Покажите примеры ввода и вывода.   -  person Jim Garrison    schedule 24.03.2016
comment
en.wikipedia.org/wiki/Partition_(number_theory)   -  person Justin Covell    schedule 25.03.2016
comment
Есть ли какая-то особая причина, по которой вы не используете явную формулу, приведенную в статье в Википедии? Это похоже на простую работу по запоминанию, а не на безумно сложную вещь, которую вы построили. Примечание: поскольку ваш код работает, хотя и медленно, вам может повезти, разместив его на Code Review. Обязательно найдутся люди, которые с радостью оптимизируют ваш код как есть, но если вам повезет, некоторые люди могут изучить основы (математику/алгоритмику). Просто не ожидайте, что кто-то будет анализировать этот запутанный беспорядок без малейшего объяснения.   -  person DarthGizka    schedule 27.03.2016
comment
Если вы добавите тег C++ к своему вопросу, я опубликую для вас оптимизированное решение.   -  person George Robinson    schedule 30.06.2019


Ответы (1)


Вы можете сгенерировать набор комбинаций слагаемых указанного числа, т. е. целочисленный раздел, используя методы mapToObj и reduce. Сначала подготовьте наборы массивов слагаемых, а затем последовательно умножьте пары этих наборов и получите декартово произведение.

Попробуйте онлайн!

int n = 7;
Set<int[]> partition = IntStream.range(0, n)
        // prepare sets of arrays of summands
        .mapToObj(i -> IntStream.rangeClosed(1, n - i)
                .mapToObj(j -> new int[]{j})
                // Stream<TreeSet<int[]>>
                .collect(Collectors.toCollection(
                        // comparing the contents of two arrays
                        () -> new TreeSet<>(Arrays::compare))))
        // intermediate output, sets of arrays of summands
        .peek(set -> System.out.println(
                set.stream().map(Arrays::toString).collect(Collectors.joining())))
        // sequential summation of pairs of sets up to the given number
        .reduce((set1, set2) -> set1.stream()
                // combinations of inner arrays
                .flatMap(arr1 -> {
                    // sum of the elements of the first array
                    int sum = Arrays.stream(arr1).sum();
                    // if the specified number is reached
                    if (sum == n) return Arrays.stream(new int[][]{arr1});
                    // otherwise continue appending summands
                    return set2.stream() // drop the combinations that are greater
                            .filter(arr2 -> Arrays.stream(arr2).sum() + sum <= n)
                            .map(arr2 -> Stream.of(arr1, arr2)
                                    .flatMapToInt(Arrays::stream)
                                    .sorted().toArray()); // the sorted array
                }) // set of arrays of combinations
                .collect(Collectors.toCollection( // two arrays that differ
                        // only in order are considered the same partition
                        () -> new TreeSet<>(Arrays::compare))))
        // otherwise an empty set of arrays
        .orElse(new TreeSet<>(Arrays::compare));
// final output, the integer partition of the specified number
partition.stream().map(Arrays::toString).forEach(System.out::println);

Промежуточный вывод, наборы массивов слагаемых:

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

Окончательный вывод, целочисленный раздел указанного числа:

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

См. также: Построение перестановки, которая эффективно суммируется с числом

person Community    schedule 30.04.2021