I'm 'new' (kind of) to javascript and I like making small "games" for myself. I've been trying to create a FIFA World Cup simulator and I want to add 31 random teams to the groups besides qatar. I ran into the issue of needing to check to see if multiple of the same team are in the tournament at once. Does anyone know an easy way to fix this without creating fifty if statements?
The only way I might know to fix this is creating an if statement to check every team (all 31 teams besides Qatar), which seems to repetitive to be the only way.
There are a few ways:
Use a Set and check if the team is already in the set (theSet.has(team)) before adding the team (theSet.add(team)).
Build an array of all 30 teams, shuffle it, and then draw your teams sequentially from the shuffled array.
Here is a short version using arrays, if I understood your question correctly. Please let me know if you meant something else or you want something more.
const teams = ["England","Holland","Morocco","France","Argentina","Portugal","Brazil","Senegal","Switzerland","Japan","Australia","Croatia","USA","Spain","Germany","Ecuador","South Korea","Cameroon","Poland","Uruguay","Tunisia","Mexico","Belgium","Ghana","Saudi Arabia","Iran","Costa Rica","Denmark","Serbia","Wales","Canada","Qatar"
];
const groups = Array.from({length: 8}, () => []); //create array of 8 empty sub-arrays
teams.sort(() => Math.random() - 0.5); //shuffle 'teams' array randomly
teams.forEach((team, i) => groups[i % 8].push(team)); //iterate over the 'teams' array and push each team into one of the 8 groups
console.log(groups);
This prints the eight groups randomly every time like so:
[
[ 'Switzerland', 'Spain', 'Portugal', 'Uruguay' ],
[ 'Iran', 'Ghana', 'France', 'Poland' ],
[ 'England', 'Saudi Arabia', 'Serbia', 'Denmark' ],
[ 'Mexico', 'Morocco', 'South Korea', 'Ecuador' ],
[ 'Costa Rica', 'Tunisia', 'Senegal', 'Australia' ],
[ 'Qatar', 'Japan', 'Brazil', 'Croatia' ],
[ 'Canada', 'Wales', 'Cameroon', 'Belgium' ],
[ 'Germany', 'Holland', 'USA', 'Argentina' ]
]
Related
I have two arrays that need merging in Javascript. They are arranged as follows:
arrayA = [town1A, town2A, town3A];
arrayB = [town3B, town5B];
Each town is an object with a townName: 'town1' (matching the object variable name). Each town also has an array of occupants: [{}, {}] which each have their own personName, and a status: 'dead' or 'alive'.
My goal, is that after merging, the new array will contain every unique town according to townName (town3B and town3A both have townName : 'town3').
arrayC = [town1, town2, town3, town5]
Any new towns in arrayB (i.e., town5) should be added directly to the list. Any towns with the same name (i.e., town3) should combine their lists of occupants, but remove any "dead" people. ArrayB has priority over ArrayA when determining status, as it is "overwriting" the old data. For example:
arrayA.town3.occupants = [{name: 'Bob', status: 'alive'}, {name: 'Joe', status: 'alive'}];
arrayB.town3.occupants = [{name: 'Bob', status: 'dead'}, {name: 'Alice', status: 'alive'}];
arrayC.town3.occupants = [{name: 'Joe', status: 'alive'}, {name: 'Alice', status: 'alive'}];
I'm just struggling with the logic sequence process here and need a nudge to figure out what tools to use. Currently I'm trying to work with Lodash's _.merge and _.union in some combination. It seems I can use _.mergeWith or _.unionBy to "nest" the merging steps without resorting to manually looping over the arrays, but their usage is going over my head. If a solution exists that uses one of those, I would like to see an example to learn better how they work.
Edit: I was asked for the entire contents of an example arrayA and arrayB:
arrayA = [
{
townName: 'town1',
occupants: [
{name: 'Charlie', status: 'alive'},
{name: 'Jim', status: 'dead'}
]
},
{
townName: 'town2',
occupants: [
{name: 'Rachel', status: 'alive'},
]
},
{
townName: 'town3',
occupants: [
{name: 'Bob', status: 'alive'},
{name: 'Joe', status: 'alive'}
]
}
];
arrayB = [
{
townName: 'town3',
occupants: [
{name: 'Bob', status: 'dead'},
{name: 'Alice', status: 'alive'}
]
},
{
townName: 'town5',
occupants: [
{name: 'Sam', status: 'dead'},
{name: 'Ray', status: 'alive'},
{name: 'Bob', status: 'alive'},
]
}
];
The output I expect is:
arrayC = [
{
townName: 'town1',
occupants: [
{name: 'Charlie', status: 'alive'},
]
},
{
townName: 'town2',
occupants: [
{name: 'Rachel', status: 'alive'},
]
},
{
townName: 'town3',
occupants: [
{name: 'Joe', status: 'alive'},
{name: 'Alice', status: 'alive'}
]
},
{
townName: 'town5',
occupants: [
{name: 'Ray', status: 'alive'},
{name: 'Bob', status: 'alive'},
]
}
];
I managed to find a consistent way to do this (thanks to #Enlico for some hints). Since _.mergeWith() is recursive, you can watch for a specific nested object property and handle each property differently if needed.
// Turn each array into an Object, using "townName" as the key
var objA = _.keyBy(arrayA, 'townName');
var objB = _.keyBy(arrayB, 'townName');
// Custom handler for _.merge()
function customizer(valueA, valueB, key) {
if(key == "occupants"){
//merge occupants by 'name'. _.union prioritizes first instance (so swap A and B)
return _.unionBy(valueB, valueA, 'name');
//Else, perform normal _.merge
}
}
// Merge arrays, then turn back into array
var merged = _.values(_.mergeWith(objA, objB, customizer));
// Remove dead bodies
var filtered = _.map(merged, town => {
town.occupants = _.filter(town.occupants, person => {return person.status == "alive";});
return town;
});
The complexity with this problem is that you want to merge on 2 different layers:
you want to merge two arrays of towns, so you need to decide what to do with towns common to the two arrays;
when handling two towns with common name, you want to merge their occupants.
Now, both _.merge and _.mergeWith are good candidates to accomplish the task, except that they are for operating on objects (or associative maps, if you like), whereas you have vectors of pairs (well, not really pairs, but objects with two elements with fixed keys; name/status and townName/occupants are fundamentally key/value) at both layers mentioned above.
One function that can be useful in this case is one that turns an array of pairs into an object. Here's such a utility:
arrOfPairs2Obj = (k, v) => (arr) => _.zipObject(..._.unzip(_.map(arr, _.over([k, v]))));
Try executing the following
townArr2townMap = arrOfPairs2Obj('townName', 'occupants');
mapA = townArr2townMap(arrayA);
mapB = townArr2townMap(arrayB);
to see what it does.
Now you can merge mapA and mapB more easily…
_.mergeWith(mapA, mapB, (a, b) => {
// … well, not that easily
})
Again, a and b are arrays of "pairs" name/status, so we can reuse the abstraction I showed above, defining
personArr2personMap = arrOfPairs2Obj('name', 'status');
and using it on a and b.
But still, there are some problems. I thought that the (a, b) => { … } I wrote above would be called by _.mergeWith only for elements which have the same key across mapA and mapB, but that doesn't seem to be the case, as you can verify by running this line
_.mergeWith({a: 1, b: 3}, {b:2, c:4, d: 6}, (x, y) => [x, y])
which results in
{
a: 1
b: [3, 2]
c: [undefined, 4]
d: [undefined, 6]
}
revealing that the working lambda is called for the "clashing" keys (in the case above just b), and also for the keys which are absent in the first object (in the case above c and d), but not for those absent in the second object (in the case above a).
This is a bit unfortunate, because, while you could filter dead people out of towns which are only in arrayB, and you could also filter out those people which are dead in arrayB while alive in arrayA, you'd still have no place to filter dead people out of towns which are only in arrayA.
But let's see how far we can get. _.merge doc reads
Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
So we can at least handle the merging of towns common across the array in a more straightforward way. Using _.merge means that if a person is common in the two arrays, we'll always pick the one from arrayB, whether that's (still) alive or (just) dead.
Indeed, a strategy like this doesn't give you the precise solution you want, but not even one too far from it,
notSoGoodResult = _.mergeWith(mapA, mapB, (a, b) => {
return _.merge(personArr2personMap(a), personArr2personMap(b));
})
its result being the following
{
town1: [
{name: "Charlie", status: "alive"},
{name: "Jim", status: "dead"}
],
town2: [
{name: "Rachel", status: "alive"}
],
town3:
Alice: "alive",
Bob: "dead",
Joe: "alive"
},
town5: {
Bob: "alive",
Ray: "alive",
Sam: "dead"
}
}
As you can see
Bob in town3 is correctly dead,
we've not forgotten Alice in town3,
nor have we forogtten about Joe in town3.
What is left to do is
"reshaping" town3 and town5 to look like town1 and town2 (or alternatively doing the opposite),
filtering away all dead people (there's no more people appearing with both the dead and alive status, so you don't risk zombies).
Now I don't have time to finish up this, but I guess the above should help you in the right direction.
The bottom line, however, in my opinion, is that JavaScript, even with the power of Lodash, is not exactly the best tool for functional programming. _.mergeWith disappointed me, for the reason explained above.
Also, I want to mention that there a module named lodash/fp that
promotes a more functional programming (FP) friendly style by exporting an instance of lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods.
This shuould slightly help you be less verbose. With reference to your self answer, and assuming you wanted to write the lambda
person => {return person.status == "alive";}
in a more functional style, with "normal" Lodash you'd write
_.flowRight([_.curry(_.isEqual)('alive'), _.iteratee('status')])
whereas with lodash/fp you'd write
_.compose(_.isEqual('alive'), _.get('status'))
You can define a function for merging arrays with a mapper like this:
const union = (a1, a2, id, merge) => {
const dict = _.fromPairs(a1.map((v, p) => [id(v), p]))
return a2.reduce((a1, v) => {
const i = dict[id(v)]
if (i === undefined) return [...a1, v]
return Object.assign([...a1], { [i]: merge(a1[i], v) })
}, a1)
}
and use it like this:
union(
arrayA,
arrayB,
town => town.townName,
(town1, town2) => ({
...town1,
occupants: union(
town1.occupants,
town2.occupants,
occupant => occupant.name,
(occupant1, occupant2) => occupant1.status === 'alive' ? occupant1 : occupant2
).filter(occupant => occupant.status === 'alive')
})
)
just wondering if someone could point me in the right direction of .map functionality. This is unfortunately something I'm struggling to get my head around.
If I had an object, lets say the following:
const myPetsAndFood = {
pets:[
{
species: "dog",
breed: "Labrador",
age: 12
},
{
species: "cat",
breed: "unknown",
age: 7,
},
{
species: "fish",
breed: "goldfish",
age: 1,
}
],
food: [
{
dogfood: 15.00,
},
{
catfood: 11.00,
},
{
fishfood: 4.00,
}
],
};
Could anyone explain how I'd utilise .map to obtain the data values of age and price if possible please?
A brief explanation or example is more than suffice, I'd appreciate any time/input possible. In all probability, I'll be sat here reading and trying to figure it out in the mean time.
If you got this far - Thank you for your time.
So the .map can only be used with arrays. This way you can not do something similar to:
myPetsAndFood.map()
Let's say you want do console.log the age. You would have to get the array first. So:
myPetsAndFood.pets.map((pet) => {
console.log(pet.age)
})
And it would print 12, followed by 7 followed by 1. If you want to store it inside an array you can create an array and use .push("//infos wanted to be pushed//")
Object.keys(myPetsAndFood).map(function(key, index) {
console.log(myPetsAndFood[key][0].dogfood);
console.log(myPetsAndFood[key][0].age);
});
You are going to have to figure out a way to replace the 0 with some sort of counter that will increment.
map is a method of arrays, it doesn't exist on objects. You could use it on the arrays within the object ( myPetsAndFood.pets.map( /* ... */ ) ) but you'd have to use a for loop or some other technique to parse each item in the object.
An example of how to use the map function for one of your arrays:
const agesArray = myPetsAndFood.pets.map((item) => {
return item.age;
});
So you have imbricated arrays here. This makes it so you have to go into your wanted array first before being able to execute your map.
For example: myPetsAndFood.pets.map(function)
The way that .map works is it executes your function on every element in your array and returns an array with the equivalency(source).
Therefore, in order to get the age of every pet, you have to tell your function to get your age property of your objects.
For example: myPetsAndFood.pets.map((pet) => pet.age)
This will return an array containing only the age of every one of your pets.
Now the problem with this is your second array. We cannot call the .map function on that array because your different properties don't have the same name. Therefore, your .map won't have any common ground to return a sensible array.
We can fix this issue by splitting your one variable into two: name and price for example. After this change, we can now call the .map on your array properly by telling it which property you need.
For example: myPetsAndFood.foods.map((food) => food.price)
Below is a full code snippet which should show off the above description.
const myPetsAndFood = {
pets:[
{
species: "dog",
breed: "Labrador",
age: 12
},
{
species: "cat",
breed: "unknown",
age: 7,
},
{
species: "fish",
breed: "goldfish",
age: 1,
}
],
foods: [
{
name: "dog",
price: 15.00,
},
{
name: "cat",
price: 11.00,
},
{
name: "fish",
price: 4.00,
}
],
};
const catAge = myPetsAndFood.pets.map((pet) => pet.age)
const foodPrice = myPetsAndFood.foods.map((food) => food.price)
console.log(catAge)
console.log(foodPrice)
array1 = [
{
"name":
{
"common": "Afghanistan",
"official": "Islamic Republic of Afghanistan",
"capital": [ "Kabul" ]
}
}]
array2 = [
{
"capital": [ "Kabul" ],
"population": 2837743
}];
I want to make it
[{
"name":
{
"common": "Afghanistan",
"official": "Islamic Republic of Afghanistan",
"capital": [ "Kabul" ],
"population": 2837743
}
}]
I want to merge to an array of objects and make one array. I was try to make it with filter method and find method but can't not solve the problem
There are a few issues with trying to do this because you will need a way of handling the merge from a semantic perspective. You need to choose what fields the merge will match on. For example, if two countries have the same population you don't want them to be matched and then merged.
You also need to be aware of and plan for the occurrence of more than two items matching. One problem that immediately comes to mind is if the two objects match yet have conflicting information. This could happen even if they genuinely are a match. Populations change over time. Even things as rigid as the capital changes.
Algorithmically there are two approaches to the matching that I will suggest.
The simpler of the two is to have a list of possible fields you want to match the objects on. You can then create an array (Dictionary or hashmap in other languages) that you key by the value seen so far.
var capital_map = []
for(country in countries1 + countries2){
if (capital_map[country.capital]){
//We have a match that we can then handle
}else{
//set up for possible matches down the array
capital_map[country.capital] = country
}
In the above example, we are only matching on the capital.
You could expand this such that if any one of many fields is matched then we merge the two elements or that at least n of N fields match.
The other way I was going to suggest is to create a function that defines some weighted matching. For example, what fraction of the fields that overlap are the same, with each field weighted based on the importance, so the population would be of low importance but capital might be of high importance.
I don't know if you want to go deeper in the nesting of the matching of the two arrays, for example with name vs an official name, but I hope you at least have a better idea of the sorts of techniques you could use for that.
You might have had the array of capitals because you expect issues with entities that pass matching but have overlapping fields and didn't want to worry about a resolution. (Such as just taking the average for the population)
If you do intend to expand this to more than two sources of data, more than two arrays you can do things like have voting where each source votes for its answer. If two sources say the capital is Brazilia and only one of the three says Rio then you can go with Brazilia.
You can do something like this (I left capital as an array, but it would be simpler if it were not):
const arr1 = [
{
name: {
common: 'Afghanistan',
official: 'Islamic Republic of Afghanistan',
},
capital: ['Kabul'],
},
{
name: {
common: 'Albania',
official: 'Republic of Albania',
},
capital: ['Tirana'],
},
];
const arr2 = [
{ capital: ['Tirana'], population: 2845955 },
{ capital: ['Kabul'], population: 2837743 },
];
const populationByCapital = new Map();
for (let el of arr2) {
populationByCapital.set(el.capital[0], el.population);
}
const arr3 = arr1.map((el) => {
return { ...el, population: populationByCapital.get(el.capital[0]) };
});
console.log(arr3);
this should give you:
[
{
name: {
common: 'Afghanistan',
official: 'Islamic Republic of Afghanistan'
},
capital: [ 'Kabul' ],
population: 2837743
},
{
name: { common: 'Albania', official: 'Republic of Albania' },
capital: [ 'Tirana' ],
population: 2845955
}
]
Note that it might break if there are two countries with the same capital names.
Edit
I see now that you were using the API from restcountries.com, thus changing capital to a scalar is beyound your control. And there's a reason why it's an array -- South Africa has 3 capitals. The code should still work because it's the same API, and all 3 are listed in the same order in both endpoints.
But there are couple of complications. The country of Bouvet Island has apparently no capital, and so the array will be empty, and the country of Norfolk Island, and Jamaica have both "Kingston" as the capital. To address both of these issues you can change the key to include the name of the country together with the capital:
const populationByCapital = new Map();
for (let el of arr2) {
populationByCapital.set(el.capital[0] + el.name.common, el.population);
}
const arr3 = arr1.map((el) => {
return {
...el,
population: populationByCapital.get(el.capital[0] + el.name.common),
};
});
I modified your starting data to fix some syntax errors:
const array1 = [
{
name: {
common: 'Afghanistan',
official: 'Islamic Republic of Afghanistan',
},
capital: ['Kabul'],
},
];
const array2 = [
{
capital: ['Kabul'],
population: 2837743,
},
];
As requested, this merge uses the filter method to match the two objects:
const newArray = array1.map((arr1Obj) => {
const capitalObj = array2.filter(
(arr2Obj) =>
JSON.stringify(arr1Obj.capital) === JSON.stringify(arr2Obj.capital)
);
return Object.assign(arr1Obj, ...capitalObj);
});
console.log(newArray);
Output:
[
{
name: {
common: 'Afghanistan',
official: 'Islamic Republic of Afghanistan',
},
capital: ['Kabul'],
population: 2837743,
},
];
First process the data of array2 and build any object with key as capitol and value as object. Use map and array1 and add the corresponding capitol info from process data (track2)
const array1 = [
{
name: {
common: "Afghanistan",
official: "Islamic Republic of Afghanistan",
},
capital: ["Kabul"],
},
];
const array2 = [
{
capital: ["Kabul"],
population: 2837743,
},
];
const track2 = {};
array2.forEach(({ capital: [city], ...rest }) => (track2[city] = rest));
const result = array1.map((item) => ({
...item,
...track2[item.capital?.[0]],
}));
console.log(result)
I am looking for a best way to implement a method which goal is to check if the string stored in the array includes a substring.
const arrayOfObj = [ {name: 'Tom', jobsPositions: ['general manager', 'staff manager', 'director']} ]
Let's assume I have a 100 persons in arrayOfObj. I have something like this:
arrayOfObj.filter(person => person.jobsPositions.some(position => position.includes('manager')));
The reslt should be the new array with all persons which have at least one job position with a 'manager' word.
Is there a better way to achieve this?
Thanks for help!
Your Code:
const arrayOfObj = [
{
name: 'Tom',
jobsPositions: [
'general manager',
'staff manager',
'director',
],
},
{
name: 'Joe',
jobsPositions: [
'director',
],
},
];
arrayOfObj.filter(person => person.jobsPositions.some(position => position.includes('manager')));
For your code, i can say its time complexity is O(M * N * L) for worst-case
M: .filter() iterates over each element,
N: .some() iterates over each element,
L: .includes() iterates over the inner array,
You may beautify code and make more readable with the hasJobPositionManager function
const hasJobPositionManager = person => person.jobsPositions.some(position => position.includes('manager'))
arrayOfObj.filter(hasJobPositionManager);
But I couldn't find the better time complexity and readable code for this code, i also use like yours.
Alright, so why am I asking this question? Because I am making a simple evolution simulator. In it, each creature will get a random amount of food each generation. The amount of of food each creature gets is crucial to if it survives or not. Since the only way I see it working is in an array(and I'm not good at arrays) can you help me find a way to assign these numbers to objects within the array?
I've looked through multiple websites for answers and none hit the dot. I also don't have any code so can you submit some code so I can see what I have to do?
You can just loop over the array and assign a random value to each creature.
Example:
let creatures = [
{name: "Bob", food: 0},
{name: "Alice", food: 0},
{name: "Steve", food: 0}
];
for(let creature of creatures)
creature.food = Math.random(); // random number for food between 0-1
console.log(creatures);
Simpley Do:
const creatures = [{
name: "Bob"
},
{
name: "Alice"
},
{
name: "Steve"
}
];
const creaturesWithFood = creatures.map((creature) => {
return {
food: Math.floor(Math.random() * 20),
...creature
}
});
console.log(creaturesWithFood);
I've limit the numbers to be less than 20...you can change it as per your needs, Hope this helps :)