0

Example data:

targets = [-7.51, -0.32, -0.3, -0.9, -2.9, -1.2, -0.6, -1.2, -2.4, -0.96]
candidates = [-0.32, -0.9, -0.6, -1.4, -1.5, -1.8, -1.2, -0.35, -0.96, -2.52, -0.32, -0.6, -3.84, -0.6, -0.3, -0.6, -0.48]
Output example:
target_list = [3, 3, 9]
candidate_list = [1, 2, 3, 4, 5]
result = [{3: [3]}, {3: [1, 2]}, {9: [4, 5]}]

Condition:

  1. The sum of the target array is equal to the sum of the candidate array.
  2. All elements in the candidates' array can only be used once.
  3. Each target may be composed of 1 or n candidates.

Question: Is there an algorithm for each target to find and its combination in the candidates' array?

Pay attention: Sometimes you need to consider whether the candidate array is not perfect to find the subset combination of each target.

I have tried a combination of backtracking and deep search. This algorithm is effective when looking for a target, but when it is necessary to find a group target, it is very difficult to find at this time. This is similar, I don't have ideas yet.

I am here to seek help.

Here is my code, it ok for simple targets and candidates arrays, but not for more complex arrays.

 class CollectionCheck:

    def combinations(self, candidate_list, target_list):
        origin_candidate_list = copy.deepcopy(candidate_list)
        origin_target_list = copy.deepcopy(target_list)

        def combinationFinder(candidates, target):

            def dfs(pos, rest):
                nonlocal sequence
                if rest == 0:
                    ans.append(sequence[:])
                    return
                if pos == len(freq) or abs(rest) < abs(freq[pos][0]):
                    return

                dfs(pos + 1, rest)
                most = int(min(round(Decimal(rest) / Decimal(freq[pos][0]), 0), freq[pos][1]))
                for i in range(1, most + 1):
                    sequence.append(freq[pos][0])
                    dfs(pos + 1, round((rest - i * float(freq[pos][0])), 2))
                sequence = sequence[:-most]

            freq = sorted(collections.Counter(candidates).items(), reverse=True)
            ans = list()
            sequence = list()

            dfs(0, target)
            return ans

        def safe_remove(candidate_list, potential_combination):
            for value in potential_combination:
                if value in candidate_list:
                    candidate_list.remove(value)

        result = []
        target_list.sort(reverse=True)
        for target in target_list:
            potential_combination = combinationFinder(candidate_list, target)
            if potential_combination == []:
                err = {
                    "info": {
                        "Need to find target": str(target),
                        "List of potential candidates": str(candidate_list),
                        "Algorithm search and return": str(potential_combination)
                    },
                    "origin": {
                        "origin_candidate_list": str(origin_candidate_list),
                        "origin_target_list": str(origin_target_list)
                    }
                }
                err = json.dumps(err)
                return None, err
            else:
                result.append({target: potential_combination[0]})
                safe_remove(candidate_list, potential_combination[0])
        return result, None

The complex array example:

target_list = [-3.12, -3.12, -3.12, -30.6, -16.8, -28.0, -28.8, -18.6, -15.0, -21.0, -6.8, -22.8, -12.8, -22.8,
                   -32.0, -28.0, -16.8, -24.8, -10.8, -5.0, -72.8, -23.8, -42.6, -16.8, -22.8, -12.0, -34.8, -22.8,
                   -19.0, -22.8, -9.8, -45.2, -16.0, -23.0, -17.4, -19.6, -22.8, -28.8, -23.8, -13.8, -28.8, -11.0,
                   -24.0, -12.0, -3.0]

candidate_list = [-19.0, -19.0, -4.8, -0.8, -4.8, -4.8, -4.8, -3.12, -33.0, -4.0, -12.0, -4.8, -24.0, -4.0, -4.8,
                      -30.0, -17.0, -12.0, -4.8, -0.8, -12.0, -4.8, -24.0, -5.0, -24.0, -3.12, -3.2, -4.8, -4.0, -11.0,
                      -4.8, -4.8, -20.0, -6.4, -24.0, -4.8, -28.0, -18.0, -4.8, -4.0, -4.0, -19.0, -12.0, -24.0, -9.6,
                      -1.6, -4.8, -30.6, -18.0, -4.8, -12.0, -17.0, -5.0, -42.0, -24.0, -19.0, -6.0, -18.0, -68.0, -4.0,
                      -4.0, -18.0, -12.0, -4.8, -4.8, -4.8, -18.0, -18.0, -0.8, -11.0, -18.0, -3.0, -11.0, -4.8, -13.0,
                      -1.6, -12.0, -3.12, -6.0]

