Complicated array merging (keeping order) - javascript

I've got two arrays as follows:
const array1 = [
{
id: 'a1',
text: 'some text'
},
{
id: 'a2',
text: 'some more text',
},
{
id: 'a3',
text: 'some more text',
},
]
and
const array2 = [
{
id: 'a1',
text: 'some text'
},
{
id: 'ab12',
text: 'some text'
},
{
id: 'abc123',
text: 'some further text'
},
{
id: 'a3',
text: 'some more text'
},
{
id: 'bx44',
text: 'some more text'
},
]
I would like to combine these arrays with information has to whether each item has a "matching" 'id' property as the other array, so:
combined = [
{
id: 'a1',
text: 'some text',
info: 'in-both'
},
{
id: 'a2',
text: 'some text',
info: 'only-array1',
},
{
id: 'ab12',
text: 'some text',
info: 'only-array2',
},
{
id: 'abc123',
text: 'some further text',
info: 'only-array2',
},
{
id: 'a3',
text: 'some more text',
info: 'in-both',
},
{
id: 'bx44',
text: 'some more text',
info: 'only-array2',
},
]
I'm trying to keep the "inherent" order, so that items being only present in array1 or array2 end up between matches (items in both arrays) relative to their index. For example, 'a2' (only present in array1) comes after 'a1' but before 'a3'. If, between matches, there are multiple items being present only in array1 or array2, I'm trying to achieve so that the ones belonging to array1 come first (in the example, 'a2' comes before 'ab12' and 'abc123').
The code so far:
array1.reduce((all, curr, a1index, a1array) => {
let correspondingItemInArray2Index = array2.findIndex(a2item => a2item.id === curr.id);
if(correspondingItemInArray2Index === -1) {
curr.info = 'only-in-array1';
}
else if(correspondingItemInArray2Index === a1index) {
// Items are on same level...
curr.info = 'in-both';
}
else {
... // I need to find all items of array2 until the next 'match' of ids?
}
}
, []);

You could get common items and iterate all between.
function merge(...data) {
var common = data.map(a => a.map(({ id }) => id)).reduce((a, b) => a.filter(v => b.includes(v))),
indices = data.map(_ => 0),
result = [];
while (indices.every((l, i) => l < data[i].length)) {
indices = indices.map((j, i) => {
while (j < data[i].length && !common.includes(data[i][j].id)) {
result.push(Object.assign({}, data[i][j++], { info: ['a', 'b'][i] }));
}
return j;
});
if (indices.some((l, i) => l >= data[i].length)) break;
result.push(Object.assign({}, data[0][indices[0]], { info: 'both' }));
indices = indices.map(v => v + 1);
}
indices.forEach((j, i) => {
while (j < data[i].length) {
result.push(Object.assign({}, data[i][j++], { info: ['a', 'b'][i] }));
}
});
return result;
}
var array1 = [{ id: 'a1', text: 'some text' }, { id: 'a2', text: 'some more text' }, { id: 'a3', text: 'some more text' }],
array2 = [{ id: 'a1', text: 'some text' }, { id: 'ab12', text: 'some text' }, { id: 'abc123', text: 'some further text' }, { id: 'a3', text: 'some more text' }, { id: 'bx44', text: 'some more text' }],
result = merge(array1, array2);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Walk through a nested array using index in JavaScript

I want walk through a nested array and need to find the target element in the array. An example path [2, 1] should return {text: 'More 2'} and path [2, 2, 1] should return { text: 'Other-2' }. I tried lodash functions but no luck yet.
My Nested array is given below:
var data = [
{ text: 'Item 1', },
{ text: 'Item 2', },
{
text: 'More',
children: [
{ text: 'More 1', children: [] },
{ text: 'More 2'},
{ text: 'Other', children:[ {text: 'Other-1'}, {text: 'Other-2'}, {text: 'Other-3'} ] }
]
}
];
Well, it's not a multi-dimensional array, nor is it a raggedy array-of-arrays. It's an array of objects (that happen contain other arrays of objects that happen to...).
Lodash's _.get() ought to do the trick for you:
const _ = require('lodash');
const data = data = [
{ text: 'Item 1', },
{ text: 'Item 2', },
{
text: 'More',
children: [
{ text: 'More 1', children: [] },
{ text: 'More 2'},
{ text: 'Other', children:[ {text: 'Other-1'}, {text: 'Other-2'}, {text: 'Other-3'} ] }
]
}
];
const widget = _.get(obj, '[2].children[1]');
console.log('widget',widget);
Or... roll your own. It's not that hard to walk the tree:
function select(data, ...path) {
let i = path.shift() ;
let node = data[i] ;
while ( node && (i=path.shift()) !== undefined ) {
node = node?.children?.[i] ;
}
return node ;
}
const widget = select( data, 2, 1 );
console.log(widget);

