I have two arrays of objects which I want to "Full Outer Join", like in SQL:
Dataset A:
[ { id: 1, name: "apple", color: "red" }, {id: 2, name: "banana", color: "yellow"} ]
Dataset B:
[ { id: 1, name: "apple", color: "blue" }, {id: 3, name: "mango", color: "green"} ]
Intended result:
[ { id: 1, dataset_a: { id: 1, name: "apple", color: "red" }
, dataset_b: { id: 1, name: "apple", color: "blue" }
}
, { id: 2, dataset_a: { id: 2, name: "banana", color: "yellow"}
, dataset_b: null
}
, { id: 3, dataset_a: null
, dataset_b: { id: 3, name: "mango", color: "green"}
}
]
The id's are unique.
Lodash may be used.
I have no restriction on ES version.
Instead of null, an empty object would be OK too. The id's don't necessarily need to be repeated, as shown below. So, this would be just as good:
[ { id: 1, dataset_a: { name: "apple", color: "red" }
, dataset_b: { name: "apple", color: "blue" }
}
, { id: 2, dataset_a: { name: "banana", color: "yellow"}
, dataset_b: {}
}
, { id: 3, dataset_a: {}
, dataset_b: { name: "mango", color: "green"}
}
]
Nina Scholtz solution, transformed into a a function:
fullOuterJoin(dataset_a_name, dataset_b_name, dataset_a, dataset_b, key) {
const getNullProperties = keys => Object.fromEntries(keys.map(k => [k, null]));
var data = { [dataset_a_name]:dataset_a, [dataset_b_name]:dataset_b },
result = Object
.entries(data)
.reduce((r, [table, rows]) => {
//forEach dynamic destructuring
rows.forEach(({ [key]:id, ...row }) => {
if (!r[id]) r.items.push(r[id] = { [key]:id, ...getNullProperties(r.tables) });
r[id][table] = row;
});
r.tables.push(table);
r.items.forEach(item => r.tables.forEach(t => item[t] = item[t] || null));
return r;
}, { tables: [], items: [] })
.items;
return result;
},
You could take a dynamic approach and store the wanted data sets in an object and iterate the entries form the object. Then group by id and get all items back.
This approach uses an object as hash table with id as key and an array as storage for the result set. If an id is not known, a new object with id and previously used keys with null value are used. Then the actual data set is added to the object.
Finally for missing tables null values are assigned as well.
const
getNullProperties = keys => Object.fromEntries(keys.map(k => [k, null]));
var dataset_a = [{ id: 1, name: "apple", color: "red" }, { id: 2, name: "banana", color: "yellow" }],
dataset_b = [{ id: 1, name: "apple", color: "blue" }, { id: 3, name: "mango", color: "green" }],
data = { dataset_a, dataset_b },
result = Object
.entries(data)
.reduce((r, [table, rows]) => {
rows.forEach(({ id, ...row }) => {
if (!r[id]) r.items.push(r[id] = { id, ...getNullProperties(r.tables) });
r[id][table] = row;
});
r.tables.push(table);
r.items.forEach(item => r.tables.forEach(t => item[t] = item[t] || null));
return r;
}, { tables: [], items: [] })
.items;
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A code snippet for specifically your need:
const datasetA = [ { id: 1, name: "apple", color: "red" }, {id: 2, name: "banana", color: "yellow"} ]
const datasetB = [ { id: 1, name: "apple", color: "blue" }, {id: 3, name: "mango", color: "green"} ]
const joined = [];
// datasetA
for (let i = 0; i < datasetA.length; i++) {
let item = {
id: datasetA[i].id,
dataset_a: datasetA[i],
};
joined.push(item);
}
// datasetB
for (let i = 0; i < datasetB.length; i++) {
const foundObject = joined.find(d => d.id === datasetB[i].id);
if (foundObject) {
foundObject['dataset_b'] = datasetB[i];
}
else {
let item = {
id: datasetB[i].id,
dataset_a: {},
dataset_b: datasetB[i],
};
joined.push(item);
}
}
console.log(joined);
var array1 = [ { id: 1, name: "apple", color: "red" }, {id: 2, name: "banana", color: "yellow"} ]
var array2 = [ { id: 1, name: "apple", color: "blue" }, {id: 3, name: "mango", color: "green"} ]
var array_sum = array1.concat(array2)
var array_result = []
array_sum.forEach(function(candidate, index){
var obj_id = candidate.id;
delete candidate.id
if(array_result.length == 0){
array_result.push({
"id": obj_id,
["dataset_" + index]: candidate
})
}else{
for(var i=0; i<array_result.length; i++){
if(array_result[i].id == obj_id){
array_result[i]["dataset_" + index] = candidate
break;
}else if(i == array_result.length - 1){
array_result.push({
"id": obj_id,
["dataset_" + index]: candidate
})
}
}
}
})
console.log(array_result)
Related
I have 2 arrays:
0: {id: 2, name: "TMA"}
1: {id: 3, name: "Hibbernate"}
0: {id: 1, name: "FB.DE"}
1: {id: 2, name: "TMA"}
2: {id: 3, name: "Hibbernate"}
3: {id: 4, name: "Event.it A"}
4: {id: 5, name: "Projket 2"}
5: {id: 6, name: "Projekt 1"}
I want to compare them and delete the objects with the id 2 and 3 cause both arrays have them and thats the similarity.
This is my Code so far:
const projectListOutput = projectsOfPersonArray.filter(project => data.includes(project));
console.log(projectListOutput);
But every time i run this projectListOutput is empty.
When using includes dont compare objects, Just build data as array of strings. Remaining code is similar to what you have.
arr1 = [
{ id: 2, name: "TMA" },
{ id: 3, name: "Hibbernate" },
];
arr2 = [
{ id: 1, name: "FB.DE" },
{ id: 2, name: "TMA" },
{ id: 3, name: "Hibbernate" },
{ id: 4, name: "Event.it A" },
{ id: 5, name: "Projket 2" },
{ id: 6, name: "Projekt 1" },
];
const data = arr1.map(({ id }) => id);
const result = arr2.filter(({ id }) => !data.includes(id));
console.log(result);
Your data array probably does not contain the exact same object references than projectsOfPersonArray. Look at the code below:
[{ foo: 'bar' }].includes({ foo: 'bar' });
// false
Objects look equal, but they don't share the same reference (= they're not the same).
It's safer to use includes with primitive values like numbers or strings. You can for example check the ids of your objects instead of the full objects.
You compare different objects, so every object is unique.
For filtering, you need to compare all properties or use a JSON string, if the order of properties is equal.
var exclude = [{ id: 2, name: "TMA" }, { id: 3, name: "Hibbernate" }],
data = [{ id: 2, name: "TMA" }, { id: 3, name: "Hibbernate" }, { id: 1, name: "FB.DE" }, { id: 2, name: "TMA" }, { id: 3, name: "Hibbernate" }, { id: 4, name: "Event.it A" }, { id: 5, name: "Projket 2" }, { id: 6, name: "Projekt 1" }],
result = data.filter(project =>
!exclude.some(item => JSON.stringify(item) === JSON.stringify(project))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do something similar to the next:
const source = [{
id: 1,
name: "FB.DE"
},
{
id: 2,
name: "TMA"
},
{
id: 3,
name: "Hibbernate"
},
{
id: 4,
name: "Event.it A"
},
{
id: 5,
name: "Projket 2"
},
{
id: 6,
name: "Projekt 1"
}
]
const toRemove = [{
id: 2,
name: "TMA"
},
{
id: 3,
name: "Hibbernate"
}
]
/**create object where keys is object "id" prop, and value is true**/
const toRemoveMap = toRemove.reduce((result, item) => ({
...result,
[item.id]: true
}), {})
const result = source.filter(item => !toRemoveMap[item.id])
You can make function from it:
function removeArrayDuplicates (sourceArray, duplicatesArray, accessor) {
const toRemoveMap = duplicatesArray.reduce((result, item) => ({
...result,
[item[accessor]]: true
}), {});
return sourceArray.filter(item => !toRemoveMap[item[accessor]])
}
removeArrayDuplicates(source, toRemove, 'id')
Or even better, you can make it work with a function instead of just property accessor:
function removeDuplicates (sourceArray, duplicatesArray, accessor) {
let objectSerializer = obj => obj[accessor];
if(typeof accessor === 'function') {
objectSerializer = accessor;
}
const toRemoveMap = duplicatesArray.reduce((result, item) => ({
...result,
[objectSerializer(item)]: true
}), {});
return sourceArray.filter(item => !toRemoveMap[objectSerializer(item)])
}
removeDuplicates(source, toRemove, (obj) => JSON.stringify(obj))
This function will help you merge two sorted arrays
var arr1 = [
{ id: 2, name: 'TMA' },
{ id: 3, name: 'Hibbernate' },
]
var arr2 = [
{ id: 1, name: 'FB.DE' },
{ id: 2, name: 'TMA' },
{ id: 3, name: 'Hibbernate' },
{ id: 4, name: 'Event.it A' },
{ id: 5, name: 'Projket 2' },
]
function mergeArray(array1, array2) {
var result = []
var firstArrayLen = array1.length
var secondArrayLen = array2.length
var i = 0 // index for first array
var j = 0 // index for second array
while (i < firstArrayLen || j < secondArrayLen) {
if (i === firstArrayLen) { // first array doesn't have any other members
while (j < secondArrayLen) { // we copy rest members of first array as a result
result.push(array2[j])
j++
}
} else if (j === secondArrayLen) { // second array doesn't have any other members
while (i < firstArrayLen) { // we copy the rest members of the first array to the result array
result.push(array1[i])
i++
}
} else if (array1[i].id < array2[j].id) {
result.push(array1[i])
i++
} else if (array1[i].id > array2[j].id) {
result.push(array2[j])
j++
} else {
result.push(array1[i])
i++
j++
}
}
return result
}
console.log(mergeArray(arr1,arr2));
I have the following array of deeply nested objects:
const data = [
{
name: "foo",
children:[
{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [
{
count: 3,
name: "C",
children: [
{
count: 4,
name: "D"
}
]
}
]
}
]
The way I'd like to transform this would be such as:
const expectedStructure = [
{
count: 1,
name: "A",
label: "foo = A"
},
{
count: 2,
name: "B",
label: "foo = B"
},
{
count: 3,
name: "C",
label: "bar = C"
},
{
count: 4,
name: "D",
label: "bar = D"
}
]
I created recursive function that transforms nested array into array of flat objects.
Here's my code:
function getChildren(array, result=[]) {
array.forEach(({children, ...rest}) => {
result.push(rest);
if(children) {
getChildren(children, result);
}
});
return result;
}
And here's output I get:
[ { name: 'foo' },
{ count: 1, name: 'A' },
{ count: 2, name: 'B' },
{ name: 'bar' },
{ count: 3, name: 'C' },
{ count: 4, name: 'D' } ]
The problem is that I need to add label field to every object in my output array, and I can't find a solution without iterating multiple times through the final array to make desired transformation. How to properly insert label field without hugely augmenting complexity of the function?
Check each iteration whether the current item is a "parent" item, and reassign label if it is.
const data = [{name:"foo",children:[{count:1,name:"A"},{count:2,name:"B"}]},{name:"bar",children:[{count:3,name:"C",children:[{count:4,name:"D"}]}]}];
function getChildren(array, result = [], label = "") {
array.forEach(({ children, name, count }) => {
if (!label || name[1]) {
label = `${name} = `;
}
if (count) {
result.push({ count, name, label: label + name });
}
if (children) {
getChildren(children, result, label);
}
});
return result;
}
const res = getChildren(data);
console.log(res);
You can use a different function for the nested levels, so you can pass the top-level name properties down through all those recursion levels.
function getTopChildren(array, result = []) {
array.forEach(({
name,
children
}) => {
if (children) {
getChildren(children, name, result);
}
});
return result;
}
function getChildren(array, name, result) {
array.forEach(({
children,
...rest
}) => {
rest.label = `${name} = ${rest.name}`;
result.push(rest);
if (children) {
getChildren(children, name, result);
}
});
}
const data = [{
name: "foo",
children: [{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [{
count: 3,
name: "C",
children: [{
count: 4,
name: "D"
}]
}]
}
]
console.log(getTopChildren(data));
You can also do this recursively with flatMap based on whether or not a parent has been passed into the recursive call :
const data = [{
name: "foo",
children: [{
count: 1,
name: "A"
},
{
count: 2,
name: "B"
}
]
},
{
name: "bar",
children: [{
count: 3,
name: "C",
children: [{
count: 4,
name: "D"
}]
}]
}
];
function flatten(arr, parent = null) {
return parent
? arr.flatMap(({name, count, children}) => [
{name, count, label: `${parent} = ${name}`},
...flatten(children || [], parent)
])
: arr.flatMap(({name, children}) => flatten(children || [], name));
}
console.log(flatten(data));
Sometimes it's a little easier to reason about the code and write it clearly using generators. You can yield* from the recursive calls:
const data = [{name: "foo",children:[{count: 1,name: "A"},{ count: 2,name: "B"}]},{name: "bar",children: [{count: 3,name: "C",children: [{count: 4,name: "D"}]}]}]
function* flat(input, n){
if (!input) return
if (Array.isArray(input)) {
for (let item of input)
yield* flat(item, n)
}
let _name = n || input.name
if ('count' in input) {
yield { count:input.count, name:input.name, label:`${_name} = ${input.name}`}
}
yield* flat(input.children, _name)
}
let g = [...flat(data)]
console.log(g)
The function returns a generator, so you need to spread it into a list [...flat(data)] if you want a list or iterate over it if you don't need to store the list.
I have a specific case and I don't even know if it is possible to achieve.
Given the input array.
var originalArr = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[{ ID: 1, name: 'Lettuce' }, { ID: 2, name: 'Brocoli' }]
];
I would like to iterate over the inner arrays and pick the ID's from objects then create new one in place of array. So my output should look something like this.
var output = [
{
'1': {
name: 'Lettuce',
ID: 1
},
'2': {
name: 'Brocoli',
ID: 2
}
},
{
'3': {
name: 'Beef',
ID: 3
},
'4': {
name: 'Macaroni',
ID: 4
},
'5': {
name: 'Sauce#1'
}
}
];
It is easy to iterate over the inner arrays with map but how can I create new Object in place of the array and have its key value dynamically pulled up ? And is it even possible given my input to produce the desired output.
Use map and reduce
originalArr.map( s => //iterate outer array
s.reduce( (acc, c) => ( //iterate inner array using reduce
acc[c.ID] = c, acc //assign the id as key to accumulator and return the accumulator
) , {}) //initialize accumulator to {}
)
Demo
var originalArr = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[{ ID: 1, name: 'Lettuce' }, { ID: 2, name: 'Brocoli' }]
];
var output = originalArr.map( s => s.reduce( (acc, c) => ( acc[c.ID] = c, acc ) , {}) );
console.log(output);
You can achieve using recursion with pure javascript
var originalArr = [
[{
ID: 3,
name: 'Beef'
}, {
ID: 4,
name: 'Macaroni'
}, {
ID: 5,
name: 'Sauce#1'
}],
[{
ID: 1,
name: 'Lettuce'
}, {
ID: 2,
name: 'Brocoli'
}]
]
function bindInObject(object, array) {
for (var i = 0; i < array.length; i++) {
var it = array[i];
if (it instanceof Array) {
bindInObject(object, it);
} else {
var id = it.ID;
object[id] = it;
}
}
}
var output = {};
bindInObject(output, originalArr);
console.log(output)
const original_array = [
[
{ ID: 3, name: 'Beef' },
{ ID: 4, name: 'Macaroni' },
{ ID: 5, name: 'Sauce#1' }
],
[
{ ID: 1, name: 'Lettuce' },
{ ID: 2, name: 'Brocoli' }
]
]
let new_array = []
for (let i=0; i < original_array.length; i++) {
if (original_array[i + 1]) new_array =
new_array.concat(original_array[i].concat(original_array[i+1]))
}
let output = []
for (let i=0; i<new_array.length; i++) {
output.push({[new_array[i].ID]: new_array[i]})
}
Let's say I have two arrays of objects:
let array1 = [
{
id: 1,
name: 'snow'
},
{
id: 4,
name: 'jo'
},
{
id: 8,
name: 'bran'
},
{
id: 12,
name: 'gondo'
},
{
id: 13,
name: 'peter'
}
]
let array2 = [
{
id: 3,
name: 'brim'
},
{
id: 4,
name: 'not-jo'
},
{
id: 8,
name: 'not-bran'
},
{
id: 13,
name: 'spleen'
}
]
I want to find all the objects in array2 that match by id with array1, and change their name values to match the name values in array1.
In my pseudocode:
array1.forEach((person)=> {
if(person.id is equal to person.id in array2){
person.name = person.name in array1
}
})
Try this :
array2.map(function(x){
var result=array1.filter(a1=> a1.id==x.id);
if(result.length>0) { x.name=result[0].name;}
return x })
Loop through each element and compare there ids. If they match update the name:
for (var i = 0; i < array2.length; i++) {
for (var k = 0; k < array1.length; k++) {
if (array2[i].id == array1[k].id) {
array2[i].name = array1[k].name;
break;
}
}
}
Try it:
let array1 = [{
id: 1,
name: 'snow'
},
{
id: 4,
name: 'jo'
},
{
id: 8,
name: 'bran'
},
{
id: 12,
name: 'gondo'
},
{
id: 13,
name: 'peter'
}
]
let array2 = [{
id: 3,
name: 'brim'
},
{
id: 4,
name: 'not-jo'
},
{
id: 8,
name: 'not-bran'
},
{
id: 13,
name: 'spleen'
}
]
console.log(array2);
for (var i = 0; i < array2.length; i++) {
for (var k = 0; k < array1.length; k++) {
if (array2[i].id == array1[k].id) {
array2[i].name = array1[k].name;
break;
}
}
}
console.log(array2);
You could use a Map and store the reference to each id and then update array2.
let array1 = [{ id: 1, name: 'snow' }, { id: 4, name: 'jo' }, { id: 8, name: 'bran' }, { id: 12, name: 'gondo' }, { id: 13, name: 'peter' }],
array2 = [{ id: 3, name: 'brim' }, { id: 4, name: 'not-jo' }, { id: 8, name: 'not-bran' }, { id: 13, name: 'spleen' }],
map = new Map(array1.map(o => [o.id, o]));
array2.forEach(o => map.has(o.id) && (o.name = map.get(o.id).name));
console.log(array2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do something like that
var idDict = {};//a dictionary to lookup array1 id
for(var idx=0;idx<array1.length;idx++) {
idDict[array1[idx].id] = array1[idx].name;
}
for(var idx=0;idx<array2.length;idx++) {
if (idDict[array2[idx].id]) {
array2[idx].name = idDict[array2[idx].id];
}
}
array2.filter(o => array1.find(o2 => o.id === o2.id))
I am using text to search over some data. Some of the data is nested, and the other part is not.
If it returns a result from the nested part of the data, it should only return that nested value (and it's parent value).
I've put it on codeSandbox too- https://codesandbox.io/s/Q0w16jLP0
The function:
function filterTypes(items, search) {
return items.filter(items => {
const nestedName = items.typechild.map(x => x.name.toLowerCase());
const name = items.name.toLowerCase();
return search.every(x => {
return name.includes(x) || nestedName.some(v => v.includes(x));
});
});
}
The datastructure:
{
"name": "Food",
"typechild": [{
"name": "Fruit", "id":12,
}, {
"name": "Vegetable", "id":13,
}]
}
How it works now:
it returns all the children of Food.
Desired result:
If the filter has value Fruit, it should return...
Food as the title
Fruit below that title
Desired datastructure
{
"name": "Food",
"typechild": [{
"name": "Fruit", "id":12,
}]
}
I would use a recursive approach:
function check(el,name){
if(el.name.includes(name)){
return [el];
}
if(el.typechild && el.typechild.find){
var child=el.typechild.find(function(child){
return check(child,name);
});
}
if(child){
child=check(child,name);
child.unshift(el);
return child;
}
return false;
}
It will return
[Food Object, Fruit Object]
Solution with mutating the the original data.
var data = [{ name: "Food", typechild: [{ name: "Fruit", level: 2, color: "#fff" }, { name: "Vegetable", level: 2, color: "#fff" }] }, { name: "Entertainment", typechild: [{ name: "Book", level: 2, color: "#fff" }, { name: "Movie", level: 2, color: "#fff" }, { name: "Bar", level: 3, color: "#fff" }] }, { name: "Misc", typechild: [{ name: "Foo", level: 2, color: "#fff" }] }],
search = 'Fruit',
index = data.length,
temp;
while (index--) {
temp = data[index].typechild.filter(o => o.name === search);
if (temp.length) {
data[index].typechild = temp;
} else {
data.splice(index, 1);
}
}
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }