How to merge two array of object - javascript

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)

Related

.map usage in an object

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)

Remove objects from an array where their properties are not found in another array

Given an array of Special objects as follows:
var allSpecials = []; //Yet to be populated
When populated, each special object in the array looks like this,
e.g.
{address: "250 Manukau Road", category: "Breakfast Special", coords: so, cusine_type: "Japanese", establishment_type: "Restaurant", …}
{address: "557 Manukau Rd", category: "Dinner Special", coords: so, cusine_type: "Italian", establishment_type: "Bar", …}
I want to remove for example, element 0 in the allSpecials array as it's category, "Breakfast Special", is not found in the following array
specialCategories = ["Happy Hour", "Dinner Special", "Lunch Special"];
How can I go about this?
Many thanks
You can use lodash keyby, pick and values functions.
const categorySpecialMap = keyBy(allSpecials, 'category');
const targetSpecials = values(pick(categorySpecialMap, specialCategories));
I believe what you are trying to do is to remove elements whose categories are not found in the specialCategories array?
Given an array:
const allSpecial = [{address: "250 Manukau Road", category: "Breakfast Special", coords: "so", cusine_type: "Japanese", establishment_type: "Restaurant"}, {address: "557 Manukau Rd", category: "Dinner Special", coords: "so", cusine_type: "Italian", establishment_type: "Bar"}, {address: "333", category: "x"}]
and another array:
const specialCategories = ["Breakfast Special", "Happy Hour", "Dinner Special", "Lunch Special"];
You can filter the allSpecials array where the category isn't included in the specialCategories array like so:
allSpecials.filter(x => specialCategories.includes(x.category))
OR
If you want to dynamically populate specialCategories with only objects that match, you can do:
myArrayOfObjects.forEach(e => {
if (specialCategories.includes(e.category)) {
allSpecials.push(e)
}
})

How do I get the top n elements per group within a sorted list with Ramda JS

