9

Suppose I have K arrays of different sizes:

A1 = [X1, X2, X3 .... Xn]

A2 = [Y1, Y2, Y3 .... Ym] .. and K-2 more such arrays.

All arrays contain elements of same data type.

And then I have a function, F = f(Xn, Ym,...K-2 more such elements). The function basically requires exactly one element from each array, and my objective is to find a maximum value for this function. And also, which all elements make it maximum.

Important Info

Nature of function: All arrays are sorted (descending order). And first element in each array provides the best solution to the function (maximizes the function within its peer set). So, the ideal case would be picking up the first element from each array. But there is a constraint, which I can best describe as:

Suppose these elements are numbers, then I have to keep their total sum less than some constant value. So the constraint is:

Xn + Ym + ... and others <= Constant

EDIT: As @Spektre asked, the function is exponential in nature.

How can I solve this without brute force? Any pointers on solutions to similar existing problems would also help!

I understand divide & conquer, dynamic programming and linear programming upto the extent taught in Introduction to Algorithms-CLRS. So, if I can apply any of those, I am not able to figure that out with respect to this problem.

EDIT: An example function and dataset for the above problem:

Maximize: F = ax + by + cz

The above function was not very accurate representation of original problem.

UPDATE: Example function updated

F = xh(x)g(x) + yh(y)g(y) + zh(z)g(z)

h and g are non decreasing linear function of x/y/z. Range of h varies from 1 to 2. Range of g varies from 1 to 50. Domain of x,y,z is positive real numbers with average value in millions (consider 10 million as maximum for the example case ahead).

Example dataset (x,y,z are in millions):

x is in [20,18,17,15,12,9,8,5]

y is in [26,21,16,13,6,3,2,1]

z is in [45,42,41,40,12,3,2,1,0]

So the arrays contain random but sorted values.

And a,b,c > 1 So take a = 2, b=3 and c=4 for instance making the function as:

F = 2x + 3y + 4z

The constraint: x + y + z <= 50

I've updated the example function after Spektre's solution, but that algorithm should still be valid as function is still an increasing function of x,y,z.

Example code of h(), g() and arrays (in JavaScript)

function h(a) {
    var h = 1 + 0.0000001 * a;
    return h;
}

function g(a) {
    var g = (50 / 10000000) * a;
    return g;
}

function f(x, y, z) {
    var f = x * Math.pow(h(x), g(x)) + y * Math.pow(h(y), g(y)) + z * Math.pow(h(z), g(z));
    return f;
}

var maxSum = 5000000;

function isSumLessThanMax(x, y, z) {

    if (x + y + z <= maxSum) {
        return true;
    }
    else {
        return false;
    }
}

var x = [2000000, 1800000, 1700000, 1500000, 1200000, 900000, 800000, 500000];

var y = [2600000, 2100000, 1600000, 1300000, 600000, 300000, 200000, 100000];

var z = [4500000, 4200000, 4100000, 4000000, 1200000, 300000, 200000, 100000, 0];
Anmol Gupta
  • 2,797
  • 9
  • 27
  • 43
  • if it requires exactly one element, then why does your signature is `f(XAn, XBm ...)`? – Salvador Dali Apr 11 '16 at 09:35
  • 1
    @Anmol need to go away from PC for a time will be back. The main idea is sort the array by their significance to output value. and then iteratively construct possible solutions **only around the sum treshold** remembering the best solution. Do you have example function and dataset for testing? – Spektre Apr 11 '16 at 10:26
  • Your presentation is extremely confusing -- you say "Suppose I have n arrays of different sizes", and then you go on to specify that `A` (presumably the first of these arrays) has exactly n elements! Did you really mean that? Separately, you specify the function as `F = f(Xn, Ym ...)` -- it's not clear from this if the function takes n elements or n arrays. Please fix these problems and I'll undo my -1. – j_random_hacker Apr 11 '16 at 12:39
  • In the meantime, are the numbers in these arrays all relatively small integers, and is the function F always a sum having a term for each of the k arrays? If both of these things are true then there's a nice DP for solving it optimally in O(nkT) time, where T is the maximum value that can be obtained by adding together a single number from each of the k arrays, and each array has size <= n: Let g(i, x) be the maximum value that can be achieved on the subproblem that considers just the first i (i <= n) arrays, under the constraint that the chosen terms sum to at most x (x <= your threshold). – j_random_hacker Apr 11 '16 at 12:46
  • If `f` always increases(or decreases) and one variable doesn't depend on another variable (`f` don't have something like `x/y`). `x = 0, y = 0, z = 0, f = 3` `x = 1, y = 0, z = 0, f = 4, x_rate = 1` `x = 0, y = 1, z = 0, f = 5, y_rate = 2` `x = 0, y = 0, z = 1, f = 6, z_rate = 3` (or we can start from 1, or take 10 step - for example use 10 and 20 values to find rates) And now we can more effecienly get X,Y,Z...s. Just find maximum available value for array with max rate etc. Yeah, it's not completly, can be variants where this algorithm does not work. – ostapische Apr 11 '16 at 13:15
  • hope the improvements will make the code faster. – Sumeet Apr 11 '16 at 14:48
  • Please don't include pastebin links in your question. You need to put the code **in the question**, or the question is off-topic. – user229044 Apr 12 '16 at 12:05

