How union arrays with validate only one property - javascript

Joining of Arrays.
I'm in need of running a "Join Array" objects, but, I need duplicated objects to be removed, see:
Example
var objArray1 = [
{ Id: 1, Name: 'João', Order: 2 },
{ Id: 2, Name: 'Pedro', Order: 5 }
];
var objArray2 = [
{ Id: 2, Name: 'Pedro', Order: 6 },
{ Id: 3, Name: 'Manoel', Order: 9 }
];
Actual code:
var result = _.union(objArray1,objArray2);
=> [
{ Id: 1, Name: 'João', Order: 2 },
{ Id: 2, Name: 'Pedro', Order: 5 },
{ Id: 2, Name: 'Pedro', Order: 6 },
{ Id: 3, Name: 'Manoel', Order: 9 }
];
I need this result:
[
{ Id: 1, Name: 'João', Order: 2 },
{ Id: 2, Name: 'Pedro', Order: 5 },
{ Id: 3, Name: 'Manoel', Order: 9 }
];
Basic I need join arrays with filter the one property, I need is possible with For but I would like a better solution

use underscore unique function as follows
var result = _.uniq(_.union(objArray1, objArray2), false, function(item){ return item.Id; });
not 100% sure if the false should be true
or, as seems to be a trend on SO - the sexy ES2015 version
var result = _.uniq(_.union(objArray1, objArray2), false, item => item.Id);

Related

Group array of objects by multiple nested values

I have an array of objects which presents tasks. These tasks are categorized (primary / secondary category).
let tasks = [
{
id: 1,
name: 'Cleanup desk',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 2,
name: 'Surfaces'
}
},
{
id: 2,
name: 'Cleanup office floors',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 3,
name: 'Ground'
}
},
{
id: 3,
name: 'Water plants',
primary_category: {
id: 2,
name: 'Outdoor'
},
secondary_category: {
id: 3,
name: 'Irrigation'
}
}
];
I now try to create a categories accordion in my frontend and therefore need to group my array differently. The structure should look like:
1) primary category
> secondary category
> tasks
> secondary category
> tasks
2) primary category
> secondary category
> tasks
Therefore I'm trying to achieve a structure similar to this:
let tasks_categorized = [
{
id: 1,
name: 'Indoor',
secondary_categories: [
{
id: 2,
name: 'Surfaces',
tasks: [
{
id: 1,
name: 'Cleanup desk'
}
]
},
{
id: 3,
name: 'Ground',
tasks: [
{
id: 2,
name: 'Cleanup office floors'
}
]
}
]
},
{
id: 2,
name: 'Outdoor',
secondary_categories: [
{
id: 3,
name: 'Irrigation',
tasks: [
{
id: 3,
name: 'Water plants'
}
]
}
]
}
];
I tried using groupBy by lodash but this does not allow grouping by multiple nested key-value pairs. Does anybody know an approach to solve this?
Thank you in advance!
The following provided approach is going to achieve the expected result within a single reduce cycle without any further nested loops.
It does so by implementing a reducer function which creates and/or aggregates at time a prioritized category task while iterating another task array. But most importantly it keeps track of a task item's related primary and secondary categories via a Map based lookup. This lookup reference together with a result array are properties of this function's return value which has to be partly provided as the reduce method's initial value as follows ... { result: [] }.
function createAndAggregatePrioritizedCategoryTask(
{ lookup = new Map, result }, item
) {
const { primary_category, secondary_category, ...taskRest } = item;
const { id: primaryId, name: primaryName } = primary_category;
const { id: secondaryId, name: secondaryName } = secondary_category;
const primaryKey = [primaryId, primaryName].join('###');
const secondaryKey = [primaryKey, secondaryId, secondaryName].join('###');
let primaryCategory = lookup.get(primaryKey);
if (!primaryCategory) {
// create new primary category item.
primaryCategory = {
id: primaryId,
name: primaryName,
secondary_categories: [],
};
// store newly created primary category reference in `lookup`.
lookup.set(primaryKey, primaryCategory);
// push newly created primary category reference to `result`.
result.push(primaryCategory);
}
let secondaryCategory = lookup.get(secondaryKey);
if (!secondaryCategory) {
// create new secondary category item.
secondaryCategory = {
id: secondaryId,
name: secondaryName,
tasks: [],
};
// store newly created secondary category reference in `lookup`.
lookup.set(secondaryKey, secondaryCategory);
// push newly created secondary category reference into the
// `secondary_categories` array of its related primary category.
primaryCategory
.secondary_categories
.push(secondaryCategory);
}
// push the currently processed task-item's rest-data as
// item into the related secondary category's `task` array.
secondaryCategory
.tasks
.push(taskRest);
return { lookup, result };
}
let tasks = [{
id: 1,
name: 'Cleanup desk',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 2, name: 'Surfaces' },
}, {
id: 2,
name: 'Cleanup office floors',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 3, name: 'Ground' },
}, {
id: 3,
name: 'Water plants',
primary_category: { id: 2, name: 'Outdoor' },
secondary_category: { id: 3, name: 'Irrigation' },
}];
const { result: tasks_categorized } = tasks
.reduce(createAndAggregatePrioritizedCategoryTask, { result: [] });
console.log({ tasks_categorized });
.as-console-wrapper { min-height: 100%!important; top: 0; }
You could take a dynamic approach with an array of arrays with functions and keys for the nested arrays.
const
tasks = [{ id: 1, name: 'Cleanup desk', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 2, name: 'Surfaces' } }, { id: 2, name: 'Cleanup office floors', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 3, name: 'Ground' } }, { id: 3, name: 'Water plants', primary_category: { id: 2, name: 'Outdoor' }, secondary_category: { id: 3, name: 'Irrigation' } }],
groups = [
[o => o, 'primary category'],
[o => o.primary_category, 'secondary category'],
[o => o.secondary_category, 'tasks']
],
result = tasks.reduce((r, o) => {
groups.reduce((parent, [fn, children]) => {
const { id, name } = fn(o);
let item = (parent[children] ??= []).find(q => q.id === id)
if (!item) parent[children].push(item = { id, name });
return item;
}, r);
return r;
}, {})[groups[0][1]];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How do I check that the correct objects are being returned via a function (expect function returns [Function Anonymous])?

I have a function:
const sort =
(pets,attribute) =>
_(pets)
.filter(pets=> _.get(pets, attribute) !== null)
.groupBy(attribute)
.value()
Some data:
const pets= [{
id: 1,
name: 'snowy',
},
{
id: 2,
name: 'quacky',
},
{
id: 3,
name: 'snowy',
age: 5,
},
{
id: null,
name: null,
age: null
}
]
const attribute = 'name'
I am currently trying to write some Jest unit tests for this, that tests if the function returns the correct resultant object after being sorted based off an attribute.
The result of:
sort(pets,attribute) is something like this:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Is there a way I can do a expect to match the two objects snowy and quacky here?
The thing I want to test for is that the objects are being correctly grouped by the key.
I've tried using something like
const res = sort(users,key)
expect(res).toEqual(
expect.arrayContaining([
expect.objectContaining({'snowy' : [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5 } ]},
expect.objectContaining({'quacky' : [ { id: 2, name: 'quacky' } ]}))
])
)
which doesn't seem to work, the received output seems to output:
Expected: ArrayContaining [ObjectContaining {"snowy": [{"id": 1, "name": "snowy"}, {"age": 5, "id": 3, "name": "snowy"}]}]
Received: [Function anonymous]
I am unsure what the best method to test this kind of function is either so advice on that would be appreciated.
If this is what your arrangeBy() returns:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Then you can just do:
const expected = {
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
const res = arrangeBy(users,key)
expect(res).toEqual(expected)
But looking at your Error message I guess you have something else mixed up. In the beginning you listed the implementation of a sort function which seems to not be used in the test. Where is arrangeBy coming from now.
Please provide more code examples.

Remove duplicate elements of an array, in an object of arrays, dynamically

I have checked other solutions but none fit the criterion of my problem
This solution does not have the ability to dynamically check each node
Problem summarized
I wish to create an algorithm that is able to check an object that has nodes of different data types, for duplicated objects in nodes that are specifically of the datatype array.
I have the following dataset:
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 2 }, { id: 3 }],
};
I wish to be able to dynamically check which of the objects (or nodes? and the algo has to recognize that it is an array) has duplicates, and reduce them to be in this form:
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 3 }],
};
EDIT
The algorithm needs to be able to handle a dynamic number of nodes (example 1), however , the duplicates will only happen 1 level down (Thanks for pointing out).
example 1 (there is 1 less node here ) :
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 2 }],
};
Here is my proposed solution to remove duplicate elements from any array in the task object:
const uniq = array => {
const map = {};
const result = [];
for (let i = 0; i < array.length; i++) {
// since elements can be objects, need to do a deep comparison.
const element = JSON.stringify(array[i]);
if (map[element] === undefined) {
map[element] = true;
result.push(array[i]);
}
}
return result;
}
const task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 2 }, { id: 3 }],
};
for (const key in task) {
if (Array.isArray(task[key])) {
task[key] = uniq(task[key])
}
}
console.log('deduped:', task);

