简体   繁体   中英

Algorithm to find a target sum of the sum of products of two lists of ordered integers

I am at work trying to write a script to parse poorly converted tables in text-form that originates from parsed pdf:s into csv:s. Essentially the headers are lengths of planks, the data is the number of planks and finally the total length of all the planks in the row is given.

Simplified example

1,0   2,0   3,0   4,0 5,0   total M
1      3    2     1         17,0

Since the layout varies wildly and I don't need to guarantee correctness I think there's a decent chance that just trying all valid combinations of number of planks times lengths added together and see which ones sum correctly should work well enough.

As a proof of concept I want to write a simple program that takes two lists of integers and looks for all valid sums of products to see that I don't get a combinatorial nightmare.

The rules for this toy problem then are.

Two lists of integers, the first [1..14], the second smallish integers (< 1000) and with 1 to 14 members. call them lengths and numPlanks

A target sum, which is found by summing the products of all the members of numPlanks with exactly one member of lengths and no two members of numPlanks can share a length. Searching through all such combinations and printing the combinations that matches the target.

Further, the members of both lists are ordered. If the first element of numPlanks is multiplied with the second element of lenghts, no other member of numPlanks can be multiplied with first element of lengths.

Example, in pseudo-code

lengths = [1, 2, 3, 4]
numPlanks = [10, 20]
target = 110

the program would then check 10 + 40, 10 + 60, 10 + 80, 20 + 60, 20 + 80, 30 + 80 to see which add up to the target and finally print out something like "10*30 + 20*40 = 110".

I've been trying to construct solutions but am stumped by only being able to think of nesting as many loops as there are members in numPlanks. Which seems terrible.

The program is written in java, so if anyone wants to point out anything language specific I'd be quite grateful, and anything else is of course fantastic as well.

Edit: sketching with pen and paper it seems the number of comparisons are related to Pascal's triangle. Eg, for lengths with two members and numPlacks with 0 to 2 members the number of comparisons are 1,2,1 for 0, 1 and 2 members in numPlanks respectively.

Given that I know that I have exactly 14 members in lengths in my actual problem and 1 to 14 members in numPlanks this would correspond to a worst case of 1716 comparisons. Which seems pretty ok.

Short answer: your estimate of the number of calculations is hopelessly optimistic, unless I'm misunderstanding your problem.

Suppose you have 14 elements in lengths and 14 elements in numPlanks . Since each length and each numPlank can only be used once (if I understand correctly), then you basically have 14*14 = 196 possible terms, and you need to find some combination of them that add up to your target.

Suppose you start with a guess that the solution includes a particular length and a particular numPlanks. That means you can cross off 13 other terms having the same numPlanks as your guess and 13 other terms having the same length as your guess. And, of course, you can cross off the term you guessed. That still leaves you with 169 terms to try to add to that guess.

So, you pick on. Now you cross off 12+13 more terms, like before, because they share a value with your guess at the 2nd term. Now, you've got 144 terms left... etc.

So, just to get all possible guesses of 3 terms, you have to look at 196*169*144 = 4.7 million possibilities.

Here's some Java code that generates solutions. This version has 14 elements in each array. It finds a solution after 64000 comparisons (far higher than your worst case estimate already). If you give it something unsolvable (eg, make all number lengths divisible by 10 and give it a target of 2051), then go get a cup of coffee and wait for the universe to end...

public class Tester {

    static int numComparisons = 0;

    public static class Term {
        final int length;
        final int numPlanks;

        public Term(final int length, final int numPlanks) {
            this.length = length;
            this.numPlanks = numPlanks;
        }

        @Override
        public String toString() {
            return "(" + this.length + "*" + this.numPlanks + ")";
        }
    }

    public static List<Term> getTerms(int target, List<Integer> lengthsList,
            List<Integer> numPlanksList, List<Term> currentTermList) {
        // System.out.println("... " + target + ", " + lengthsList + ", " +
        // numPlanksList + ", " + currentTermList);
        lengthsLoop: for (int l : lengthsList) {
            numPlanksLoop: for (int n : numPlanksList) {
                numComparisons++;
                if (numComparisons % 100 == 0) {
                    System.out.println("... numComparisons = " + numComparisons
                            + " .... " + currentTermList);
                }
                if ((l * n) > target) {
                    continue lengthsLoop;
                } else if (l * n == target) {
                    final List<Term> newCurrentTermList = new ArrayList<Term>(
                            currentTermList);
                    newCurrentTermList.add(new Term(l, n));
                    return newCurrentTermList;
                } else {
                    final int newTarget = target - (l * n);
                    final List<Integer> newLengthsList = new ArrayList<Integer>(
                            lengthsList);
                    newLengthsList.remove((Integer) l);
                    final List<Integer> newNumPlanksList = new ArrayList<Integer>(
                            numPlanksList);
                    newNumPlanksList.remove((Integer) n);
                    final List<Term> newCurrentTermList = new ArrayList<Term>(
                            currentTermList);
                    newCurrentTermList.add(new Term(l, n));
                    final List<Term> answer = getTerms(newTarget,
                            newLengthsList, newNumPlanksList,
                            newCurrentTermList);
                    if (answer.size() > 0) {
                        return answer;
                    }
                }
            }
        }

        // System.out.println("Over!");
        return new ArrayList<Term>();

    }