4 Answers4

2

The example data you provided so far offers some opportunities for optimization:

First of all, rather than comparing x's, y's, and z's, use the intermediate calculation, x*h(x)^g(x), or a pre-calculated table-lookup of those values. Looking at rounded and proportionally reduced output for an easier visual, x / 100000 and Math.round(x * Math.pow(h(x), g(x)) / 100000), we see that some values are more than an order of magnitude greater than others.

xs = { 20, 18, 17, 15, 12,  9,  8, 5}
     {124, 80, 65, 43, 24, 13, 11, 6}

ys = { 26,  21, 16, 13, 6, 3, 2, 1}
     {525, 155, 52, 29, 7, 3, 2, 1}

zs = {    45,    42,    41,    40, 12, 3, 2, 1}
     {192306, 66268, 46965, 33467, 24, 3, 2, 1}

Group variables and their intermediate functional results as tuples according to a calibrated choice of ranges of magnitude k. For example, using our reduced overview, let's say k = 500:

[385 * k]    [133 * k]   ...  [2 * k]
(45,192306)  (42,66268)  ...  any x or y

It makes no sense to try different possibilities for zs when we have one value that's more than 150 times greater than any x or y. Once we've removed the larger variable, our search is now: maximize f'(x,y), where x + y <= 50 - 45).

If you foresee data results with extreme differences in magnitude, because f is actually linear in the intermediate calculation, call it i(x), you could implement a calibration at each round of elimination, until faced with choices within the same order of magnitude, where you would use brute-force with a greedy early exit.

גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • Thank you! I'll try this and update here. Btw, though calibration is a very simple operation, we can do it without any explicit calibration, right? Just take a magnitude k, and see if f(a1)/f(a2) < k, else reject a2. Was there any other intention of calibration? – Anmol Gupta Apr 12 '16 at 16:44
  • @Anmol What I meant by `calibration` is establishing a relevant magnitude that could illuminate an obvious choice (like 500 in my example). I guess the maximum few for each array could help determine that. – גלעד ברקן Apr 12 '16 at 16:54
  • IOW 1. discard all prefixes above the constraint's total from the arrays; 2. pick the biggest head *below* the current total; and 3. reduce the total by the head's value; 4. goto 1. This directly finds the answer. **This because `f (x,y,z)` is actually `= v (x) + v (y) + v (z)` with monotonically increasing `v`.** So we get a sorted tuple as the result. If there were equals to pick, must pick such that leaves the largest yet not picked on the rest of the arrays. For the last array, pick the largest element *not above* the remaining total. At each step, if impossible to pick, *backtrack*. – Will Ness Apr 13 '16 at 09:17
  • (addition to 2. above: put aside the picked-from array of course). ... *backtrack*, discard the picked element, and try again. This happens when 45 is picked (no further pick possible, as ~(5 < 50-45) ). So 42 is the next possible pick (but it could be from some other array, too). This suggest a lazy functional implementation, coded with the usual list's `flatmap`, and order-preserving *`merge`* used instead of plain `append` in the equal picks cases, as one possibility. Another is to maintain a candidates-set of arrays to pick from, winnowing it down by examining the *next* elements in each. – Will Ness Apr 13 '16 at 10:04
  • @WillNess would it be useful to keep a sum of minimums, that way avoid picking 45? Also, would it make a difference if the data is a 15-tuplet as the OP suggested, rather than a triple? – גלעד ברקן Apr 13 '16 at 11:36
  • I don't know if its only me but now I am unable to understand how calibration is working here! :( – Anmol Gupta Apr 13 '16 at 14:01
  • @Anmol don't worry about it - I just used the word, "calibrate," to mean picking a magnitude that could help determine if one of the arrays has a number that is so large as to be an obvious choice since all of the numbers chosen from other arrays put together could not make a larger one. "Calibration" refers to how the magnitude might change after eliminating a choice. `k = min(maximums of all remaining arrays to choose from)` Then if one array has a number that's a huge multiple of `k`, compared to the next largest from any of the other arrays, that is an obvious optimal choice. – גלעד ברקן Apr 13 '16 at 15:13
  • @גלעדברקן trying not to pick the wrong candidate in advance feels like too much trouble for its own good. as for 3- or 15- it shouldn't matter. I still have doubts though: imagine we have `total=42` and `[[22,2], [20,1], [20,1]]`. My process would pick `(22,1,1)` but what about `(20,20,2)`? It might produce bigger `f` value, so my process is *wrong*. Ordering is not guaranteed, it depends how far apart the arrays' entries are vis-a-vis how steep the exponentials are. with s=10**n,n=5 it's still `f(22*s,1*s,1*s) < f(20*s,20*s,2*s)`; only for n>=5.12 the sign flips to `>`. – Will Ness Apr 13 '16 at 21:09
  • so the process can be modified as: drop all prefixes above total; sort arrays by head, decreasing; for each head do my original process, modified to drop prefixes which are above the picked head (producing sorted tuples, for uniqueness). thus at each step we have (e.g.) 15, 14, 13, ... results which need to be sorted by `f` -- or just the one with the max `f` to be picked from them?). and duplicates complicate things, as we saw earlier. OTOH `f` can be built progressively by additions going backwards (1,2,3...) ; perhaps this can be exploited for efficiency. – Will Ness Apr 13 '16 at 21:23
  • @WillNess Should I take your comment about your process potentially picking (22,1,1) over (20,20,2) as a disinclination to follow the first suggestion in my answer to examine the values, `x*h(x)^g(x)` rather than `x`s ? If we follow it, then we're just maximizing basic addition. – גלעד ברקן Apr 13 '16 at 21:53
  • yes, the counterexample shows that the pick is not deterministic after all. – Will Ness Apr 13 '16 at 22:21
  • @WillNess perhaps you're misunderstanding? We subtract 45 (or 42 rather) to fit the max sum of variables, but the maximization process relates to arrays of sorted `x*h(x)^g(x)` values (e.g., 192306 is far greater than 525). Why not use those? We're just maximizing a basic sum in that case, no? – גלעד ברקן Apr 13 '16 at 23:08
  • perhaps. yes of course use the values, but for which pick? we can't know in advance, need both (all) possibilities. we can add the values gradually as picks are made, and sort the list of possibilities according to the current sum on each step. – Will Ness Apr 13 '16 at 23:18
1

Well now it is much more clearer. As the max sum is applied to input variables not the output values of f then it is much more simple. So here first simple approach in C++:

double MaxSum = 50.0;
double X[] = { 20.0,18.0,17.0,15.0,12.0,9.0,8.0,5.0 };     int Nx=sizeof(X)/sizeof(double);
double Y[] = { 26.0,21.0,16.0,13.0, 6.0,3.0,2.0,1.0 };     int Ny=sizeof(Y)/sizeof(double);
double Z[] = { 45.0,42.0,41.0,40.0,12.0,3.0,2.0,1.0,0.0 }; int Nz=sizeof(Z)/sizeof(double);
double f(double x,double y,double z)
    {
    return pow(2.0,x)+pow(3.0,y)+pow(4.0,z);
    }
void solve()
    {
    int cnt;    // just tested solutions counter
    int x,y,z;
    bool init;
    double sum,a,aa,xx,yy,zz,x1,y1,z1;
    // near max sum only
    cnt=0.0;
    init=true; sum=0.0;
    for (x=0;x<Nx;x++) { x1=X[x]; sum+=x1; if (sum<=MaxSum)
    for (y=0;y<Ny;y++) { y1=Y[y]; sum+=y1; if (sum<=MaxSum)
    for (z=0;z<Nz;z++) { z1=Z[z]; sum+=z1; if (sum<=MaxSum)
        {
        cnt++;                                          // update counter for debug purposes
        a=f(X[x],Y[y],Z[z]);                            // compute actual solution
        if ((init)||(aa<a)) { init=false; aa=a; xx=x1; yy=y1; zz=z1; }  // remeber the best one
        }
    if (sum          <=MaxSum) z=Nz; sum-=z1; }
    if (sum+Z[0]     <=MaxSum) y=Ny; sum-=y1; }
    if (sum+Z[0]+Y[0]<=MaxSum) x=Nx; sum-=x1; }
    // here:
    // cnt is number of tested solutions
    // Nx*Ny*Nz is number of all possibilities
    // found solution aa=f(xx,yy,zz)
    }