Solve problems with lingo, but now I don't know how to use python code to implement this formula.

model:
  sets:
    !row/1..21/:r;
    !col/1..108/:c;
    row/1..10/:r;
    col/1..17/:c;
    !row/1..4/:r;
    !col/1..8/:c;
    link(row, col):a;
  endsets

  data:
    !r = 10, 9, 8, 7;
    !c = 5, 5, 4, 5, 4, 4, 2, 5;
    r = -7.51, -0.32, -0.3, -0.9, -2.9, -1.2, -0.6, -1.2, -2.4, -0.96;
    c = -0.32, -0.9, -0.6, -1.4, -1.5, -1.8, -1.2, -0.35, -0.96, -2.52, -0.32, -0.6, -3.84, -0.6, -0.3, -0.6, -0.48;
    !r = 1915, 1205, 1370, 1110, 35, 1680, 200, 315, 100, 80, 30, 30, 120, 240, 2895, 1175, 1400, 100, 60, 255, 340;
    !c = 30, 300, 30, 210, 120, 70, 60, 50, 200, 35, 30, 35, 30, 540, 175, 120, 140, 90, 60, 70, 300, 360, 30, 35, 120, 300, 30, 540, 60, 80, 240, 30, 200, 35, 25, 150, 180, 180, 80, 120, 80, 30, 60, 60, 800, 60, 240, 340, 120, 35, 200, 35, 100, 180, 30, 80, 100, 30, 60, 25, 35, 60, 30, 25, 30, 30, 30, 35, 70, 70, 180, 70, 200, 35, 90, 25, 30, 25, 160, 180, 540, 120, 80, 1020, 25, 400, 540, 35, 60, 80, 260, 100, 60, 30, 35, 25, 35, 400, 30, 360, 80, 120, 25, 30, 200, 30, 30, 340;
    !r = -19.15, -12.05, -13.7, -11.1, -0.35, -16.8, -2.0, -3.15, -1.0, -0.8, -0.3, -0.3, -1.2, -2.4, -28.95, -11.75, -14.0, -1.0, -0.6, -2.55, -3.4;
    !c = -0.3, -3.0, -0.3, -2.1, -1.2, -0.7, -0.6, -0.5, -2.0, -0.35, -0.3, -0.35, -0.3, -5.4, -1.75, -1.2, -1.4, -0.9, -0.6, -0.7, -3.0, -3.6, -0.3, -0.35, -1.2, -3.0, -0.3, -5.4, -0.6, -0.8, -2.4, -0.3, -2.0, -0.35, -0.25, -1.5, -1.8, -1.8, -0.8, -1.2, -0.8, -0.3, -0.6, -0.6, -8.0, -0.6, -2.4, -3.4, -1.2, -0.35, -2.0, -0.35, -1.0, -1.8, -0.3, -0.8, -1.0, -0.3, -0.6, -0.25, -0.35, -0.6, -0.3, -0.25, -0.3, -0.3, -0.3, -0.35, -0.7, -0.7, -1.8, -0.7, -2.0, -0.35, -0.9, -0.25, -0.3, -0.25, -1.6, -1.8, -5.4, -1.2, -0.8, -10.2, -0.25, -4.0, -5.4, -0.35, -0.6, -0.8, -2.6, -1.0, -0.6, -0.3, -0.35, -0.25, -0.35, -4.0, -0.3, -3.6, -0.8, -1.2, -0.25, -0.3, -2.0, -0.3, -0.3, -3.4;
  enddata
  
  min=@sum(row(i):(r(i)-@sum(col(j):a(i, j)*c(j))) ^ 2);
  
  @for(row(i):@for(col(j):@bin(a(i, j))));
  @for(row(i):@sum(col(j):a(i, j) * c(j)) = r(i));
  @for(col(j):@sum(row(i):a(i, j)) = 1);

end

Lingo screenshot 0

Lingo screenshot 1