I'm fairly new to RamdaJS and functional programming, so I was hoping to understand if there's a more efficient way to achieve the following.
Suppose I have a list of footballers. Each footballer in the list has properties for their name, their team, and their monthly pay.
const footballers = [
{
name: "Mesut Ozil",
team: "Arsenal",
pay: 1400000
},
{
name: "Lionel Messi",
team: "Barcelona",
pay: 7300000
},
{
name: "Kylian Mbappe",
team: "PSG",
pay: 1500000
},
{
name: "Alexis Sanchez",
team: "Manchester United",
pay: 1990000
},
{
name: "Philippe Coutinho",
team: "Barcelona",
pay: 2000000
},
{
name: "Neymar",
team: "PSG",
pay: 2700000
},
{
name: "Luis Suarez",
team: "Barcelona",
pay: 2500000
},
{
name: "Antoine Griezmann",
team: "Atletico Madrid",
pay: 2900000
},
{
name: "Gareth Bale",
team: "Real Madrid",
pay: 2200000
},
{
name: "Cristiano Ronaldo",
team: "Juventus",
pay: 4100000
}
]
I wish to order this list by pay, but restrict each team to a maximum of only two players in the final list. Currently my solution has to sort list twice. At the moment it sorts at the beginning and then the final list, but it could also sort after grouping by team. I believe there's a smarter (but still readable) solution that only requires the one sort, but I'm not sure what it is.
const byPayAsc = ascend(prop('pay')) // spare, for checking
const byPayDes = descend(prop('pay'))
const sortByPay = sort(byPayDes)
const groupedByTeam = compose(values, groupBy(prop('team')))
const topTwoPlayers = map(take(2))
const topTwoHighestPaidPlayersPerTeam = pipe(
sortByPay,
groupedByTeam,
topTwoPlayers,
flatten,
sortByPay
)
topTwoHighestPaidPlayersPerTeam(footballers)
My current investigation has unearthed a few options:
I've heard about transducers, which I think might provide the benefit of only looping through the list once.
I also wondered if it was possible I could use my sorting function to merge the grouped lists?
Or I could write a reducer to loop through the list of lists and take
the top 2 per team.
What is the idiomatic way of doing this with Ramda JS.
A single-sort solution
An approach which actually does avoid a second sort is certainly possible. Here's one version which does so:
const topTwoHighestPaidPlayersPerTeam = pipe (
sort (descend (prop ('pay'))),
reduce (
({result, counts}, player, {team} = player) => ({
result: counts [team] >= 2 ? result : [...result, player],
counts: {...counts, [team]: (counts[team] || 0) + 1}
}),
{result: [], counts: {}}
),
prop ('result')
)
const footballers = [{"name":"Mesut Ozil","team":"Arsenal","pay":1400000},{"name":"Lionel Messi","team":"Barcelona","pay":7300000},{"name":"Kylian Mbappe","team":"PSG","pay":1500000},{"name":"Alexis Sanchez","team":"Manchester United","pay":1990000},{"name":"Philippe Coutinho","team":"Barcelona","pay":2000000},{"name":"Neymar","team":"PSG","pay":2700000},{"name":"Luis Suarez","team":"Barcelona","pay":2500000},{"name":"Antoine Griezmann","team":"Atletico Madrid","pay":2900000},{"name":"Gareth Bale","team":"Real Madrid","pay":2200000},{"name":"Cristiano Ronaldo","team":"Juventus","pay":4100000}]
const result = topTwoHighestPaidPlayersPerTeam(footballers)
console .log (result)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script> const {pipe, sort, descend, prop, reduce} = R </script>
We start with a standard sort by pay, and then iterate through them, keeping track of per-team counts alongside the results, adding the team whenever the count is less than our maximum of 2. We track this by accumulating an object with both the results and the counts. At the end, we just extract the results property. While this could be done with a filter instead of reduce, the function passed to filter would need to maintain the counts state, which seems like a bad fit for filter.
The slightly more obvious version of
result: counts [team] < 2 ? [...result, player] : result,
won't work because initially the team counts are undefined, and undefined < 2 yields false. We could choose this version instead if it seems more clear:
result: (counts [team] || 0) < 2 ? [...result, player] : result,
Why we probably shouldn't use this solution
This code does avoid a second sort. But it does so at a cost in readability, and, probably, for reasonable-sized collections, in actual performance.
A two-sort solution is still O (n log (n)); doing something twice does not change the gross performance metric. So this version is not asymptotically faster than a two-sort one. But the code is not nearly as readable as that from either Scott Christopher or Ori Drori. Unless we've measured and can point to specific bottlenecks, adding complexity to our code for performance reasons seems a total waste.
So I would recommend the solution from Ori Drori over this. And Scott Christopher also has an interesting approach as well.
But this sort of technique is still often useful for maintaining some additional state while folding a list of values.
Your idea of sorting again is valid, since grouping the players by team changes the initial sort.
If you want to skip a second sort, you'll need to keep the original index of each item, and then sort by the index anyway. so I'm not sure it's worth it.
However, for the sake of checking the idea, this snippet converts the array items to pairs of [index, player] after sorting, but before grouping by the player's team. When you flatten the groups back to an array using R.values, and R.chain (with R.take), you convert the pairs back at an object using R.fromPairs. Since ES6 traversal of integer object keys is ascending, the original order is restored, and now you get the array via calling R.values again.
const { pipe, sort, descend, prop, toPairs, groupBy, path, values, chain, take, fromPairs } = R
const topTwoHighestPaidPlayersPerTeam = pipe(
sort(descend(prop('pay'))), // sort descending by pay
toPairs, // convert to pairs if [index, player object]
groupBy(path([1, 'team'])), // group by the team
values, // get an array of an arrays
chain(take(2)), // flatten and take the 1st two items of each group
fromPairs, // convert to an object of objects with original index as key
values // convert to an array in the correct order
)
const footballers = [{"name":"Mesut Ozil","team":"Arsenal","pay":1400000},{"name":"Lionel Messi","team":"Barcelona","pay":7300000},{"name":"Kylian Mbappe","team":"PSG","pay":1500000},{"name":"Alexis Sanchez","team":"Manchester United","pay":1990000},{"name":"Philippe Coutinho","team":"Barcelona","pay":2000000},{"name":"Neymar","team":"PSG","pay":2700000},{"name":"Luis Suarez","team":"Barcelona","pay":2500000},{"name":"Antoine Griezmann","team":"Atletico Madrid","pay":2900000},{"name":"Gareth Bale","team":"Real Madrid","pay":2200000},{"name":"Cristiano Ronaldo","team":"Juventus","pay":4100000}]
const result = topTwoHighestPaidPlayersPerTeam(footballers)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
What you have here is close to a merge sort, with the initial sorting performed per team and then merging the sorted lists of players.
const { descend, prop, take, sort, reduceBy, pipe, values, reduce } = R
const sorter = descend(prop('pay'))
// this is used by `R.reduceBy` below to build up the sorted list with a max size
const addToGroup = (size, compare) => (group, a) =>
take(size, sort(sorter, [a, ...group]))
const sortByTeam = reduceBy(addToGroup(2, sorter), [], prop('team'))
// recursively merges two sorted(!) lists by the given comparator
const mergeListsBy = compare => {
const go = (xs, ys) =>
xs.length == 0 ? ys :
ys.length == 0 ? xs :
compare(xs[0], ys[0]) < 0 ? [xs[0], ...go(xs.slice(1), ys)] :
[ys[0], ...go(xs, ys.slice(1))]
return go
}
const run = pipe(sortByTeam, values, reduce(mergeListsBy(sorter), []))
////
const footballers = [{"name": "Mesut Ozil", "pay": 1400000, "team": "Arsenal"}, {"name": "Lionel Messi", "pay": 7300000, "team": "Barcelona"}, {"name": "Kylian Mbappe", "pay": 1500000, "team": "PSG"}, {"name": "Alexis Sanchez", "pay": 1990000, "team": "Manchester United"}, {"name": "Philippe Coutinho", "pay": 2000000, "team": "Barcelona"}, {"name": "Neymar", "pay": 2700000, "team": "PSG"}, {"name": "Luis Suarez", "pay": 2500000, "team": "Barcelona"}, {"name": "Antoine Griezmann", "pay": 2900000, "team": "Atletico Madrid"}, {"name": "Gareth Bale", "pay": 2200000, "team": "Real Madrid"}, {"name": "Cristiano Ronaldo", "pay": 4100000, "team": "Juventus"}]
console.log(run(footballers))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
For sake of simplicity, I'd just write a custom filter function...
const playersPerTeam = (n) => {
const teams = {};
const predicate = (({ team }) => {
teams[team] = (teams[team] || 0) + 1;
return teams[team] <= n;
});
return R.filter(predicate);
};
const fn = R.pipe(
R.sort(R.descend(R.prop('pay'))),
playersPerTeam(2),
);
// ------
const data = [
{
name: "Mesut Ozil",
team: "Arsenal",
pay: 1400000
},
{
name: "Lionel Messi",
team: "Barcelona",
pay: 7300000
},
{
name: "Kylian Mbappe",
team: "PSG",
pay: 1500000
},
{
name: "Alexis Sanchez",
team: "Manchester United",
pay: 1990000
},
{
name: "Philippe Coutinho",
team: "Barcelona",
pay: 2000000
},
{
name: "Neymar",
team: "PSG",
pay: 2700000
},
{
name: "Luis Suarez",
team: "Barcelona",
pay: 2500000
},
{
name: "Antoine Griezmann",
team: "Atletico Madrid",
pay: 2900000
},
{
name: "Gareth Bale",
team: "Real Madrid",
pay: 2200000
},
{
name: "Cristiano Ronaldo",
team: "Juventus",
pay: 4100000
},
];
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

