In order to check if all continents are included at least once, you need to keep track of the continents you’ve found so far.
Since only the continent
property is used, I’m going to simplify this array:
const list = [
{ continent: "Africa" },
{ continent: "Americas" },
{ continent: "Asia" },
{ continent: "Europe" },
{ continent: "Oceania" },
];
You’re also resetting the continents
parameter to an array in the question post, rather than use what has been passed.
The function call should look like containsContinents(list, [ "Africa", "Americas", "Asia", "Europe", "Oceania" ])
already, so setting the continents
array inside the function is redundant.
Keeping track of found results is easy with Sets.
You can obtain a Set of all used continents and all expected continents by mapping like this:
const usedContinents = new Set(list.map(({ continent }) => continent));
const expectedContinents = new Set(continents);
Now, you’re looking for a way to check if all expected continents occur in the used continents, but it’s not clear what is supposed to happen with used continents that are not included in the expected continents.
If your only criterion is “for all continents of the expected continents, the continent exists in the used continents”, then you have to check whether expectedContinents
is a subset of usedContinents
.
If an additional criterion is “the set of expected continents is the same as the set of used continents” (i.e. no used continent exists that doesn’t exist in the expected continents), then simply checking if the set sizes are the same is sufficient.
This function checks the subset relation using Array.from
, every
, and has
:1
const isSubset = (a, b) => Array.from(a).every((elem) => b.has(elem));
The full code now looks like this:
const list = [
{ continent: "Africa" },
{ continent: "Americas" },
{ continent: "Asia" },
{ continent: "Europe" },
{ continent: "Oceania" }
];
const isSubset = (a, b) => Array.from(a).every((elem) => b.has(elem));
const allContinentsRepresented = (list, continents) => {
const usedContinents = new Set(list.map(({ continent }) => continent));
const expectedContinents = new Set(continents);
return isSubset(expectedContinents, usedContinents);
// Alternative:
// return isSubset(expectedContinents, usedContinents) && expectedContinents.size === usedContinents.size;
};
console.log(allContinentsRepresented(list, [ "Africa", "Americas", "Asia", "Europe", "Oceania" ]));
If you want to exclude all unexpected continents, include the additional expectedContinents.size === usedContinents.size
check.
In that case, if the list contains an additional { continent: "Mu" }
, but the passed continents
array doesn’t, the allContinentsRepresented
function will return false
.
1: The New Set methods TC39 proposal will allow a simple a.isSubsetOf(b)
or b.isSupersetOf(a)
in the future.
Others have suggested to check continents.every((continent) => list.some((listItem) => listItem.continent === continent))
.
This has a quadratic runtime complexity (i.e. O(n²)), because the some
has to perform a linear search through the array, but so does the every
.
Since some
is called for each element, the time complexity is multiplied: O(n · n).
The approach with Set
s is approximately linear (i.e. O(n)), as Sets use constant lookup time internally (like hash maps / hash tables).
See also: Javascript ES6 computational/time complexity of collections.
The every
call performs a linear pass through the array, but the lookup with has
requires approximately constant time.
Essentially, this means that the Set approach is more efficient.
(Note that Array.from
, the two new Set
calls, and the map
call all perform a linear pass, but these calls are not nested; they’re not performed for every element within another linear pass, so the runtime complexity isn’t affected by all these calls.)