I've looked at this Stack question, "Removing duplicate objects with Underscore for Javascript" and that is exactly what I am trying to do, but none of the examples work. In fact I can not get any iterator function to work with _.uniq.
_.uniq([1, 2, 1, 3, 1, 4]);
> [1, 2, 3, 4]
_.uniq([1, 2, 1, 3, 1, 4], false, function(a){ return a===4;});
> [1, 2, 3, 4]
_.uniq([1, 2, 1, 3, 1, 4], true, function(a){ return a===4;});
> [1, 2, 1, 3, 1, 4]
_.uniq([1, 2, 1, 3, 1, 4], false, function(a){ return false;});
> [1, 2, 3, 4]
_.uniq([1, 2, 1, 3, 1, 4], false, function(a){ return true;});
> [1, 2, 3, 4]
var people = [ { name: 'John', age: 20 }, { name: 'Mary', age: 31 }, { name: 'Kevin', age: 20 }];
_.uniq(people, false, function(p){ return p.age; });
> [ { age: 20, name: "John" },
{ age: 31, name: "Mary" },
{ age: 20, name: "Kevin" } ]
I would do:
_.uniq(_.map(people, function(p){ return p.age; }));
> [20, 31]
but it returns only the mapped value, not the original object.
Any help appreciated. I am using underscore version 1.1.7
I had the same problem. It is caused because _.uniq() returns a new reduced array so you have to assing it to a variable. So with this little correction it has to work.
var people = [ { name: 'John', age: 20 }, { name: 'Mary', age: 31 }, { name: 'Kevin', age: 20 }];
people = _.uniq(people, false, function(p){ return p.age; });
[ { age: 20, name: "John" },
{ age: 31, name: "Mary" } ]
Looks like comparison functions for _.uniq were introduced in 1.2.0
from the changelog:
_.uniq can now be passed an optional iterator, to determine by what criteria an object should be considered unique.
Related
Say I have an object as follows:
const data = {
title: 'firstStackOverFlowQuestion!',
id: '100',
datePosted: '04-10-2022',
options: [
{
questionId: 1,
difficultyLevel: 1,
difficultyScale: 10,
complexity: 2,
complexityScale: 10,
},
{
questionId: 2,
difficultyLevel: 4,
difficultyScale: 10,
complexity: 3,
complexityScale: 10,
},
{
questionId: 3,
difficultyLevel: 8,
difficultyScale: 10,
complexity: 6,
complexityScale: 10,
},
]
}
What is the cleanest way to reduce the options array to just two properties, to show the below:
const data = {
title: 'firstStackOverFlowQuestion',
id: '100',
datePosted: '04-10-2022',
options: [
{
questionId: 1,
difficultyLevel: 1,
},
{
questionId: 2,
difficultyLevel: 4,
},
{
questionId: 3,
difficultyLevel: 8,
},
],
}
Any suggestions would be most welcome! I imagine mapping, reducing, and /or using the spread operator would offer the cleanest solutions.
As you said, map + ... (spread):
const result = {
...data,
options: data.options.map(
({questionId, difficultyLevel}) => ({questionId, difficultyLevel})
)
}
Use map() along with destructuring.
data.options = data.options.map(
({questionId, difficultyLevel}) => ({questionId, difficultyLevel})
);
I have an array containing objects. Now I want to slice the array to a new object containing only those objects matching a certain property name and grouped by this property name. The thing is that I also have properties names that are different between them. The names and ID's repeat through the array of objects, but inside the new object, it should contain the ID and the name only once.
The original array looks like this:
let personArray = [
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl'
},
{
id_dentist: 2,
dentist_name: 'Lisa',
id_secretary: 9,
secretary_name: 'Beth',
id_security: 5,
security_name: 'Monica'
},
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl'
}
];
The new object should look like this:
let personObject = {
dentist: [
{ id_dentist: 1, dentist_name: 'John' },
{ id_dentist: 2, dentist_name: 'Lisa' },
],
secretary: [
{ id_secretary: 6, secretary_name: 'Paul' },
{ id_secretary: 9, secreatary_name: 'Beth' },
],
security: [
{ id_security: 3, security_name: 'Carl' },
{ id_security: 5, security_name: 'Monica' }
]
};
I appreciate the help.
As requested, I tried using reduce() and filter(), but I was not able to make them to split.
Here is the code:
const obj = personArray.reduce((acc, cur) => {
const key = Object.keys(cur).filter(f => /^id_/.test(f))[0].split('_')[1];
if (!acc[key]) acc[key] = [];
acc[key].push(cur);
return acc;
}, {});
console.log(obj);
About the strange data structure, I am getting those data from a database with a SELECT SQL syntax.
Here you go, This can be enhance further
let personArray = [{"id_dentist":1,"dentist_name":"John","id_secretary":6,"secretary_name":"Paul","id_security":3,"security_name":"Carl"},{"id_dentist":2,"dentist_name":"Lisa","id_secretary":9,"secretary_name":"Beth","id_security":5,"security_name":"Monica"},{"id_dentist":1,"dentist_name":"John","id_secretary":6,"secretary_name":"Paul","id_security":3,"security_name":"Carl"}];
const personObject = { dentist: [], secretary: [], security: [] };
const isExist = (arr, id, key) => arr.find(x => x[key] === id);
personArray.reduce((personObj, person) => {
const isDentistExists = isExist(personObj.dentist, person.id_dentist, 'id_dentist');
if (!isDentistExists) {
personObj.dentist.push({
id_dentist: person.id_dentist,
dentist_name: person.dentist_name
});
}
const isSecretaryExists = isExist(personObj.secretary, person.id_secretary, 'id_secretary');
if (!isSecretaryExists) {
personObj.secretary.push({
id_secretary: person.id_secretary,
secretary_name: person.secretary_name
});
}
const isSecurityExists = isExist(personObj.security, person.id_security, 'id_security');
if (!isSecurityExists) {
personObj.security.push({
id_security: person.id_security,
security_name: person.security_name
});
}
return personObj;
}, personObject);
console.log(personObject);
This is not a trivial algorithm. Here is a [mostly] functional implementation that handles an arbitrary number of ids and names
let personArray = [
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl',
},
{
id_dentist: 2,
dentist_name: 'Lisa',
id_secretary: 9,
secretary_name: 'Beth',
id_security: 5,
security_name: 'Monica',
},
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl',
},
]
const parsed = Object.fromEntries(
Object.keys(personArray[0])
.filter(key => key.startsWith('id_'))
.map(id => {
const uniqIds = [...new Set(personArray.map(person => person[id]))]
const [, name] = id.split('_')
const matchingPeople = uniqIds.map(uniqId => {
return personArray.find(person => uniqId === person[id])
})
return matchingPeople.map(person => ({
[id]: person[id],
[`${name}_name`]: person[`${name}_name`],
}))
})
.filter(entry => entry.length > 0)
.map(groupedPeople => {
const [name] = Object.keys(groupedPeople[0])
.find(key => key.includes('_name'))
.split('_')
return [name, groupedPeople]
})
)
console.log(parsed)
I probably went along a similar path as #Andrew did and, yes, I admit too, that this was not really trivial.
It might be a better idea to change your SQL selection in order to avoid receiving redundant data that makes these lengthy conversions necessary.
const arr = [
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl'
},
{
id_dentist: 2,
dentist_name: 'Lisa',
id_secretary: 9,
secretary_name: 'Beth',
id_security: 5,
security_name: 'Monica'
},
{
id_dentist: 1,
dentist_name: 'John',
id_secretary: 6,
secretary_name: 'Paul',
id_security: 3,
security_name: 'Carl'
}
], types=Object.keys(arr[0]).reduce((a,c,k)=>{
k=c.match(/id_(.*)/);
if(k) a.push(k[1]);
return a;
},[]);
const res=Object.entries(arr.reduce((a,c)=>{
types.forEach((t,id)=>{
id="id_"+t;
a[t+":"+c[id]]={[id]:c[id],[t+"_name"]:c[t+"_name"]}
});
return a;
},{})).reduce((a,[k,o],n)=>{
[n]=k.split(":");
(a[n]=a[n]||[]).push(o)
return a;
},{});
console.log(res);
I need to find objects in array by matching array of ids. Array of ids can be longer or equal to length of array of persons. I made it with forEach loop of persons array and inside used includes method to find matched id but not sure that it is the good approach. Is there a way to optimize searching algorithm?
const ids = [1, 4, 9, 7, 5, 3];
const matchedPersons = [];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
persons.forEach((person) => {
if (ids.includes(person.id)) {
matchedPersons.push(person);
}
});
console.log(matchedPersons);
codesanbox
You could take a Set with O(1) for the check.
const
ids = [1, 4, 9, 7, 5, 3],
persons = [{ id: 1, name: "James" }, { id: 2, name: "Alan" }, { id: 3, name: "Marry" }],
idsSet = new Set(ids),
matchedPersons = persons.filter(({ id }) => idsSet.has(id));
console.log(matchedPersons);
you better use filter. it does exactly what it is meant to do:
const ids = [1, 4, 9, 7, 5, 3];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
const matchedPersons = persons.filter(({id}) => ids.includes(id))
console.log(matchedPersons)
you can use Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
const ids = [1, 4, 9, 7, 5, 3];
const matchedPersons = [];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
const personsMap = new Map()
persons.forEach((person) => {
personsMap.set(person.id, person)
});
persons.forEach((person) => {
if (personsMap.has(person.id)) {
matchedPersons.push(personsMap.get(person.id));
}
});
console.log(matchedPersons);
i've the following array of object, (ets say that the timestemp here is mock and greater is the last one)
var firstArr = [{
id: 1,
a: 2,
timestemp: 111
}, {
id: 2,
a: 4,
timestemp: 222
}, {
id: 3,
a: 6,
timestemp: 333
}, {
id: 1,
a: 3,
timestemp: 777
}, {
id: 3,
a: 5555,
timestemp: 5555
}];
What I need to do is somehow filter this array and create new array with unique value.
I need at the end
var endArr = [{
id: 1,
a: 3,
timestemp: 777
}, {
id: 2,
a: 4,
timestemp: 222
}, {
id: 3,
a: 5555,
timestemp: 555
}];
As you can see I've filter this array by two things
uniqe ID (the entry 1 & 3 are exist just once)
timestemp (add just the object with the last timestemp)
How can I do that with array methods such as map/reduce/filter?
I try to do it with array.filter without success
You can use orderBy() and uniqBy() to get all items with unique ids that has the latest timestamps:
var firstArr = [{
id: 1,
a: 2,
timestamp: 111
}, {
id: 2,
a: 4,
timestamp: 222
}, {
id: 3,
a: 6,
timestamp: 333
}, {
id: 1,
a: 3,
timestamp: 777
}, {
id: 3,
a: 5555,
timestamp: 5555
}];
var result = _(firstArr)
.orderBy(['id', 'timestamp'], ['asc', 'desc'])
.uniqBy('id')
.value();
console.log(result);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
First of all you spelt timestamp as timestemp.
var firstArr = [{
id: 1,
a: 2,
timestamp: 111
}, {
id: 2,
a: 4,
timestamp: 222
}, {
id: 3,
a: 6,
timestamp: 333
}, {
id: 1,
a: 3,
timestamp: 777
}, {
id: 3,
a: 5555,
timestamp: 5555
}];
Here is the function:
function updateList(a_list) {
var seen = {};
for (var entry in a_list) {
var id = a_list[entry]["id"];
if (seen.hasOwnProperty(id)) {
var current_timestamp = seen[id]["timestamp"]
var check_timestamp = a_list[entry]["timestamp"]
if (current_timestamp < check_timestamp) {
seen[id] = a_list[entry];
}
} else {
seen[id] = a_list[entry];
}
}
var updated = [];
for (var newest in seen) {
updated.push(seen[newest]);
}
return updated;
}
https://jsfiddle.net/vpg3onqm/
If this is the answer you want, make sure to press upvote and the greentick.
Because of the requirement of using filter/map/reduce, I think I'd go with:
var lastestPerId = firstArr.reduce(function(state, curr) {
if(state[curr.id]){ // We've seen this id before
if(curr.timestemp > state[curr.id].timestemp) { // and its later
state[curr.id] = curr; // so, update this item to be the latest item
}
} else {
state[curr.id] = curr; // add because unknown
}
return state; // pass along the state to the next item in the array
}, {});
var endArr = Object.keys(lastestPerId)
.map(function (key) {return bestPerId[key]});
This creates an initial state ({}), loops over each item in firstArr. It tries to find out if the id is already known, and if it is it keeps track (in state) of the item with the highest timestemp. The state is passed along for each element in firstArr. Because the result is an object (with id's as keys, and the actual item as value), we need to map it back to an array.
This will work if firstArr is sorted by timestamp. The returned array will also be sorted by timestamp.
Starting from the end of the array (larger timestamps), include the current element in the new array if it was not already found. found array is used to keep track of extracted elements.
var found = [];
firstArr.reverse().filter( function(el){
if( found.indexOf( el.id ) === -1 ){
found.push( el.id );
return true;
}
return false;
}).reverse();
groupBy is your friend: https://lodash.com/docs#groupBy
_(firstArr)
.groupBy('id')
.map(function(x) {
return _(x).orderBy(x,['timestemp'], ['desc']).head();
})
.value();
https://jsfiddle.net/koljada/53esaqLz/3/
You can sort and then filter first id occurrence:
var firstArr = [
{ id: 1, a: 2, timestemp: 111 },
{ id: 2, a: 4, timestemp: 222 },
{ id: 3, a: 6, timestemp: 333 },
{ id: 1, a: 3, timestemp: 777 },
{ id: 3, a: 5555, timestemp: 5555 }
];
// sort firstArr by decrescent timestamp:
firstArr.sort((x,y)=>y.timestemp-x.timestemp);
// get only the first occurrence of each id:
var endArr = firstArr.filter((o,i)=>i==firstArr.findIndex(u=>u.id==o.id));
// job finished
output.innerHTML = JSON.stringify( endArr, null, 2 );
<pre id=output>
Is there a simple way to get the latter array from the former?
Source array :
[1, 2, 3, 4, 5]
Target structure:
[
{id: 1, text: idToName(1)},
{id: 2, text: idToName(2)},
{id: 3, text: idToName(3)},
{id: 4, text: idToName(4)},
{id: 5, text: idToName(5)}
]
It's easy to use Array.prototype.map here:
var array = [1, 2, 3, 4, 5];
var mapped = array.map(function(num) { return { id: num, text: idToName(num) }; });
With ES6 arrow functions it would be:
let mapped = array.map(num => ({ id: num, text: idToName(num) }));
var _x = [1, 2, 3, 4, 5].map(function(v){
return {"id":v, "text":idToName(v)};
});
Use .map, Live Fiddle
Or in ES6
var _x = [1, 2, 3, 4, 5].map(v => ({"id":v, "text":idToName(v)}));
Live Fiddle