Count duplicates in an array and return new array with a key value derived from another array in Javascript

Following on from my previous question, I'd like to change and extend the capability of what was suggested.
Here's the data I've got:
const things = [
{
id: 1,
title: 'Something',
categoryId: 1
},
{
id: 2,
title: 'Another thing',
categoryId: 1
},
{
id: 3,
title: 'Yet another thing',
categoryId: 2
},
{
id: 4,
title: 'One more thing',
categoryId: 4
},
{
id: 5,
title: 'Last thing',
categoryId: 4
}
]
const categories = [
{
id: 1,
title: 'Category 1'
},
{
id: 2,
title: 'Category 2'
},
{
id: 4,
title: 'Category 3'
}
]
Previously I've been shown how to do something along these lines:
const duplicatesCountWithTitle = (things, categories) => {
const thingsReduced = things.reduce((hash, { categoryId }) => {
hash[categoryId] = (hash[categoryId] || 0) + 1
return hash
}, {})
}
As you'd be able to tell, the problem with this is that it actually returns a new object, and not a new array. Also I'd like to join the categoryTitle from the categories array with the results of the duplicated count from the things array, based on the categoryId matching the id in categories.
// currently the above returns an object in the structure of:
// {
// 1: 2,
// 2: 1,
// 4: 2
// }
// what I'm after is an array like this:
// [
// { 'Category 1': 2 },
// { 'Category 2': 1 },
// { 'Category 3': 2 }
// ]
Thanks in advance, again.
Something like this?
const newArr = categories.map(category => {
const count = things.filter(thing => thing.categoryId === category.id).length;
return { [category.title]: count }
});
console.log(newArr);
https://jsfiddle.net/f3x6m12j/
You could take a Map for the relation of id and title.
const
duplicatesCountWithTitle = (things, categories) => things.reduce((hash, { categoryId }) => {
hash[categories.get(categoryId)] = (hash[categories.get(categoryId)] || 0) + 1
return hash;
}, {}),
things = [{ id: 1, title: 'Something', categoryId: 1 }, { id: 2, title: 'Another thing', categoryId: 1 }, { id: 3, title: 'Yet another thing', categoryId: 2 }, { id: 4, title: 'One more thing', categoryId: 4 }, { id: 5, title: 'Last thing', categoryId: 4 }],
categories = [{ id: 1, title: 'Category 1' }, { id: 2, title: 'Category 2' }, { id: 4, title: 'Category 3' }],
result = duplicatesCountWithTitle(
things,
categories.reduce((m, { id, title }) => m.set(id, title), new Map)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

format data using .map and .filter

i got a following type of result from the data base when i fetch database. i have tried many thing and serch google but could't found anything. please help me with this. thank you.
{ metaData:
[ { name: 'ID' },
{ name: 'NAME' },
{ name: 'LED_ID' },
{ name: 'LED_ORG_ID' },
{ name: 'COMPANY_ADD' },
{ name: 'STATE_CODE' },
{ name: 'CIN_NO' } ],
rows:
[ [ 1,
'company name',
2481,
'161',
'address ',
'27',
'number' ],
[ 2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2' ]
}
}
I am trying to achieve below formatted data
{
data:[
{
ID:1,
NAME:'company name',
LED_ID:2481,
LED_ORG_ID: '161',
COMPANY_ADD:'address',
STATE_CODE:'27',
CIN_NO:'number'
},
{
ID:2,
NAME:'company name 2',
LED_ID:2581,
LED_ORG_ID: '164',
COMPANY_ADD:'address 2',
STATE_CODE:'27',
CIN_NO:'number 2'
}
]
}
You could get the keys first and then map the object from the entries.
var data = { metaData: [{ name: 'ID' }, { name: 'NAME' }, { name: 'LED_ID' }, { name: 'LED_ORG_ID' }, { name: 'COMPANY_ADD' }, { name: 'STATE_CODE' }, { name: 'CIN_NO' }], rows: [[1, 'company name', 2481, '161', 'address ', '27', 'number'], [2, 'company name2', 2581, '164', 'address 2', '27', 'number2']] },
keys = data.metaData.map(({ name }) => name),
result = { data: data.rows.map(a => Object.fromEntries(keys.map((k, i) => [k, a[i]]))) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use map in conjunction with flatMap and reduce:
const metaData = [{name:'ID'},{name:'NAME'},{name:'LED_ID'},{name:'LED_ORG_ID'},{name:'COMPANY_ADD'},{name:'STATE_CODE'},{name:'CIN_NO'}];
const rows = [[1,'company name',2481,'161','address ','27','number'],[2,'company name2',2581,'164','address 2','27','number2']];
const res = rows.flatMap(e => e.map((f, i) => ({ [metaData[i].name]: f })).reduce((a, c) => ({ ...a, ...c }), {}));
console.log(res);
More performant solution thanks to Mark Meyer:
const res = rows.map(e => e.reduce((a, c, i) => ({ ...a, ...{ [metaData[i].name]: c }}), {});
You can use array.map() and Object.fromEntires():
let data = { metaData:
[ { name: 'ID' },
{ name: 'NAME' },
{ name: 'LED_ID' },
{ name: 'LED_ORG_ID' },
{ name: 'COMPANY_ADD' },
{ name: 'STATE_CODE' },
{ name: 'CIN_NO' } ],
rows:
[ [ 1,
'company name',
2481,
'161',
'address ',
'27',
'number' ],
[ 2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2' ]
]
}
let result = data.rows.map(
entry => Object.fromEntries(
entry.map((x, i) => [data.metaData[i].name, x])
)
)
console.log(result)
EDIT: The outer map transforms rows so there will be two objects returned. The inner one transforms all the values into format like ["ID", 1]. That array of arrays is passed as an argument into Object.fromEntries which creates a new object based on those pairs.
let data = {
metaData: [{
name: 'ID'
},
{
name: 'NAME'
},
{
name: 'LED_ID'
},
{
name: 'LED_ORG_ID'
},
{
name: 'COMPANY_ADD'
},
{
name: 'STATE_CODE'
},
{
name: 'CIN_NO'
}
],
rows: [
[1,
'company name',
2481,
'161',
'address ',
'27',
'number'
],
[2,
'company name2',
2581,
'164',
'address 2',
'27',
'number2'
]
]
}
let transform = (meta, item) => {
return meta.map((a, i) => ({
[a.name]: item[i]
}))
}
let result = data.rows.map(i => transform(data.metaData, i))
console.log(result.map(i => Object.assign({}, ...i)))
It can be better...

Compare two arrays and connect it

I have two arrays as types and defaultTypes. I need to display types with default values array defaultTypes.
const types = [
{
Id: 2,
Name: 'Some value here'
},
{
Id: 3,
Name: 'Some value here'
},
{
Id: 4,
Name: 'Some value here'
}
];
const defaultTypes = [
{
Id: 1,
Name: 'Default value 1'
},
{
Id: 2,
Name: 'Default value 2'
}
]
If in types does not exist some of default types (in this case Id: 1 does not exist in the types array). I need to add that object in types array.
Expected result will be:
const expectedTypes = [
{
Id: 1,
Name: '-'
},
{
Id: 2,
Name: 'Some value here'
},
{
Id: 3,
Name: 'Some value here'
},
{
Id: 4,
Name: 'Some value here'
}
];
Objects with Ids 1 and 2 always need to be in expectedTypes.
const expectedTypes = types.concat(
defaultTypes.filter(
type => !types.some(t => t.Id == type.Id)
));
explanation: basically what you want is types + stuff in defaultTypes that are not in types already.
Try this one:
let types = [{
Id: 2,
Name: 'Some value here'
},
{
Id: 3,
Name: 'Some value here'
},
{
Id: 4,
Name: 'Some value here'
}
];
const defaultTypes = [{
Id: 1,
Name: 'Default value 1'
},
{
Id: 2,
Name: 'Default value 2'
}
];
defaultTypes.forEach(dt => {
if (!types.some(t => t.Id === dt.Id)) {
types.push(dt);
}
});
types = types.sort((a, b) => a.Id - b.Id);
console.log(types);
Use this code and try
var _ = require('lodash');
defaultTypes.forEach(type => {
if (!_.find(types, { Id: type.Id })) {
types.push({ Id: type.Id, Name: '-' });
}
});
You can first use create a Set using map() which will contain its of elements in types.
Then loop through the defaultTypes and check if the Set contain the Id. If doesn't then push() it to types
At the end use sort() to order the elements by Id
const types = [ { Id: 2, Name: 'Some value here' }, { Id: 3, Name: 'Some value here' }, { Id: 4, Name: 'Some value here' } ];
const defaultTypes = [ { Id: 1, Name: 'Default value 1' }, { Id: 2, Name: 'Default value 2' } ]
let ids = new Set(types.map(x => x.Id));
defaultTypes.forEach(x => {
if(!ids.has(x.Id)) types.push(x)
})
types.sort((a,b) => a.Id - b.Id)
console.log(types)
Purpose Of Set
The purpose of Set is to make the time complexity liner O(n). If you don't use Set you will need to use some() on the types in each loop. So the time-complexity would be O(m*n)
Set.prototype.has has time complexity O(1)
You could reduce the elements in the wanted order with a Map and get all values as result.
const
types = [{ Id: 2, Name: 'Some value here' }, { Id: 3, Name: 'Some value here' }, { Id: 4, Name: 'Some value here' }],
defaultTypes = [{ Id: 1, Name: 'Default value 1' }, { Id: 2, Name: 'Default value 2' }],
result = Array.from([...defaultTypes, ...types]
.reduce((m, o) => m.set(o.Id, Object.assign({}, m.get(o.Id), o)), new Map)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Lodash - How to create a tree from a flat array

Every time my application loads, I receive the following json:
[
{
id: 'mALRRY93jASr',
identifier: '100',
text: 'Text A'
},
{
id: '7S3xHZEdNcfV',
identifier: '200',
text: 'Text B'
},
{
id: '2ZA5xSJeukU6',
identifier: '300',
text: 'Text C',
},
{
id: 'bhg3GnLEvw2k',
identifier: '300.100',
text: 'Text C - A'
},
{
id: 'bhg3GnLEvw2k',
identifier: '300.100.100',
text: 'Text C - A - A'
},
{
id: '2AcXNr4HT388',
identifier: '300.200',
text: 'Text C - B'
}
]
The tree levels are identified by the identifier property.
The tree can have thousands of children, so it needs to be recursive.
How can I arrange the json using Lodash to looks like the following json?
[
{
id: 'mALRRY93jASr',
identifier: '100',
text: 'Text A'
},
{
id: '7S3xHZEdNcfV',
identifier: '200',
text: 'Text B'
},
{
id: '2ZA5xSJeukU6',
identifier: '300',
text: 'Text C',
children: [
{
id: 'bhg3GnLEvw2k',
identifier: '300.100',
text: 'Text C - A',
children: [
{
id: 'bhg3GnLEvw2k',
identifier: '300.100.100',
text: 'Text C - A - A'
}
]
},
{
id: '2AcXNr4HT388',
identifier: '300.200',
text: 'Text C - B'
}
]
}
]
You could take an iterative approach by looking for objects in the same path of identifier and build a nested structure.
This approach works for unsorted data as well.
var data = [{ id: 'mALRRY93jASr', identifier: '100', text: 'Text A' }, { id: '7S3xHZEdNcfV', identifier: '200', text: 'Text B' }, { id: '2ZA5xSJeukU6', identifier: '300', text: 'Text C' }, { id: 'bhg3GnLEvw2k', identifier: '300.100', text: 'Text C - A' }, { id: 'bhg3GnLEvw2k', identifier: '300.100.100', text: 'Text C - A - A' }, { id: '2AcXNr4HT388', identifier: '300.200', text: 'Text C - B' }],
tree = [];
data.reduce((r, o) => {
o.identifier
.split('.')
.map((_, i, a) => a.slice(0, i + 1).join('.'))
.reduce((q, identifier, i, { length }) => {
var temp = (q.children = q.children || []).find(p => p.identifier === identifier);
if (!temp) {
q.children.push(temp = { identifier });
}
if (i + 1 === length) {
Object.assign(temp, o);
}
return temp;
}, r);
return r;
}, { children: tree });
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I used a previous answer of mine as a base. There are a lot of similarities, but your "path" syntax is a little different and I had to tweak some of the parsing.
const data = [
{
id: 'mALRRY93jASr',
identifier: '100',
text: 'Text A'
},
{
id: '7S3xHZEdNcfV',
identifier: '200',
text: 'Text B'
},
{
id: '2ZA5xSJeukU6',
identifier: '300',
text: 'Text C',
},
{
id: 'bhg3GnLEvw2k',
identifier: '300.100',
text: 'Text C - A'
},
{
id: 'bhg3GnLEvw2k',
identifier: '300.100.100',
text: 'Text C - A - A'
},
{
id: '2AcXNr4HT388',
identifier: '300.200',
text: 'Text C - B'
}
];
const pathPartRegex = /.*?\./g;
const tree = _.reduce(data, (result, value) => {
// We use the . character as a "path part" terminator,
// but it is not there at the end of the string, so we add it
let identifier = value.identifier;
if (!identifier.endsWith(".")) {
identifier = identifier + ".";
}
const pathParts = identifier.match(pathPartRegex);
let node = result;
let path = "";
// Go down through tree until last path part
const notLastPart = pathParts.splice(0, pathParts.length - 1);
for (const pathPart of notLastPart) {
path += pathPart;
const existingNode = node.children
? node.children.find(item => path.startsWith(item.identifier) )
: node.find(item => path.startsWith(item.identifier));
if (existingNode) {
node = existingNode
} else {
// If we need to traverse over a path that doesn't exist, just create it
// See notes
const newNode = {
identifier: path,
children: []
};
// The root element is just an array, and doesn't have a children property
if (node.children) {
node.children.push(newNode);
} else {
node.push(newNode);
}
node = newNode;
}
}
// Add new node
const newNode = {
id: value.id,
text: value.text,
identifier: value.identifier,
children: []
};
// The root element is just an array, and doesn't have a children property
if (node.children) {
node.children.push(newNode);
} else {
node.push(newNode);
}
return result;
}, []);
Tested via RunKit (https://npm.runkit.com/lodash)
Notes:
The same warnings from the original answer also apply here.
You can use Array.reduce() and _.setWith() to create a tree of objects by the path (identity). Then you can use a recursive function with _.transform() to convert the children to an array using _.values():
const createTree = (arr) => {
// reduce to a tree of objects
const oTree = arr.reduce((r, o) => {
const key = o.identifier.replace(/\./g, '.children.');
// creates the path and adds the object value
return _.setWith(r, key, o, Object)
}, {});
// transforms the children to an array recursivly
const transformChildren = (tree) =>
_.transform(tree, (acc, v, k) => {
const value = _.isObject(v) ? transformChildren(v) : v;
acc[k] = _.eq(k, 'children') ? _.values(value) : value;
});
return transformChildren(_.values(oTree));
};
const data = [{"id":"mALRRY93jASr","identifier":"100","text":"Text A"},{"id":"7S3xHZEdNcfV","identifier":"200","text":"Text B"},{"id":"2ZA5xSJeukU6","identifier":"300","text":"Text C"},{"id":"bhg3GnLEvw2k","identifier":"300.100","text":"Text C - A"},{"id":"bhg3GnLEvw2k","identifier":"300.100.100","text":"Text C - A - A"},{"id":"2AcXNr4HT388","identifier":"300.200","text":"Text C - B"}];
const result = createTree(data);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

Categories