Javascript copy no repeat object data taht have same property from array

I have an arrry that has 100 object and it has same property code
Data = [
{yera:"2019", name:"saif", topic:"oil"},
{yera:"2018", name:"abc", topic: "oil"},
{yera:"2018", name:"jorj", topic:"energy"},
{yera:"2017", name:"tom", topic:"gas"},
{yera:"2016",name:"saif",topic:"electricity "},
{yera:"2014", name:"gour",topic:"oil"},
Assuming you want to remove duplicates from the array of objects based on a key of that object, the code below will achieve that.
var data = [
{yera:"2019", name:"saif", topic:"oil"},
{yera:"2018", name:"abc", topic: "oil"},
{yera:"2018", name:"jorj", topic:"energy"},
{yera:"2017", name:"tom", topic:"gas"},
{yera:"2016",name:"saif",topic:"electricity "},
{yera:"2014", name:"gour",topic:"oil"}
]
function getUniqueData(originalData, keyToCheckForUniqueness) {
var store = {};
var output = [];
originalData.forEach(function (ob) {
var val = ob[keyToCheckForUniqueness];
if (!store[val]) {
store[val] = [ob];
} else {
store[val].push(ob);
}
});
// at this point your store contains all the repeating data based on that property value
// console.log(store);
// now emit single values from that store;
// this logic can be based on any criterion, I chose the first element of the array - it may change depending on the order of values in input
Object.keys(store).forEach(function (key) {
var uniqueValArray = store[key];
var uniqueVal = uniqueValArray[0]; // take the first entry
output.push(uniqueVal);
});
return output;
}
getUniqueData(data, "topic");
This will achieve what I think you want to figure out. A word of advice - Don't let people think when you ask them for help. Second, try writing the logic for yourself. Post your non-working solution and ask, where you made a mistake - rather than asking. Given your rep, welcome to SO. Hope you a great learning experience.
Assuming, you want unique values for a given property of the objects, you could map that value and take a Set for getting unique values.
function getUnique(array, key) {
return Array.from(new Set(array.map(({ [key]: v }) => v)));
}
var array = [{ year: "2019", name: "grace", topic: "oil" }, { year: "2018", name: "grace", topic: "oil" }, { year: "2018", name: "jane", topic: "energy" }, { year: "2017", name: "tom", topic: "gas" }, { year: "2016", name: "jane", topic: "electricity" }, { year: "2014", name: "gour", topic: "oil" }];
console.log(getUnique(array, 'year'));
console.log(getUnique(array, 'name'));
console.log(getUnique(array, 'topic'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to weight items in a fuzzy search

Using Fuse.js, I need to weight individual item for a better ranking in search results. For instance, how do I make sure "Paris France" has the biggest score for a "Paris" query with the data below?
places = [{
name: 'Paris, France'
weigth: 10
},{
name: 'Paris, Ontario'
weigth: 2
},
{
name: 'Paris, Texas'
weigth: 1
}]
As far as I am aware, there are no methods built into Fuse.js to do this. The weight property is meant to be applied to properties which are being searched (in the options object), rather than to the object that is being searched (as seen in the example here.
What I might suggest is writing a function to sort this yourself. So once you get your results array back, after the search, perform an Array.sort() on it yourself (documentation here).
For example...
//Your places object
var places = [
{
name: 'Paris, Texas',
weight: 2
},
{
name: 'Paris, France',
weight: 10
},
{
name: 'Paris, Texas',
weight: 1
}
];
//Your search options
var options = {
keys: [
"name"
]
};
var fuse = new Fuse(places, options); // "list" is the item array
var result = fuse.search("Paris");
//Once you have got this result, perform your own sort on it:
result.sort(function(a, b) {
return b.weight - a.weight;
});
console.log('Your sorted results:');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.1.0/fuse.min.js"></script>

Categories