Lostsoul
Lostsoul

Reputation: 26017

Creating a list of all possible percentages of items?

My goal is to give the program a few items(Strings), a range, and target percent and let it give me all possible percentages of each item. For example, Imagine you go to the grocery store and have a basket of Apples & Pears you want to know all the percentages you could have using ALL items(not a full solution, I'm doing this by hand): {Apple:50, Pears:50}, {Apple:75, Pears:25}, {Apple:90, Pears:10},etc.

If I do the same thing with a range of 20-50(meaning the highest value a single item can have is 50% and the lowest 20%) then the only result is: {Apple:50, Pears:50} (since there are only 2 items and it cannot exceed 50% weight)

I thought it had similar traits as an knapsack problem with a few big differences since there are no values/weights associated with the items(but like knapsack problem trying to fit items in a knapsack I’m trying to fit values within a target_percent, 100%). I’m also having trouble applying general dynamic programming ideas as well since I can’t figure out how to break the problem down(typical knapsack problems build up results and then ‘cache’ results to reuse but if if I have a list of X items, I need all X items to be used within a range).

I can do this via brute force but I don’t feel like its efficient because it just tries everything so the bounds that I’m using aren’t being used to make it efficient at all(for example if apple is 75% then there’s no reason Pear should exceed 25%..bounds are size of list, range, and target_percent..I might have 20-30 list items with a range of 5-20 or maybe 50 items with a range from 1-5..or anything in between I want to play around with how many complete results I can get as fast as possible. I have not shown the target_percent part in the question because I can set it up that once I understand how to solve the problem, but basically all the examples assume 100% max, but sometimes you may already have 20% oranges in your basket and see how you can use Apples/Pears to fill up the rest 80%).

My questions are, How can I approach this(any ideas logic to use, examples or proxy problems I can look up)? Is dynamic programming appropriate for this problem or the fact that I cannot break this into smaller chucks a problem(remember because its always includes all items in the list, its not building up)? If someone can point me to the right direction, I’m willing to study any topics that might help(After spending 2 days trying to figure this out,I’m just not sure if the Dynamic programming route is correct). Also is there a name for this type of problem(I looked up knapsack problems, integer partitioning, combinatorics but none of them seemed to fit)?

Here's my(broken) brute force approach(its not actually working as expected but maybe gives you an idea of the brute force method):

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


public class brute_force_percent_returner {
    static String[] data = new String[]{"Apple", "Pears"};
    static int[] coeff = new int[data.length];
    static ArrayList<int[]> queue = new ArrayList<int[]>();

    public static void main(String[] args) {
        System.out.println("Starting");
        recursion(0,data);
        for (int[] item : queue) {
            for (int item2 = 0; item2<data.length; item2++) {
                System.out.print(data[item2] + " = " + item[item2] + " ");
            }
            System.out.println();
        }
    }

    private static void recursion(int k, String[] data2) {
        // this is not exactly working
        for (String item: data2) {
            for (int x = 0; x<5;x++) {
                int[] coeff_temp = Arrays.copyOf(coeff, coeff.length);
                coeff_temp[k] = x;
                queue.add(coeff_temp);
            }
        }
        if (k == data.length-1) {
            return;
        } else {
            recursion(k+1, data2);
        }
    }
}

If it helps the solution I was trying to create was somewhat based on this one(its a knapsack problem but seems to be super quick for large number of variables but in this care the items its processing are the items in the list whereas in my case the list is just strings):

public class TurboAdder {
    private static final int[] data = new int[] { 5, 10, 20, 25, 40, 50 };

    private static class Node {
        public final int index;
        public final int count;
        public final Node prevInList;
        public final int prevSum;
        public Node(int index, int count, Node prevInList, int prevSum) {
            this.index = index;
            this.count = count;
            this.prevInList = prevInList;
            this.prevSum = prevSum;
        }
    }

    private static int target = 100;
    private static Node sums[] = new Node[target+1];

    // Only for use by printString.
    private static boolean forbiddenValues[] = new boolean[data.length];

    public static void printString(String prev, Node n) {
        if (n == null) {
            System.out.println(prev);
        } else {
            while (n != null) {
                int idx = n.index;
                // We prevent recursion on a value already seen.
                if (!forbiddenValues[idx]) {
                    forbiddenValues[idx] = true;
                    printString((prev == null ? "" : (prev+" + "))+data[idx]+"*"+n.count, sums[n.prevSum]);
                    forbiddenValues[idx] = false;
                }
                n = n.prevInList;
            }
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < data.length; i++) {
            int value = data[i];
            for (int count = 1, sum = value; count <= 100 && sum <= target; count++, sum += value) {
                for (int newsum = sum+1; newsum <= target; newsum++) {
                    if (sums[newsum - sum] != null) {
                        sums[newsum] = new Node(i, count, sums[newsum], newsum - sum);
                    }
                }
            }
            for (int count = 1, sum = value; count <= 100 && sum <= target; count++, sum += value) {
                sums[sum] = new Node(i, count, sums[sum], 0);
            }
        }
        printString(null, sums[target]);

    }
}

Upvotes: 2

Views: 597

Answers (2)

amaidment
amaidment

Reputation: 7298

I'm pretty confident that the brute-force approach is the best way to go - at least, that's the way I would do it (which is by no means the same thing...).

Here's an attempt to work with the recursive approach that I've got working (although I haven't tested it with high values for weightsNo. This works on the basis that you're interested in the combinations of weights, rather than the permutations of weights - although the switch is relatively straightforward.

public static Set<int[]> getPossiblePercentageWeights(int weightsNo, int min, int max){
  return recusiveFixWeight(weightsNo, 100, min, max);
}

private static Set<int[]> recusiveFixWeight(int weightsNo, int sum, int min, int max){
  Set<int[]> weightsSet = new LinkedHashSet<int[]>();
  if (weightsNo>2){
    for (int iWeight=min; iWeight<=max; iWeight++){
      Set<int[]> subSet = recusiveFixWeight(weightsNo-1, sum-iWeight, min, iWeight);
      for (int[] subWeights : subSet){
        int[] weights = new int[weightsNo];
        weights[0] = iWeight;
        System.arraycopy(subWeights, 0, weights, 1, subWeights.length);
        weightsSet.add(weights);
      }
    }
  } else {
    int iMax = Math.min(max, sum/weightsNo);
    for (int iWeight=min; iWeight<=iMax; iWeight++){
      int jWeight = sum-iWeight;
      if (jWeight>=min && jWeight<=max){
        weightsSet.add(new int[]{iWeight,jWeight});
      }
    }
  }
  return weightsSet;
}

That said, having looked at the results, it looks like there should be an algorithm to determine how many weightSets there given a weightsNo, min and max, and from there it should be fairly straightforward to fill those in with possible values. That said, I can't quite figure it out at the moment. (Or indeed, whether it would be any quicker than the brute-force approach...)

Upvotes: 1

goat
goat

Reputation: 31823

This sounds like homework so I'm extra reluctant to help you too much, but here's an approach.

to define the ranges, make a couple hash maps, like

lower bounds = {apples  => 20, pears => 40,  oranges => 0}
upper bounds = {apples  => 50, pears => 100, oranges => 30}

if you think about it, every final (valid) combination would at the very least, have the contents defined by the lower bound map. so call that the base combination.

next, figure out the theoretical max of each type you can potentially add to the base combination. this is just another map

{apples  => 30, pears => 60,  oranges => 30}

figure how many total items you can add to the base map, which is 100 - the sum of all the lower bound values, in the example its 40.

now, you need to generate the combinations. You'll probably find recursion the easiest way to do it. ill demonstrate the remaining algorithm with pseudo code and hardcoded stuff to improve clarity, although you'll need to write a generic, recursive version of it.

totalItemsToAdd = 40 //as calculated via baseCombo.sumOfEntries()


for (i=0; i<maxApples; i++) {
    combo = clone the base combination
    combo.apples += i;
    remainingItemsToAdd = totalItemsToAdd - i;
    if (remainingItemsToAdd > 0) {
        for (j=0; j<maxPears; j++) {
            combo.pears += j;
            // and so on, recursively
        }
    }

    results.append(combo)
}

notice how it only generates valid combinations by keeping track of how many more items are possible for each of the combinations. So, this wouldnt be brute force, and it would actually do the minimum work needed to generate the set of combinations.

Upvotes: 1

Related Questions