The goal is to find the optimal description of a subset of countries, using a combination of individual countries and groups of countries that are predefined.
Set of countries is comprised of all 2 letter ISO country codes, (US/DE/NL/etc)
Groups of countries are fixed, for example:
- GROUP1: NL,BE,LU
- GROUP2: NL,BE,LU,CZ,US,FI,NO,GI,FR,DK
I want to have a representation of this list of countries:
- CZ,US,FI,NO,GI,FR,DK,DE
I would like to shorten this using the defined groups, to the representation with the least amount of elements. For this example that would be:
- +GROUP2 +DE -GROUP1
Because if we expand this back out we get:
- Add GROUP2: NL,BE,LU,CZ,US,FI,NO,GI,FR,DK
- Add DE
- Remove GROUP1: NL,BE,LU Which brings us back to CZ,US,FI,NO,GI,FR,DK,DE
Any pointers to libraries or examples are welcome in any programming language
I'm at a loss what a good approach would be. I've tried looking into existing algorithms, comparable problems, but I can't seem to wrap my head around the problem and find a good fit.
Just to illustrate a simple brute force solution that will obviously not scale:
let countries = ['C1', 'C2', 'C3', 'C4', 'C5', 'C6'] let groups = { G1: ['C1', 'C2', 'C3', 'C4', 'C5'], G2: ['C1', 'C4'], } let output = getBest(['C2', 'C3', 'C5', 'C6']) // output == ["+C6", "+G1", "-G2"] function getBest(input) { const ids = countries.concat(Object.keys(groups)) let best = input for (const t of combinations(ids)) { if (expand(t, groups).sort().toString() == input.toString()) { if (t.length < best.length) best = [...t] } } return best } // Expands short form to long form function expand(s, groups) { return Array.from( s.sort().reduce((acc, curr) => { let symbol = curr[0] let id = curr.slice(1) if (groups[id]) { curr = groups[id] } else { curr = [id] } if (symbol == '+') { return new Set([...acc, ...curr]) } else { return new Set([...acc].filter((a) => !curr.includes(a))) } }, new Set()) ) } // Yields all possible short form options function* combinations(array) { array = ['+', '-'].reduce((acc, curr) => { return acc.concat(array.map((s) => curr + s)) }, []) for (const subset of subsets(array)) { yield subset } } // Creates powerset of array function* subsets(array, offset = 0) { while (offset < array.length) { let first = array[offset++] for (let subset of subsets(array, offset)) { subset.push(first) yield subset } } yield [] }