    public static void main(String[] args) {

        List<Integer> lengthsList = new ArrayList<Integer>(Arrays.asList(1, 2,
                3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14));
        Collections.sort(lengthsList, Collections.reverseOrder());
        List<Integer> numPlanksList = new ArrayList<Integer>(Arrays.asList(1,
                20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140));
        Collections.sort(numPlanksList, Collections.reverseOrder());
        int target = 2051;

        final List<Term> finalAnswer = getTerms(target, lengthsList,
                numPlanksList, new ArrayList<Term>());
        if (finalAnswer.size() > 0) {
            System.out.println("Final Answer:");
            System.out.println("=============");
            for (Term t : finalAnswer) {
                System.out.println(t.length + "*" + t.numPlanks);
            }
        } else {
            System.out.println("No solution");
        }
        System.out.println("numComparisons = " + numComparisons);

    }
}

Because the integer arrays are ordered, this should be a quick solution.

Testing with

    int[] lengths = { 1, 2, 3, 4 };
    int[] plankCount = { 10, 20 };
    int totalPlankLength = 110;

I got the following result:

    [10 x 3, 20 x 4]

Testing with

    int[] lengths = { 1, 2, 3, 4, 5, 6, 7 };
    int[] plankCount = { 10, 20, 30 };
    int totalPlankLength = 280;

I got the following results

    [10 x 1, 20 x 3, 30 x 7]
    [10 x 2, 20 x 4, 30 x 6]

Thanks to greybeard for the comment. In a made up example, it's likely that more than one answer fits. With the real data, it's less likely, but still possible.

I use binary to help me create a set of the number of possibilities of plank count times the lengths. There's nothing magic about binary, except that it solves the problem.

Let me illustrate with the simpler example. We have the following input:

    int[] lengths = { 1, 2, 3, 4 };
    int[] plankCount = { 10, 20 };
    int totalPlankLength = 110;

So, we need a way to get all the possible ways to multiply a plank count with a length.

First, I calculated the number of possibilities by calculating 2 to the lengths length power. In this example, we calculate 2 to the 4th power, or 16.

Since we're using an int, the maximum length of the lengths List is 30. If you want a longer lengths List, you would have to convert the ints to longs.

We don't need to look at all of the binary numbers between 15 and 0. We just need to look at the binary numbers that have plankCount length one bits. We look at the binary numbers in reverse order.

12 1100
10 1010
 9 1001
 6 0110
 5 0101
 3 0011

The decimal numbers on the left don't matter. The bit patterns on the right are what matter. The set of bit patterns show the number of ways you can multiply the plankCount by the lengths.

So, we'll perform 6 multiplications with the two plank counts, for a total of 12 multiplications. This happens quickly.

I do the multiplications and sum the products for each binary pattern to see if the sum is equal to the total plank length. If so, I write out those multiplications.

Here's the corrected code. Try it with 14 lengths, and see if it's fast enough for your needs.

package com.ggl.testing;

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

public class BoardLength {

    public static void main(String[] args) {
        BoardLength boardLength = new BoardLength();
        int[] lengths = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int[] plankCount = { 10, 20, 30 };
        int totalPlankLength = 360;
        List<List<PlankLength>> plankLength = boardLength.calculatePlankLength(
                lengths, plankCount, totalPlankLength);
        displayResults(plankLength);
    }

    private static void displayResults(List<List<PlankLength>> plankLength) {
        if (plankLength.size() <= 0) {
            System.out.println("[]");
        } else {
            for (List<PlankLength> list : plankLength) {
                System.out.println(Arrays.toString(list.toArray()));
            }
        }
    }

    public List<List<PlankLength>> calculatePlankLength(int[] lengths,
            int[] plankCount, int totalPlankLength) {
        List<List<PlankLength>> plankLength = new ArrayList<>();
        String formatString = "%" + lengths.length + "s";

        int maximum = (int) Math.round(Math.pow(2D, (double) lengths.length));
        for (int index = maximum - 1; index >= 0; index--) {
            int bitCount = Integer.bitCount(index);
            if (bitCount == plankCount.length) {
                String bitString = String.format(formatString,
                        Integer.toBinaryString(index)).replace(' ', '0');
                calculateTotalPlankLength(lengths, plankCount,
                        totalPlankLength, plankLength, bitString);
            }
        }

        return plankLength;
    }

    private void calculateTotalPlankLength(int[] lengths, int[] plankCount,
            int totalPlankLength, List<List<PlankLength>> plankLength,
            String bitString) {
        List<PlankLength> tempList = new ArrayList<>();
        int plankIndex = 0;
        int sum = 0;
        for (int bitIndex = 0; bitIndex < bitString.length(); bitIndex++) {
            if (bitString.charAt(bitIndex) == '1') {
                PlankLength pl = new PlankLength(lengths[bitIndex],
                        plankCount[plankIndex++]);
                sum += pl.getPlankLength();
                tempList.add(pl);
            }
        }

        if (sum == totalPlankLength) {
            plankLength.add(tempList);
        }
    }

    public class PlankLength {
        private final int length;
        private final int plankCount;

        public PlankLength(int length, int plankCount) {
            this.length = length;
            this.plankCount = plankCount;
        }

        public int getLength() {
            return length;
        }

        public int getPlankCount() {
            return plankCount;
        }

        public int getPlankLength() {
            return length * plankCount;
        }

        @Override
        public String toString() {
            return "" + plankCount + " x " + length;
        }

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM