Flatten and distinct multiple arrays in an object - javascript

I have the following object:
'1': {
id: 1,
...
tags: ['cat I', 'cat II']
},
'2': {
id: 2,
tags: ['cat II', 'cat III']
}
To get all the categories ( but no duplicates ) I do the following:
const cats = [];
this.courses.forEach(data => (data.tags) ? cats.push(data.tags) : '');
return [...new Set(cats.flat())];
It works but I have the feeling this is way to "over the top". It's also runs twice because its in the computed properties.
Is there a beter way to distinct & filter out the categories. Maybe by giving a query to the store?

Use Array.flatMap() to get an array of tags. You can use destructuring and defaults to get the tags property, or assign an empty array if it's missing. To get unique tags, create a Set from the array of tags, and spread the Set back to an array:
const data = [{"id":1,"tags":["cat I","cat II"]},{"id":2,"tags":["cat II","cat III"]},{"id":3}]
const result = [...new Set(data.flatMap(({ tags = [] }) => tags))]
console.log(result)

You do:
const courses = [{id: 1, tags: ['cat I', 'cat II ']}, {id: 2, tags: ['cat II', 'cat III']}, {id: 3}]
const tags = courses
.reduce((a, { tags = [] }) => [...a, ...tags], [])
.map(tag => tag.trim()) // <-- to remove extra spaces in "cat II "
const result = [...new Set(tags)]
console.log(result)

Related

How to make filter function that doesn't stop at first false?

Im trying to filter an array of objects by comparing it to another array by looping through it's values.
The problem is that the filter function stops at the first false by default, so if the loop returns (false, true, false) it will not pass the filter.
is there a way around this?
code (I've put simplfied arrays and objects in the code for the example):
const jobs = [
{id: 1,
location: 'AA'},
{id: 2,
location: 'BB'},
{id: 3,
location: 'CC'},
]
const filteredLocations = ['AA', 'CC']
const filteredJobs = jobs.filter(function(el, i) {
do {
return el.location === filteredLocations[i];
}
while (filteredLocations.length >= i)
})
// this only returns the first object instead of the desired first and third
Thank you!
Use the .filter function as it is intended, any return will be added to the returned array.
const jobs = [
{id: 1,
location: 'AA'},
{id: 2,
location: 'BB'},
{id: 3,
location: 'CC'},
]
const filteredLocations = ['AA', 'CC']
const filteredJobs = jobs.filter(el => {
return filteredLocations.includes(el.location);
});
sidenote, your code has 2 issues:
it may return false inside the while loop, if it doesnt match the 1st occurrence, hence why the single output you had
you compare filteredLocations.length with the i (index) of the jobs array loop, jobs and filteredLocations are 2 different arrays, so that doesnt make sense to do so
Filter doesn't stop at first false. It works correctly but it cannot return the first and third objects because the third object is on index 2 and you don't have any items in filteredLocations on that index.
I would advise you to replace the do-while loop with a simple finder function as follows:
const jobs = [
{id: 1,
location: 'AA'},
{id: 2,
location: 'BB'},
{id: 3,
location: 'CC'},
]
const filteredLocations = ['AA', 'CC']
const filteredJobs = jobs.filter(function(el, i) {
return filteredLocations.find((filteredLocation) => filteredLocation === el.location);
})
console.log(filteredJobs);
let filtered = jobs.map(j => filteredLocations.every(fl => fl == j.location));
How to make filter function that doesn't stop at first false ?
filter() never stop at first false. It will check for all the true values which passed the applied condition and return those elements from the array in a new array.
To achieve the requirement, You can simply use the single line of code in the filter function instead of do-while loop.
Demo :
const jobs = [
{id: 1,
location: 'AA'},
{id: 2,
location: 'BB'},
{id: 3,
location: 'CC'},
];
const filteredLocations = ['AA', 'CC'];
const filteredJobs = jobs.filter(({ location }) => filteredLocations.includes(location));
console.log(filteredJobs);

How to compaire parts of a string in an array of objects with other strings?

I have an array of objects in javascript like:
var arr_objects= [
{id: 1, authors: "person1||person2", edithors: "person1||person7"},
{id: 2, authors: "person3", edithors: "person2"},
{id: 3, authors: "person4||person5||person6", edithors: "person7||person6"},
{id: 4, authors: "person7||person6", edithors: "person6"}
];
I want to check if any name in "authors" (person1, person2 etc.) occurs in the same object in "edithors". If thats the case - write a new key-value pair ("occurance") to the object containing the name of the author/editor.
The output should look like this:
var arr_objects= [
{id: 1, authors: "person1||person2", edithors: "person1||person7", occurance: "person1"},
{id: 2, authors: "person3", editors: "person2" },
{id: 3, authors: "person4||person5||person6", edithors: "person7||person6", occurance: "person6"},
{id: 4, authors: "person7||person6", edithors: "person6", occurance: "person6"}
];
I am new to programming and seem to be completely stuck.
I guess to achieve the output, I have to use regulair expressions on "authors" and "editors" to separate the values and compaire both strings with eachoter. Unfortunately am unable to use the regex and loop through the array while comparing the values.
I would be very thankful for any advice regarding my problem.
arr_objects.forEach(obj => {
const authors = obj.authors.split('||');
const editors = obj.edithors.split('||');
const matches = authors.filter(val => editors.includes(val));
if (matches.length) {
obj.occurrences = matches;
}
});
Split the authors and editors into two arrays.
Find all the duplicates between the two arrays.
Assign the duplicates to your new object property occurrences.
Here is a solution with some explanations
arr_objects.map(function(item)
{
// split both authors and editors into an array
let authors = item.authors.split('||');
let editors = item.edithors.split('||');
let occurance = [];
// for each author, check if it also appears among editors
for (a in authors)
{
if (editors.indexOf(authors[a])>=0)
{
//if yes, push it into occurrence buffer
occurance.push(authors[a]);
}
}
// join the array into a string
item.occurance = occurance.join('||');
return item;
})
var arr_objects= [
{id: 1, authors: "person1||person2", edithors: "person1||person7"},
{id: 2, authors: "person3", edithors: "person2"},
{id: 3, authors: "person4||person5||person6", edithors: "person7||person6"},
{id: 4, authors: "person7||person6", edithors: "person6"}
];
function getOccurance(arr){
// clone
arr = [...arr]
arr = arr.map(a => {
let authors = a.authors.split("||");
let editors = a.edithors.split("||")
let occurance = ""
authors.forEach(auth => {
editors.forEach(ed => {
if(auth == ed) {
if(occurance.length > 0){
occurance += "||" + auth;
}else{
occurance += auth
}
}
})
})
if(occurance.length > 0){
a.occurance = occurance
}
return a
})
return arr
}
console.log(getOccurance(arr_objects))
No need for regex, you can use split to get an array of persons out of the person7||person6 string :
const persons = `person7||person6`.split('||');
The opposite can be done with join :
['person7', 'person6'].join('||'); //evaluates to person7||person6
Now all you have to do is loop through arr_objects, split authors and edithors, compute their intersection and put it in occurance :
for(obj of arr_objects) {
const authors = obj.authors.split('||');
const editors = obj.edithors.split('||');
const intersection = computeIntersection(authors, editors);
if(intersection.length > 0) {
obj.occurance = intersection.join('||')
}
}
Now all that's left is implementing computeIntersection :
function computeIntersection(array1, array2) {
let intersection = [];
let visited = new Set();
for(e of array1) {
visited.add(e);
}
for(e of array2) {
if(visited.has(e)) {
intersection.push(e);
}
}
return intersection;
}

Suggested way to Map an Object array to Array of Ids

If given an array of ids [1,2,3,4,5]
And an object array:
[{animal:tiger, id:1}, {animal:"fish", id:2}]
What would be the suggested way to return 'tiger, fish'. Would that be through using .map or would a for loop be better for constructing the sentence?
What you need is just go through the list of ids and find corresponding animal in the animals list.
Note, that in case animals list is not expected to store all the animals and some of them are missing, you will need to add additional filter step to be sure that no undefined values appear on the last map step.
const ids = [1,5,2,4,3,6]; // added 6 which is missing in animals
const animals = [
{name:'Tiger',id:1},
{name:'Horse',id:2},
{name:'Mouse',id:3},
{name:'Elephant',id:4},
{name:'Cat',id:5}
];
const result = ids
.map(id => animals.find(a => a.id === id))
.filter(Boolean) // this will exclude undefined
.map(a => a.name)
.join(',');
console.log(result);
var ids = [1,2,3,4,5];
var objects = [{ animal:"tiger", id: 1 }, { animal: "fish", id: 2 }];
objects.map(function(o) { if(ids.includes(o.id)) return o.animal }).join();
I'm guessing you only want the animal names who's id appears in your array. If so, you could filter the array of objects first, followed by a map and a join.
let idarr = [1, 2, 3, 4];
let objarr = [{
animal: "tiger",
id: 1
}, {
animal: "fish",
id: 2
}];
console.log(objarr.filter(x => idarr.includes(x.id)).map(x => x.animal).join(', '))
I suggest two options, depending on your data & use cases.
1. map + find if the animal kingdoms are not too many to loop through.
const animals = [
{animal:tiger, id:1},
{animal:"fish", id:2}
]
const ids = [1,2,3,4,5];
const names = ids.map(id =>
animals.find(animal => animal.id === id));
2. convert animals array to object first, for easier frequent access later. One upfront loop, then easier to access by id later.
const animals = [
{animal: "tiger", id:1},
{animal: "fish", id:2}
]
/*
Convert
[{animal:tiger, id:1}, {animal:"fish", id:2}]
to
{
1: { animal: "tiger", id: 1 },
2: { animal: "fish", id: 2 },
}
*/
const animalsObj = animals.reduce((acc, animal) => {
return {
...acc,
[animal.id]: animal,
}
}, {});
const ids = [1,2,3,4,5];
const names = ids.map(id => animalsObj[id].animal)

Mapping the array element with other array

I have two arrays
array1 = [{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}];
array2 = [{tags: "1",title: "USA",type: "text"},
{tags: "1,2,3",title: "Japan",type: "image"},
{tags: "2,3",title: "Japan",type: "image"}];
I have to map the id of the array1 to tags of the array2 and display the corresponding title from the array1.
The new array2 should look like,
array2=[{tags:"Writing",title:"USA", type:"text"},
{tags: "Writing,Singing,Dance",title: "Japan",type: "image"},
{tags: "Singing,Dance",title: "Japan",type: "image"}];
I did this to get the array1 mapping and got stuck after that.
var newtags= (array1).map(obj=>{
var rObj={};
rObj[obj.id]=obj.title;
return rObj;
});
You can create a mapping object with each id as key and title as value using reduce. Then map over array2 and split each tags to get the new tags
const array1=[{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}],
array2=[{tags:"1",title:"USA",type:"text"},{tags:"1,2,3",title:"Japan",type:"image"},{tags:"2,3",title:"Japan",type:"image"}]
const map = array1.reduce((r, { id, title }) => ({ ...r, [id]: title }), {});
const output = array2.map(({ tags, ...rest }) => {
const newTags = tags.split(',').map(id => map[id]).join(',')
return { tags: newTags, ...rest }
})
console.log(output)
You could also get the mapping object using Object.fromEntries()
const map = Object.fromEntries(array1.map(({ id, title }) => [id, title]));
Then use the regex /\d+(?=,|$)/ to match the numbers and replace them with their respective titles
const array1=[{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}],
array2=[{tags:"1",title:"USA",type:"text"},{tags:"1,2,3",title:"Japan",type:"image"},{tags:"2,3",title:"Japan",type:"image"}]
const map = Object.fromEntries(array1.map(({ id, title }) => [id, title]));
const output = array2.map(({ tags, ...rest }) => {
const newTags = tags.replace(/\d+(?=,|$)/g, n => map[n])
return { tags: newTags, ...rest }
})
console.log(output)
Here's a solution
I'm using .map, .reduce and .replace to join array1 and array2 together.
const array1 = [
{
id: "1",
title: "Writing"
},
{
id: "2",
title: "Singing"
},
{
id: "3",
title: "Dance"
}
]
const array2 = [
{
tags: "1",
title: "USA",
type: "text"
},
{
tags: "1,2,3",
title: "Japan",
type: "image"
},
{
tags: "2,3",
title: "Japan",
type: "image"
}
]
const array3 =
array2.map(item => ({
...item,
tags: array1.reduce((tags, {id, title}) => tags.replace(id, title), item.tags),
}))
console.log(array3)
You can use filter, map and join method, split tags and filter tags in array1 first.
var newtags= (array2).map(obj=>{
let tags = obj.tags.split(",");
let titles = array1.filter(c=>tags.includes(c.id)).map(c=>c.title);
obj.tags = titles.join();
return obj;
});
array1 = [{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}];
array2 = [{tags: "1",title: "USA",type: "text"},
{tags: "1,2,3",title: "Japan",type: "image"},
{tags: "2,3",title: "Japan",type: "image"}];
var newtags= (array2).map(obj=>{
let tags = obj.tags.split(",");
let titles = array1.filter(c=>tags.includes(c.id)).map(c=>c.title);
obj.tags = titles.join();
return obj;
});
console.log(newtags);
You can try following
Use Array.reduce to convert array1 into an object with id as key and title as value (Step 1)
Iterate over array2 using Array.forEach to update its tags property
To update tags property first split it by , to convert into an array
Map each value in array to its corresponding value in Object created in step 1
Join back the array with , and assign back to tags
let array1 = [{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}];
let array2 = [{tags: "1",title: "USA",type: "text"},{tags: "1,2,3",title: "Japan",type: "image"},{tags: "2,3",title: "Japan",type: "image"}];
let obj = array1.reduce((a,c) => Object.assign(a, {[c.id] : c.title}), {});
array2.forEach(o => o.tags = o.tags.split(",").map(v => obj[v]).join(","));
console.log(array2);
To achieve expected result, use below option of looping array1 and replacing array2 tags with title
Loop Array1 using forEach
Replace array2 tags with each array1 title using array id
array1 = [{id:"1",title:"Writing"},{id:"2",title:"Singing"},{id:"3",title:"Dance"}];
array2 = [{tags: "1",title: "USA",type: "text"},
{tags: "1,2,3",title: "Japan",type: "image"},
{tags: "2,3",title: "Japan",type: "image"}];
array1.forEach(v =>{
const re = new RegExp(v.id, "g");
array2 = JSON.parse(JSON.stringify(array2).replace(re, v.title))
})
console.log(array2);
I would consider breaking this down into several reusable functions. Of course it might be premature abstraction, but I've seen variants of this questions like often enough here that it makes sense to me to look toward the fundamentals.
We want to be able to look up the values in a list stored as an array with what might be arbitrary field names. So we use a function makeDictionary that takes both the field names and the array and returns an object that maps them, such as {'1': 'Writing', '2': 'Singing',...}`.
Then we can use fillField supplying a dictionary, a field name, and an object, and replace that field with the result of looking up the tags in the dictionary. This is a little more specific to the problem, mostly because the comma-separated string format for your tags is a little more cumbersome than it might be if it were an array.
With these, useTags is simple to write, and it is the first function here focused directly on your needs. It combines the above, supplying the field names id and title for the dictionary and tags for your main objects.
This is what it looks like combined:
const makeDictionary = (keyName, valName) => (arr) =>
arr .reduce
( (a, {[keyName]: k, [valName]: v}) => ({...a, [k]: v})
, {}
)
const fillField = (field, dict) => ({[field]: f, ...rest}) => ({
...rest,
[field]: f .split (/,\s*/) .map (t => dict[t]) .join (', ')
})
const useTags = (tags, dict = makeDictionary ('id', 'title') (tags) ) =>
(objs) => objs .map ( fillField ('tags', dict) )
const tags = [{id: "1", title: "Writing"}, {id: "2", title: "Singing"}, {id: "3", title: "Dance"}];
const updateTags = useTags (tags)
const items = [{tags: "1", title: "USA", type: "text"}, {tags: "1, 2, 3", title: "Japan", type: "image"}, {tags: "2, 3", title: "Japan", type: "image"}];
console .log (
updateTags (items)
)
Note that I took a little liberty with the tags: "2,3" and tags: "Singing,Dance" formats, adding a little white space. It's trivial to take this out. But even better, if possible, would be to change this to use arrays for your tags.
You could take a real Map and map the values to the new objects.
var array1 = [{ id: "1", title: "Writing" }, { id: "2", title: "Singing" }, { id: "3", title: "Dance" }],
array2 = [{ tags: "1", title: "USA", type: "text" }, { tags: "1,2,3", title: "Japan", type: "image" }, { tags: "2,3", title: "Japan", type: "image" }],
tags = array1.reduce((m, { id, title }) => m.set(id, title), new Map),
result = array2.map(o => ({ ...o, tags: o.tags.split(',').map(Map.prototype.get, tags).join() }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Take the first objects with unique IDs from an array and put them in new list

I am making a dropdown from json file containing array of objects, the problem is some of the objects have same IDs, and I want to extract only the first objects with unique ID (for example 1, then take the second object with unique ID 2 etc..) and put them in a list. That is, I need a list with unique IDs only.
What I have tried so far:
var distinctId = [];
for (var i = 0; i < data.length; i++) {
var id = data[i]["id"];
if (distinctId[id] == undefined) {
distinctId[id] = [];
}
distinctId[id].push(data[i]);
console.log(data[i]);
}
The json file looks something like this:
[
{
id: 1,
field1: ...,
field2: ...
},
{
id: 1,
field1: ...,
field2: ...
},
{
id: 2,
field1: ...,
field2: ...
},
{
id: 2,
field1: ...,
field2: ...
},
{
id: 3,
field1: ...,
field2: ...
},
{
id: 3,
field1: ...,
field2: ...
},
]
If all you wish to do is get the first two unique ids of your objects you can do this by mapping your array to an array of id's using .map and destructing assignment.
Once you have done this you can use a set to remove all the duplicate's from the array. Lastly, you can use .splice to keep the first 2 unique ids from your array:
const arr = [{id:1,field1:'foo',field2:'bar'},{id:1,field1:'foo',field2:'bar'},{id:2,field1:'foo',field2:'bar'},{id:2,field1:'foo',field2:'bar'},{id:3,field1:'foo',field2:'bar'},{id:3,field1:'foo',field2:'bar'}],
res = [...new Set(arr.map(({id}) => id))].splice(0, 2);
console.log(res);
If you wish to have an array of objects which are unique you can use .reduce to create a new array. Essentially the new array is created by adding the first object from the array into it, then checking if the next object has the same id as that object. To do this we use .every. If it does have the same id we go to the next object, if the id is different then we can add this to our array. Then when we look at our next object we check if it matches any of the now 2 object id's in our array, if it doesn't we can add it and so on.
const arr = [{id:1,field1:'foo1',field2:'bar1'},{id:1,field1:'foo2',field2:'bar2'},{id:2,field1:'foo3',field2:'bar3'},{id:2,field1:'foo4',field2:'bar4'},{id:3,field1:'foo5',field2:'bar5'},{id:3,field1:'foo6',field2:'bar6'}],
res = arr.splice(1).reduce((acc, elem) => acc.every(({id}) => id != elem.id) ? [...acc, elem] : acc, [arr[0]]);
console.log(res);
You can reduce your array, and check if there is an element that matches your criteria:
your_json_array.reduce((destArray, obj) => {
if (destArray.findIndex(i => i.id === obj.id) < 0) {
return destArray.concat(obj);
} else {
return destArray;
}
}, []);
That will give you another array with the desired data.
You can achieve this by using something along the lines of this, however it may be a better idea to do what I've done in my second example. Within the second example, you can see that it's storing a list of all duplicated ID's, the first example will only work for an even number of duplicated ID's... I.E. if there were three objects with the id property set to a value of 1, you'd still get the value 1 within the arrayvariable.
I mean there are other ways in which you could achieve this, you could use a filter function in there somewhere, etc. If you require more documentation on how to use the reduce function, I'd suggest the MDN Docs.
Finally with example 3, that's simply removing all duplicates, making use of more modern syntax and features such as destructuring.
Example 1
var objects = [{id: 1,a:'x'},{id:1,a:'x'},{id:2,a:'x'},{id:3,a:'x'}];
var array = objects.reduce((a, i) => {
a.indexOf(i.id) == -1 ? a.push(i.id) : a.splice(a.indexOf(i.id), 1);
return a;
}, []);
console.log(array);
Example 2
var objects = [{id: 1,a:'x'},{id:1,a:'x'},{id:2,a:'x'},{id:3,a:'x'}];
var array = objects.reduce((a, i) => {
objects.filter(e => e.id == i.id).length == 1 ? a.push(i.id) : null;
return a;
}, []);
console.log(array);
Example 3
const objects = [{id: 1,a:'x'},{id:1,a:'x'},{id:2,a:'x'},{id:3,a:'x'}];
const removeDuplicates = ([...o]) => o.reduce((a, i) => {
a.indexOf(i.id) == -1 ? a.push(i) : null;
return a
}, []);
console.log(removeDuplicates(objects));
1) To remove duplicates from the original array you can use filter() in conjunction with the optional argument that is passed as this to the filtering method.
2) To get an array with unique ids you can use map() on the previous result.
This is shown on next example:
const input = [
{id: 1, field1: "f1", field2: "f2"},
{id: 1, field1: "f1", field2: "f2"},
{id: 2, field1: "f1", field2: "f2"},
{id: 2, field1: "f1", field2: "f2"},
{id: 3, field1: "f1", field2: "f2"},
{id: 3, field1: "f1", field2: "f2"},
];
// Remove duplicates elements.
let res = input.filter(
function({id}) {return !this.has(id) && this.add(id)},
new Set()
);
console.log("No duplicates:", res);
// Get a map with only the IDs.
let ids = res.map(({id}) => id);
console.log("Ids:", ids);

Categories

Resources