Why is .push() method giving duplicates - javascript

{
category: 'Music',
id: '625064c2e12f6dec240bdfcb',
correctAnswer: "Don't Leave Me This Way",
incorrectAnswers: [
'Ice Ice Baby',
'In-A-Gadda-Da-Vida',
'Rebirth of Slick (Cool Like Dat)'
],
question: 'What song did Thelma Houston have a hit with in 1976?',
tags: [ 'songs', 'one_hit_wonders', 'music' ],
type: 'Multiple Choice',
difficulty: 'hard',
regions: []
}
// Here is the response from the API...
const oldArray = quizData[number].incorrectAnswers;
oldArray.push(quizData[number].correctAnswer);
console.log(oldArray);
// Here i am pushing the correctAnswer into the incorrect Answer array but my console.log keeps repeating the correct answer data, Why is this happening? Am i pushing it wrong?
['An arrow', 'A lightning bolt', 'A bottle of wine', 'A Discus', 'A Discus', 'A Discus', 'A Discus', 'A Discus']
//Here is my console.log array.
// 'A Discus' keeps getting repeated 4 times, Same length of the present array, So the array has 7 values instead of just 3, What is going on?

use the spread operator to avoid duplications
console.log( [...new Set(oldArray)])
const list = ['Anemia', 'Heart Attack', 'Paralysis', 'Epilepsy', 'Epilepsy']
console.log( [...new Set(list)])

Related

Lodash merging and unioning of nested array / object structure

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')
})
)

How to transfer MAP.values into Arrays efficiently?

In the example below, you see some noisy but straightforward implementation.
Initial Situation
We have an initial Array with repeating information.
const listArray = [
{ songBy: 'George Michael', uid: 'A', whatEver: 12},
{ songBy: 'George Michael', uid: 'A', whatEver: 13},
{ songBy: 'George Michael', uid: 'A', whatEver: 14},
{ songBy: 'Michael Jackson', uid: 'B', whatEver: 12},
{ songBy: 'Michael Jackson', uid: 'B', whatEver: 16},
]
STEP 1
We create a new Map because we distinctly want to save artist names, which means → no artist-name-repetitions (and we also want to get rid of the third column, by the way).
We need to use songBy as key, for some reason. We change it within the value into name.
const listMap = new Map();
listArray.forEach(
row => listMap.set(
row.songBy, {name: row.songBy, uid: row.uid}
)
);
STEP 2
Finally, we need an array with its values:
const distinctListArray: Array<any> = [];
listMap.forEach(value => distinctListArray.push(value));
So we achieve a result as an Array of distinct objects in the form of:
[
name: string
uid: string
]
Question:
To my mind, this implementation is too noisy and not so elegant. There are too many steps and too many variables. (This example here is a simplified version of a real code I cannot share).
Is there a way to simplify that code and make it more efficient?
EDIT: See online: TypeScript Playground
Convert the list to [key, value] pairs using Array.map(), and then create the map from the list. Convert the Map back to an array by applying Array.from() to the Map.values() iterator (TS playground):
const listArray = [{"songBy":"George Michael","uid":"A","whatEver":12},{"songBy":"George Michael","uid":"A","whatEver":13},{"songBy":"George Michael","uid":"A","whatEver":14},{"songBy":"Michael Jackson","uid":"B","whatEver":12},{"songBy":"Michael Jackson","uid":"B","whatEver":16}]
const distinctListArray = Array.from(
new Map(listArray.map(({ songBy, uid }) =>
[songBy, { name: songBy, uid }]
)).values()
)
console.log(distinctListArray)

Filter multidimensional array with objects

With an array like the following, how can I filter objects based on the texts.id? For example if I wanted to return this very same array without the texts object reffering to the id 123 how would I go about it? I have access to the id I want to filter out and also the name of the object where its located. I have been trying to figure this out but I am not sure this is doable with the filter method only. Thanks in advance to anyone that helps.
[
0: Object {
name: 'Name 1',
texts[
0: Obj{
id: '123',
body: 'test message'
},
1: Obj{
id: '456',
body: 'test message 2'
}
]
},
1: Object {
name: 'Name 2',
texts[
0: Obj{
id: '789',
body: 'test message3'
},
1: Obj{
id: '101112',
body: 'test message 4'
}
]
}
]
It's not totally clear to me whether you want to remove the entire outer object where the texts array contains an object with a certain id (i.e: in your example, this would remove the whole object with name 'Name 1'), or if you just want to remove the object in the texts array which has a certain id (i.e: leaving the outer object, just removing the object in texts with id 123).
The former has been answered by #lissettdm above, but if it's the latter, you could do something like this:
const filter = "123";
entry.forEach(item => item.texts = item.texts.filter(text => text.id !== filter));
Yes, it is possible using Array.prototype.filter() and Array.prototype.find()
const entry = [{name: "Name 1",texts: [{id: "123",body: "test message"},{id: "456",body: "test message 2"}]},{name: "Name 2",texts: [{id: "789",body: "test message3"},{id: "101112",body: "test message 4"}]}];
const filter= "123";
const output = entry.filter(item => item.texts.find(text=> text.id===filter));
console.log(output);