Javascript re-order array of object by value

How do I re-order array of object showing below by follow value. If follow value is not -1, move the item below to the item that has the id value same as follow value.
Here is the example.
let charObj = [
{ id: 8, name: 'Catelyn Stark', follow: -1 },
{ id: 7, name: 'Jaime Lannister', follow: 8 },
{ id: 3, name: 'Jon Snow', follow: -1 },
{ id: 4, name: 'Daenerys Targaryen', follow: 7 },
{ id: 5, name: 'Sansa Stark', follow: 4 }
];
Expected output will be;
let charObj = [
{ id: 8, name: 'Catelyn Stark', follow: -1 },
{ id: 7, name: 'Jaime Lannister', follow: 8 },
{ id: 4, name: 'Daenerys Targaryen', follow: 7 },
{ id: 5, name: 'Sansa Stark', follow: 4 },
{ id: 3, name: 'Jon Snow', follow: -1 }
];
Not sure if I can use sort(). What is the best way to re-order this object?
I think this will do what you're asking. I'm sure it could be made more efficient, but unless your list gets quite large that shouldn't make much practical difference. Also, this assumes any character will only have one follower. If that's not the rule, then the function will have to be adjusted.
let charObj = [
{ id: 8, name: "Catelyn Stark", follow: -1 },
{ id: 7, name: "Jaime Lannister", follow: 8 },
{ id: 3, name: "Jon Snow", follow: -1 },
{ id: 4, name: "Daenerys Targaryen", follow: 7 },
{ id: 5, name: "Sansa Stark", follow: 4 }
];
function sortChars(chars) {
let result = [];
let leaders = chars.filter(c => c.follow === -1);
for (let i = 0; i < leaders.length; i++) {
let current = leaders[i];
while (current) {
result.push(current);
let next = charObj.find(c => c.follow === current.id);
current = next;
}
}
return result;
}
console.log(sortChars(charObj));

How to convert JS keyed object into keyed array while skipping undefined?

How to convert this JS object:
users = {
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
// 3 is missing
4: { id: 4, name: "..." },
...
}
into an array:
usersArray = [
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
// 3 is missing -- I do not want undefined here
4: { id: 4, name: "..." },
...
]
The key is that I do not want undefined, blank spaces, the key must be the object Id. The reason I do not want blanks/undefined entries is because I use vue.js and I am pretty sure it would not iterate over an array with undefined values in v-for.
Something like: https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143/2 but without the additional array storing just ids.
Is this possible?
You can use Object.values()
const users = {
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
4: { id: 4, name: "..." },
};
console.log(Object.values(users))
You could create a sparse array with missing indices by assigning the object to an array. As you see, forEach or map does not visit sparse items.
var users = { 1: { id: 1, name: "..." }, 2: { id: 2, name: "..." }, 4: { id: 4, name: "..." } },
array = Object.assign([], users);
array.forEach((o, i) => console.log(i, o));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources