Skip to content Skip to sidebar Skip to footer

Determining The Right Combination

Struggling to write the code...getting lost in the loops. I've got there 2 data sets, for example: var elements = [ {'id':'21.U2duHWiX.0zu.E0C','amount':'345'}, {'i

Solution 1:

OK. Check this script:

// this part is only needed if your ids are arbitrary, and can contain the join-character
// if not, you could replace this by the identity function
var count = 0, numericids = {};
function getNumericId(id) {
    return id in numericids ? numericids[id] : numericids[id] = count++;
}

// returns the same (reversible) id for all similiar [unsorted] key combinations
function id(keys) {
    return keys.map(getNumericId).sort().join('-');
    // you might remove the getNumericId part if distinct without
}

// now, build a map that holds the summed amount for each single (sub)combination
var amounts = {};
function add(amount, keys) {
    var key = id (keys);
    if (key in amounts)
        amounts[key] += amount;
    else
        amounts[key] = amount;
}
for (var i=0; i<elements.length; i++) // each element is a single combination
    add(Number(elements[i].amount), [elements[i].id]);
for (var i=0; i<elements_in_combination.length; i++)
    add(Number(elements_in_combination[i].amount), elements_in_combination[i].combination);
// so we have the amounts in a good accessible structure now

Next, we will need to find all partitions of a set. Wow. This is an NP-hard problem and not easily solvable. What was easy for three elements (the five combinations in your question) gets more and more complicated, for 6 elements you already have 203 possibilities (Bell numbers. For further reading, I've found

OK, let's solve this recursively, caching results and getting the minimum value:

// first, get the set for which we want to determine the result:
var initialset = elements.map(function(el){return getNumericId(el.id);}).sort();
// set up a cache for minimum value results:
var cache = {};

function partition(set) {
// returns an array of all partitionings into two parts
    var results = [[[set[0]],[]]];
    for (var i=1; i<set.length; i++)
        for (var j=0, l=results.length; j<l; j++) {
            // repeat the array with duplicates
            results[j+l] = [results[j][0].slice(),results[j][1].slice()];
            // but while we push to the first part in the first half
            results[ j ][0].push(set[i]);
            // we push to the second part in the second half
            results[j+l][1].push(set[i]);
        }
    return results;
}

function getMin(set) {
    var key = set.join('-');
    if (key in cache) // quick escape
        return cache[key];
    var result = {amount:Infinity, set:null};
    if (key in amounts) // there is a combination with this
        result = {amount:amounts[key], set:[key]};
    var divisions = partition(set);
    // for all possibilities to divide the set in two parts
    // (unless the first, which is [set, []])
    for (var i=1; i<divisions.length; i++) {
        // get the minimal amounts of both parts
        var first = getMin(divisions[i][0]);
        var second = getMin(divisions[i][1]);
        var sum = first.amount + second.amount;
        if (sum < result.amount) // and find new minima
            result = {amount:sum, set: first.set.concat(second.set)};
    }
    return cache[key] = result;
}
// And now invoke this monster!
if (!initialset.length) throw new Error("When searching for nothing you would find nothing");
var min = getMin(initialset);
cache = null, amounts = null; // and immediately free the memory

So, here is your result! It contains the sum you wanted in the amount property and the used set of combination-keys in the set property.

Building your array of elements is easy now:

var elemArr = [];
function addElem(el, comb) {
    if (min.set.indexOf(id(comb)) >= 0)
         elemArr.push(el);
}
for (var i=0; i<elements.length; i++) // each element is a single combination
    addElem(elements[i], [elements[i].id]);
for (var i=0; i<elements_in_combination.length; i++)
    addElem(elements_in_combination[i], elements_in_combination[i].combination);

return elemArr; // We've done it!

The script returns the correct results for all your examples:

  • 329 (21.U2duHWiX.0zu.E0C) + 328 (21.U2duHWiX.A5q.E0C)
  • 314 (21.U2duHWiX.0zu.E0C) + 313 (21.U2duHWiX.A5q.E0C) + 312 (21.U2duHWiX.P1y.E0C)
  • 344 (21.U2duHWiX.A5q.E0C) + 314 (21.U2duHWiX.0zu.E0C) + 311 (21.U2duHWiX.J3e.E0C) + 312 (21.U2duHWiX.P1y.E0C) - a [B]-[A,C,D] combination :-)

Note that these may not be the sole solutions, as there is only the first of many possible minima found


Solution 2:

function find_matches(elements, elements_in_combination) {
    var matches = ();
    var element_ids = ();
    for (var i = 0; i < elements.length; i++) {
        element_ids.push(elements[i].id);
    }
    element_ids.sort();
    for (i = 0; i < elements_in_combination.length; i++) {
        combs = elements_in_combination[i].combination.slice(0).sort();
        if (array_equal(element_ids, combs)) {
            matches.push(elements_in_combination[i].amount;
        }
    }
    return matches;
}

See this question for how to implement array_equal().


Solution 3:

ok.. this might be an option, but because I have no idea what the terms for the "best combination" are, I couldn't reduce it any further.

the following code should produce an object that contains each element as an object. Each element object will then contain another object for each unique amount (from low to high). The amount object then contains the combinations possible for that amount.

ie. container object (finalElements) - element id - order & amounts - combinations:

var finalElements = { };

// sort:
elements_in_combination.sort( eic_sortOnAmmount );

function eic_sortOnAmmount( a, b ) {
    return a.amount - b.amount;
}

// parse the elements array and create an object for each element
// add the initial amount as a key:
for( var i in elements ) {
    finalElements[ elements[i].id ] = { order:[] };
    finalElements[ elements[i].id ][ elements[ i ].amount ] = null;
}

// parse the elements_in_combination array
// if the id matches one of the elements in finalElements
// add its amount and combination
for( var i in elements_in_combination ) {
    if( finalElements.hasOwnProperty( elements_in_combination[ i ].id ) ) {
        if( finalElements[ elements_in_combination[ i ].id ].hasOwnProperty( elements_in_combination[ i ].amount ) ) {
            finalElements[ elements_in_combination[ i ].id ][ elements_in_combination[ i ].amount ].push( elements_in_combination[ i ].combination );
        } else {
            finalElements[ elements_in_combination[ i ].id ].order.push(elements_in_combination[ i ].amount);
            finalElements[ elements_in_combination[ i ].id ][ elements_in_combination[ i ].amount] = [ elements_in_combination[ i ].combination ];
        }
    }
}

example usage:

console.log(finalElements["21.U2duHWiX.0zu.E0C"].order[0]); //produces 314
console.log(finalElements["21.U2duHWiX.0zu.E0C"][finalElements["21.U2duHWiX.0zu.E0C"].order[0]]); // produces the combinations for 314

Hope this helps - btw: the null amount is the original element amount.


Post a Comment for "Determining The Right Combination"