javascript - pick unique items from first array

Suppose I have two arrays of objects
const array1 = [{
listName: 'My top 5 Sci-Fi Movies',
listCreator: 'Anon',
listItem: 'The Fifth Element'
},
{
listName: 'My top 5 Sci-Fi Movies',
listCreator: 'Anon',
listItem: 'Cube'
}]
and
const array2 = [{
listName: 'My top 5 Sci-Fi Movies',
listCreator: 'Anon',
listItem: 'The Fifth Element'
},
{
listName: 'My top 5 Sci-Fi Movies',
listCreator: 'Dude',
listItem: 'Cube'
}]
I want to able to compare the two arrays and create a new array that only has unique members of array1, in this case
const uniqueArray = [{
listName: 'My top 5 Sci-Fi Movies',
listCreator: 'Anon',
listItem: 'Cube'
}]
I am using lodash library (but not necessary to solve this problem if not required) and I'd like to accomplish this in the fewest lines of code possible.
hello dear brother you use Hashset to fetch automatically Unique Data from list
HashSet<Integer>set = new HashSet<Integer>(list1);
List<Integer>list2 = new ArrayList<Integer>(set);
and then pass this Hashset to List again
ITS JAVA CODE BUT LOGIC IS SAME FOR EVERY LANGUAGE
After banging my head around the intertubes, I found a simple and elegant solution.
If I (using lodash) do
const arrNew = _.differenceWith(array1, array2, _.isEqual);
arrNew is a new array of unique items from array1. Hope this helps someone else out.
Would love to see if there's another more performant solution.
https://lodash.com/docs/4.17.15#differenceWith

Merge array of objects by key/value

I am trying to figure out the best way to map an objects id property that is within a multi-dimension array to object values that are within another array that share the same id.
As an example i have an array of genre_ids like so:
0: {id: 1, name: 'sci-fi'},
1: {id: 2, name 'comedy'},
2: {id: 3, name: 'action'}
And an array of tv_show_genre_ids which looks like:
0: {name: ..., genre_ids: [1, 4, 9]},
1: {name: ..., genre_ids: [2, 3, 4]},
I was trying to figure out the best way to retrieve a list of the genres name by its id.
I have managed to create a working solution so far but it feels incredibly dirty as i am performing multiple nested loops and i wasn't sure if there is a cleaner more declarative approach to my solution
Here is my approach which assumes i already have a list of genre ids and names (accessed within this.genres.
this.http.get('https://api.com/shows')
.subscribe((res: array <any> ) => {
this.shows = res.results;
this.shows.forEach(show => {
show.genre_names = '';
show.genre_ids.forEach(id => {
for (const [i, v] of this.genres.entries()) {
if (id == v.id) {
if (this.genres[i] && this.genres[i].name) {
if (show.genre_names === '') {
show.genre_names = this.genres[i].name
} else {
show.genre_names += `, ${this.genres[i].name}`;
}
}
}
}
})
});
});
Is there a better way of doing this as i seem to come across this type of problem quite often when trying to map ids from one object to another within multi-dimension arrays.
Any guidance would be greatly appreciated.
EDIT:
Here is an example of the Genre Data from the API af:
0: {id: 10759, name: "Action & Adventure"}
1: {id: 16, name: "Animation"}
And here is an example of the show data from the API:
0:
backdrop_path: "/ok1YiumqOCYzUmuTktnupOQOvV5.jpg"
first_air_date: "2004-05-10"
genre_ids: (2) [16, 35]
id: 100
name: "I Am Not an Animal"
origin_country: ["GB"]
original_language: "en"
original_name: "I Am Not an Animal"
overview: "I Am Not An Animal is an animated comedy series about the only six talking animals in the world, whose cosseted existence in a vivisection unit is turned upside down when they are liberated by animal rights activists."
popularity: 10.709
poster_path: "/nMhv6jG5dtLdW7rgguYWvpbk0YN.jpg"
vote_average: 9.5
vote_count: 341
I want to add a new property to the show object named genre_names which gets the genre name via the genre response.
Your best bet is to first convert your genres into a Map or an object to use as a lookup:
const genreLookup = new Map();
this.genres.forEach(genre => genreLookup.set(genre.id, genre));
Now when you process an array of shows, you don't have to loop through the genres multiple times:
this.shows.forEach(show => {
show.genre_names = show.genre_ids
.filter(id => genreLookup.has(id))
.map(id => genreLookup.get(id).name)
.join(', ');
});

Categories