My application references a database object that acts as a catalog. It's a catalog of items that can be crafted if the user has the necessary components. Here is a small sample of the catalog:
const itemCatalog = {
"bramble_vest" : {
"components" : [ "Chain Vest", "Chain Vest" ],
"name" : "Bramble Vest"
},
"guardian_angel" : {
"components" : [ "B.F. Sword", "Chain Vest" ],
"name" : "Guardian Angel"
},
"hextech_gunblade" : {
"components" : [ "B.F. Sword", "Needlessly Large Rod" ],
"name" : "Hextech Gunblade"
},
"locket_of_the_iron_solari" : {
"components" : [ "Chain Vest", "Needlessly Large Rod" ],
"name" : "Locket of the Iron Solari"
},
"morellonomicon" : {
"components" : [ "Giant's Belt", "Needlessly Large Rod" ],
"name" : "Morellonomicon"
},
"sunfire_cape" : {
"components" : [ "Chain Vest", "Giant's Belt" ],
"name" : "Sunfire Cape"
},
"zekes_herald" : {
"components" : [ "B.F. Sword", "Giant's Belt" ],
"name" : "Zeke's Herald"
}
}
When the user has the necessary components for any given item, the user can assemble that item. The user is awarded components arbitrarily and randomly, but how the user receives the components is not relevant to my question. Suffice to say that the user's components are put into an array on the client, which is then used to determine which items the user can assemble:
let myComponents = [
"B.F. Sword",
"Chain Vest",
"Giant's Belt",
"Chain Vest",
"Needlessly Large Rod"
]
I have written a block of code that determines which items are possible with the elements in myComponents
. That's fairly straightforward, even though it isn't particularly concise or stylish.
With the components listed in myComponents
all of the items in this sample of itemCatalog
are possible. However, they are not simultanesouly possible. The reason for this, of course, is that there are not enough components for all the items.
I need logic that can determine which items are simultaneously possible, given the components in myComponents
when referenced against itemCatalog
. The output should be an array of arrays. Each inner array would be a list of simultaneously possible catalog items. In this case, with the components currently in myComponents
it would look like this:
[
["Bramble Vest", "Hextech Gunblade"],
["Bramble Vest", "Morellonomicon"],
["Bramble Vest", "Zeke's Herald"],
["Guardian Angel", "Locket of the Iron Solari"],
["Guardian Angel", "Morellonomicon"],
["Guardian Angel", "Sunfire Cape"],
["Hextech Gunblade", "Sunfire Cape"],
["Locket of the Iron Solari", "Sunfire Cape"],
["Locket of the Iron Solari","Zeke's Herald"]
]
Below is my current logic. There's a lot of logging there to help sift through, but the main issue with the function buildSimultaneousItems()
is that once an item is checked against another item during iteration, those two items aren't checked again. I don't want to get into it too much, as I don't want to scare people away with information overload. It's all pretty straightforward, despite its ugliness. The main thing is that the expected output is above. Please feel free to ask questions.
// A catalog of items that can be assembled using components.
// The app uses this as a reference. This catalog is larger in the app, with many more items.
const itemCatalog = {
"bramble_vest" : {
"components" : [ "Chain Vest", "Chain Vest" ],
"name" : "Bramble Vest"
},
"guardian_angel" : {
"components" : [ "B.F. Sword", "Chain Vest" ],
"name" : "Guardian Angel"
},
"hextech_gunblade" : {
"components" : [ "B.F. Sword", "Needlessly Large Rod" ],
"name" : "Hextech Gunblade"
},
"locket_of_the_iron_solari" : {
"components" : [ "Chain Vest", "Needlessly Large Rod" ],
"name" : "Locket of the Iron Solari"
},
"morellonomicon" : {
"components" : [ "Giant's Belt", "Needlessly Large Rod" ],
"name" : "Morellonomicon"
},
"sunfire_cape" : {
"components" : [ "Chain Vest", "Giant's Belt" ],
"name" : "Sunfire Cape"
},
"zekes_herald" : {
"components" : [ "B.F. Sword", "Giant's Belt" ],
"name" : "Zeke's Herald"
}
}
// Components the user currently has
let myComponents = [
"B.F. Sword",
"Chain Vest",
"Giant's Belt",
"Chain Vest",
"Needlessly Large Rod"
]
// Returns array of possible items with provided component combinations (myComponents)
getPossibleItems = (arr) => {
let possibleItems = [];
for (const possItem in arr) {
if (doArraysMatch(arr[possItem].components, myComponents) == true) {
possibleItems.push(arr[possItem].name);
}
}
return possibleItems;
}
// Returns array of components at corresponding indices that correspond to the array returned in the above function
getPossItemsComponents = (arrA, arrB) => {
let possItemsComponents = []
for (const item in arrA) {
for (const combItem in arrB) {
console.log(arrB[combItem].name, ": ",arrB[combItem].components);
if (arrA[item] == arrB[combItem].name) {
possItemsComponents.push(arrB[combItem].components);
}
}
}
return possItemsComponents;
}
// Attempts to return an array of arrays. Each inner array is a list of items that can be
// assembled SIMULTANEOUSLY with the provided components (myComponents)
buildSimultaneousItems = () => {
let terms = [];
possibleItems = getPossibleItems(itemCatalog);
possibleItemsComponents = getPossItemsComponents(possibleItems, itemCatalog);
for (let i = 0; i < possibleItems.length; i++) {
let simultaneousItems = [];
let simultaneousItemsComponents = [];
simultaneousItems.push(possibleItems[i]);
console.log(JSON.stringify(possibleItems[i]), ": ", JSON.stringify(possibleItemsComponents[i]), "-----------------------")
simultaneousItemsComponents.push(possibleItemsComponents[i]);
//console.log(possibleItemsComponents[i][0])
for (let j = 0; j < possibleItems.length; j++) {
console.log("Does myItems", JSON.stringify(myComponents), " contain ",JSON.stringify(simultaneousItemsComponents[0].concat(possibleItemsComponents[j])), " for ", JSON.stringify(possibleItems[j]),this.containsAllItems(myComponents, simultaneousItemsComponents[0].concat(possibleItemsComponents[j])))
while (containsAllItems(myComponents, simultaneousItemsComponents[0].concat(possibleItemsComponents[j]))) {
simultaneousItems.push(possibleItems[j]);
console.log("Add ", JSON.stringify(possibleItemsComponents[j]), " to ", JSON.stringify(simultaneousItemsComponents[0]))
simultaneousItemsComponents[0].push(possibleItemsComponents[j][0]);
simultaneousItemsComponents[0].push(possibleItemsComponents[j][1]);
}
}
terms.push(simultaneousItems);
}
console.log(terms)
}
// Utility functions for comparing arrays -------------------------- //
doArraysMatch = (subset, superset) => {
const subsetCount = _.countBy(subset);
const supersetCount = _.countBy(superset);
return _.every(subsetCount, (count, value) => supersetCount[value] >= count);
}
containsAllItems = (arrA, arrB) => {
arrA.forEach(elA => {
if (arrB.includes(elA)) {
arrB.splice(arrB.indexOf(elA), 1);
}
})
if (arrB.length == 0) {
return true;
} else {
return false;
}
}
buildSimultaneousItems()
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>