Tony Joy
  • 1
  • 2
  • Please add programming-language tag – c0der Sep 28 '21 at 05:55
  • This problem is NP-complete. For example it includes https://en.wikipedia.org/wiki/Multiway_number_partitioning as a special case. – btilly Sep 28 '21 at 15:44
  • @btilly Thank you for your answer. I read the content in the link. The scenario described in it is very close to my problem, but I can't find an effective algorithm to judge whether there is an optimal solution that I want to find. – Tony Joy Sep 29 '21 at 07:17
  • @btilly I have checked a lot of information. The simulated annealing algorithm may be able to solve my problem, but the information I found is all about concepts, and there is no defined code that allows me to imitate the implementation. – Tony Joy Sep 29 '21 at 08:24
  • Simulated annealing normally finds an approximate solution. You might be better of with a DP solution, depending on the input range. – n. m. could be an AI Sep 29 '21 at 09:24
  • @ n. 1.8e9-where's-my-share m. Yes, it seems that DP is a better solution, but I don't know how to judge a subset as a "potentially possible answer" in the calculation process, because the criterion for judging is the remaining targets and candidates after removing this subset, able to match exactly. This seems to be an infinite loop. – Tony Joy Sep 29 '21 at 11:47
  • @n.1.8e9-where's-my-sharem. What's more, if I find that this choice is not the best choice in the process of searching downwards, I should go back to which state before, and then continue to look down from this state. And there is still a problem, how to judge that is the state is ideal, it should be traced back to this state instead of other states. – Tony Joy Sep 29 '21 at 11:51
  • Do you understand how to solve the straight subset sum problem with DP? It is equivalent to your problem with exactly 2 targets. – n. m. could be an AI Sep 29 '21 at 13:03
  • @n.1.8e9-where's-my-sharem. I think maybe you don't understand my problem. When only one target needs to be found, this problem is easy to solve, but when there are multiple targets, the problem becomes very difficult. It is not only to find out a subset at every step, it also needs to ensure that this subset is the "optional choice", because if it is not the optional choice, but there will also be a problem of contention for candidate elements in the follow-up. – Tony Joy Sep 30 '21 at 07:17
  • I understand your problem. In the straight subset-sum DP algo, you in effect at each step `n` build a set of target sums that can be achieved with elements `0..n-1` (you either include an element in the target, or you don't). The reason why it works is that you collapse different ways to achieve the same sum into just one element. Now in your problem, your decision is not whether to include the current element, but to which target to include it. You in effect build a set of multi-targets, and collapse identical multi-targets to one element. – n. m. could be an AI Sep 30 '21 at 07:49
  • @n.1.8e9-where's-my-sharem. If you say that, I understand a little bit. I will try again according to your ideas, thanks. – Tony Joy Sep 30 '21 at 08:02
  • But I just notice that you have non-integer weights, this makes DP problematic... With your toy example it is easy to demo: with element `1` you can achieve only `(0,0,1)` targets, with `1` and `2` you can achieve `[(0,0,3)`,`(0,1,2)]`. Add `3`, and you can get `[(0,0,6),(0,1,5),(0,2,4),(0,3,3),(1,2,3)]`. Note there is only one `(0,3,3)` because it doesn't matter if we use `(0,1+2,3)` or `(0,3,1+2)`. When we add `4` we will only have one `(0,3,7)` because `(0,3+4,3)` is the same as `(0,3,3+4)`. This is how DP works. But with fractional weights all of this breaks down. – n. m. could be an AI Sep 30 '21 at 08:02
  • Have you considered using a MILP solver ? This problem sould be fairly easy to formalize to a MILP solver like Z3. And given that it is an NP problem this is a got shot. – Max Ostrowski Sep 30 '21 at 11:12
  • @n.1.8e9-where's-my-sharem. I tried for several days but still didn't solve it. I decided to deal with other tasks temporarily, only when I have time to deal with this problem, thank you for your advice. – Tony Joy Oct 08 '21 at 02:10
  • @n.1.8e9-where's-my-sharem. @ Max Ostrowski We solve this problem with lingo, but we don't know how to convert Lingo's formula into python code. – Tony Joy Jan 04 '22 at 11:34
  • Here is code in lingo: model: sets: row/1..10/:r; col/1..17/:c; link(row, col):a; endsets data: r = -7.51, -0.32, -0.3, -0.9, -2.9, -1.2, -0.6, -1.2, -2.4, -0.96; c = -0.32, -0.9, -0.6, -1.4, -1.5, -1.8, -1.2, -0.35, -0.96, -2.52, -0.32, -0.6, -3.84, -0.6, -0.3, -0.6, -0.48; enddata min=@sum(row(i):(r(i)-@sum(col(j):a(i, j)*c(j))) ^ 2); @for(row(i):@for(col(j):@bin(a(i, j)))); @for(row(i):@sum(col(j):a(i, j) * c(j)) = r(i)); @for(col(j):@sum(row(i):a(i, j)) = 1); end – Tony Joy Jan 04 '22 at 11:36

0 Answers0