Output:

[   0.027 ms]Solution 19342813113834066800000000=f(5,3,42) tested 64 of 576 combinations

The idea is test only those combinations which sum is near MaxSum constraint. As you can see the loops are almost identical so you can nest as many of them as you want. If you got variable count of the input arrays then you can use

Algorithm:

  1. The program basically loops through "all possiblities" from the biggest one to lowest.

  2. Then test only if the target sum is smaller or equal to MaxSum.

  3. after that if partial sum is small enough that even if used the biggest values from unused arrays still fits into the MaxSum stop that loop to avoid un-necessary iterations. This effectively cut down the complexity from brute-force O(n^k) to O(g(n)^k) where n is average array size, k is the number of arrays and g(n) is average number of tested values per array (depends on the MaxSum,n,k and array values. In your test case g(n)=2)

Improvements:

Now if you got background information about the function f and know that some variables are more significant then others (in your test case the Z array) Then you can limit that variable on first few biggest values that still fits into that limit ignoring the small values of that array. This can be done recursively on other arrays too but VERY CAREFULLY so you do not miss the actual solution.

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • @fjardon I would be interested in the reason too but there is almost none chance for such response downvoters usually down vote (sometimes even not read fully) and go away for good. The more responsible usually add comment with what is wrong. – Spektre Apr 11 '16 at 13:04
  • @Spektre Thanks a lot. I am still comprehending it (trying it in Java). – Anmol Gupta Apr 11 '16 at 13:08
  • 1
    @Anmol are you sure this code formatting is better in this case? You do not see the symmetry of the indexes now so clear or it is just me? – Spektre Apr 11 '16 at 14:31
  • @Spektre Yes, I did realize this after suggesting the edit. Please edit it back to your original one if you can! – Anmol Gupta Apr 11 '16 at 14:32
  • 1
    @Anmol I rolled back to edit 4 (before the formatting changes – Spektre Apr 11 '16 at 14:44
  • it might be better to have your arrays sorted in decreasing order of head elements (at *each* step), so the biggest element will be picked first -- your solution is the very first one found this way (45+5 already =50, so it's 42,5,3) -- this works because exponential is *very* steep; and instead of one `sum` to have three, each inside its loop, each reflecting its state before the nested loop's additions. – Will Ness Apr 11 '16 at 15:02
  • @Spektre This does help and reduce the complexity, But, talking about my average case: I'll have 15 such arrays with an average of 30 elements per array. So, that makes the brute force complexity to 30^15. Assuming only 5 elements per array get tested (Elements of the array are generated on runtime so no control over them), then the complexity as per this algorithm will be: 5^15 which is still unsolvable. I wonder if the complexity can be reduced drastically in this case! – Anmol Gupta Apr 11 '16 at 15:40
  • @Anmol if the power base is different enough for each array then yes up to a point – Spektre Apr 11 '16 at 19:45
  • @Anmol give us one set of real data, arrays and bases, on pastebin or something. are bases always the same or do they vary too? – Will Ness Apr 11 '16 at 21:17
  • 1
    @WillNess I've updated the function and also put a pastebin. The domain and range of the data, and nature of function are now like real data. – Anmol Gupta Apr 12 '16 at 06:03
  • @Anmol the `g(),h()` functions are the same for all arrays or they have each different coefficients? If the latter then I am afraid you can not use further improvement of this by array (axis) separation unless done some very thorough analysis to detect which arrays are the most significant. – Spektre Apr 12 '16 at 07:48
  • @Spektre They are same for all the arrays. – Anmol Gupta Apr 12 '16 at 08:35
1

You may be interested in greedy algorithms. That's simple but useful idea. First my thought when I see such problem is try to approach using this technique.

Another approach is method of gradient descent.

George Sovetov
  • 4,942
  • 5
  • 36
  • 57
0

Dynamic programming is a technique that will work well in your case. Since you know a bit more about your data -- namely that the lists are sorted -- you can optimize the search to not go too far down a sub-optimal path.

This is not a quick answer with pseudocode. I highly recommend watching MIT's algorithms course in its entirety, but Lectures 19-22 relate to dynamic programming. Here is the playlist on youtube.

mfa
  • 5,017
  • 2
  • 23
  • 28