Javascript: array of objects, group/restructure objects - javascript

The following object contains a list of recipients and subscriptions, I wish to create a new array with a different structure such as below.
[{"recipientId":"13251376",
"services":"3218143,15656200,3721"},{"recipientId":"13251316",
"services":"3218143"}
let serviceSubscriptions = [{"recipientId":"13251316","serviceId":"3218143"},{"recipientId":"13251376","serviceId":"3218143"},{"recipientId":"13251376","serviceId":"15656200"},{"recipientId":"13251376","serviceId":"3721"}]
let testArr = [];
serviceSubscriptions.forEach(serviceSubscriptions => {
for (const [key, value] of Object.entries(serviceSubscriptions)) {
//console.log(`${key}: ${value}`);
testArr.push(`${key}:${value}`);
}
});
console.log(testArr);
Here is a list of things I've tried - https://jsfiddle.net/v5azdysg/2/
update 30/11/22 19:08
I am trying to integrate #Mr. Polywhirl your answer with my idea, but I cannot get far as I am not skilled in this area, here is what I have so far. https://jsfiddle.net/aectk8v1/4/ What I need is to add a new key called essentially services/subscriptions with the list of ids of the subscriptions, but this should be appended to the existing list of keys and also a key which shows if subscriptions exist, true or false, on the other hand another version I need is to list all the services keys and add the value of true or false under a single key such as "subscriptions":{12345:true,123456:false}
;let recipients = [
{"id":"666777","lawfulBasis":"0","jurisdiction":"AMER","name":"David G"},
{"id":"888999","lawfulBasis":"1","jurisdiction":"ASIA","name":"Mike A"},
{"id":"444555","lawfulBasis":"2","jurisdiction":"EUR","name":"John No Sub"}
];
let serviceSubscriptions = [
{"recipientId":"666777","serviceId":"3218143"},
{"recipientId":"666777","serviceId":"8956799"},
{"recipientId":"888999","serviceId":"15656200"},
{"recipientId":"000000","serviceId":"3721"}
];
/* return subscribed */
//.map method creates new array populated with result of call
//.some performs test true|false
// ... dot notation copies all parts from 1 array to another merge/join
var result = recipients.map(Obj1 => {
return { ...Obj1,
isSubscribed:serviceSubscriptions.some(Obj2 => Obj1.id == Obj2.recipientId),
services1:serviceSubscriptions.map(Obj2 => Obj1.id == Obj2.recipientId),
services:serviceSubscriptions.map(Obj2 => Obj2.serviceId),
}
});
console.log(result)

You can reduce each item into a Map<String, Set<String>>. After you have the map, you can map the entries to objects where you join the serviceId arrays.
const serviceSubscriptions = [
{ "recipientId": "13251316", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "15656200" },
{ "recipientId": "13251376", "serviceId": "3721" }
];
const testArr = [
...serviceSubscriptions
.reduce((acc, { recipientId, serviceId }) =>
acc.set(recipientId,
(acc.get(recipientId) ?? new Set).add(serviceId)), new Map)
.entries()
]
.map(([recipientId, serviceIds]) => ({
recipientId,
services: [...serviceIds].join(',')
}));
console.log(testArr);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here is an alternative version that uses Object and Array types instead of Map and Set types.
Note: This performs worse than the version above, because it uses spreading which mutates the accumulator and service array values. This should be avoided.
const serviceSubscriptions = [
{ "recipientId": "13251316", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "15656200" },
{ "recipientId": "13251376", "serviceId": "3721" }
];
const testArr = Object
.entries(serviceSubscriptions
.reduce((acc, { recipientId, serviceId }) => ({
...acc,
[recipientId]: [...(acc[recipientId] ?? []), serviceId]
}), {}))
.map(([recipientId, serviceIds]) => ({
recipientId,
services: serviceIds.join(',')
}));
console.log(testArr);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Efficiency
Here is the most efficient way possible. This algorithm is O(n) instead of O(2n) as seen in the preceding approaches.
const serviceSubscriptions = [
{ "recipientId": "13251316", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "15656200" },
{ "recipientId": "13251376", "serviceId": "3721" }
];
const testArr = [], idLookup = new Map();
for (let i = 0; i < serviceSubscriptions.length; i++) {
const sub = serviceSubscriptions[i];
if (!idLookup.has(sub.recipientId)) {
idLookup.set(sub.recipientId, testArr.length);
testArr.push({
recipientId: sub.recipientId,
services: sub.serviceId
});
} else {
const index = idLookup.get(sub.recipientId);
testArr[index].services += `,${sub.serviceId}`
}
}
console.log(testArr);
.as-console-wrapper { top: 0; max-height: 100% !important; }
And here is a function version, of the code above:
const serviceSubscriptions = [
{ "recipientId": "13251316", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "3218143" },
{ "recipientId": "13251376", "serviceId": "15656200" },
{ "recipientId": "13251376", "serviceId": "3721" }
];
const reducer = (arr, keyFn, valueFn, addFn, reducerFn) => {
const result = [], idLookup = new Map();
for (let i = 0; i < arr.length; i++) {
const item = arr[i], key = keyFn(item), val = valueFn(item);
if (!idLookup.has(key)) {
idLookup.set(key, result.length);
result.push(addFn(key, val));
} else {
const index = idLookup.get(key);
reducerFn(result[index], val);
}
}
return result;
};
const testArr = reducer(
serviceSubscriptions,
({ recipientId }) => recipientId,
({ serviceId }) => serviceId,
(key, val) => ({ recipientId: key, services: val }),
(existing, curr) => existing.services += `,${curr}`
);
console.log(testArr);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Final update
This should work for your updated data:
const recipients = [
{ "id": "666777", "lawfulBasis": "0", "jurisdiction": "AMER", "name": "David G" },
{ "id": "888999", "lawfulBasis": "1", "jurisdiction": "ASIA", "name": "Mike A" },
{ "id": "444555", "lawfulBasis": "2", "jurisdiction": "EUR", "name": "John No Sub" }
];
const subscriptions = [
{ "recipientId": "666777", "serviceId": "3218143" },
{ "recipientId": "666777", "serviceId": "8956799" },
{ "recipientId": "888999", "serviceId": "15656200" },
{ "recipientId": "000000", "serviceId": "3721" }
];
const serviceIds = subscriptions.map(({ serviceId }) => serviceId);
const result = recipients.map(({ id, ...rest }) => ({
id,
isSubscribed: subscriptions.some(({ recipientId }) => recipientId === id),
...rest,
services: [...serviceIds],
services1: subscriptions.map(({ recipientId }) => recipientId === id)
}));
console.log(result);
.as-console-wrapper { top: 0; max-height: 100% !important; }

There are many ways to solve this, Mr. Polywhirl's answer is more elegant than the following (I haven't done the O() calc on it to determine efficiency vs this one), but a basic solution just needs to use a couple for loops.
One to get the elements and another to iterate over the array again starting from the next index to add the service ids. I just have an extra object to use as a map to check if we already handled the recipientId.
let serviceSubscriptions = [{"recipientId":"13251316","serviceId":"3218143"},{"recipientId":"13251376","serviceId":"3218143"},{"recipientId":"13251376","serviceId":"15656200"},{"recipientId":"13251376","serviceId":"3721"}]
let testArr = [];
const addedRecipients = {};
for(let i = 0; i < serviceSubscriptions.length; i++) {
// Create a new object so we don't alter the original.
const recipSub = Object.assign({}, serviceSubscriptions[i]);
// `undefined` is false in JS so we can check a map if we already handled it.
if (addedRecipients[recipSub.recipientId]) continue;
addedRecipients[recipSub.recipientId] = true;
for (let y = i + 1; y < serviceSubscriptions.length; y++) {
if (serviceSubscriptions[y].recipientId === recipSub.recipientId) {
recipSub.serviceId = recipSub.serviceId + ',' + serviceSubscriptions[y].serviceId;
}
}
testArr.push(recipSub);
}
console.log(testArr);
console.log(serviceSubscriptions);

Here's a simple and efficient version:
let result = Object.create(null)
for (let s of serviceSubscriptions) {
result[s.recipientId] = result[s.recipientId] ?? []
result[s.recipientId].push(s.serviceId)
}
for (let r in result)
result[r] = result[r].join()
Not sure why you'd want these arrays joined though.

I've worked on this script but is not 100% correct, I'd like the services ids user is subscribed to under a list also. such as the key example servicesExample2 . I've been trying to integrate the code from the answers but my lack of coding is blocking me Mr. Polywhirl, Stephen Gilboy, gog
;let recipients = [
{"id":"666777","lawfulBasis":"0","jurisdiction":"AMER","name":"David G"},
{"id":"888999","lawfulBasis":"1","jurisdiction":"ASIA","name":"Mike A"},
{"id":"444555","lawfulBasis":"2","jurisdiction":"EUR","name":"John No Sub"}
];
let serviceSubscriptions = [
{"recipientId":"666777","serviceId":"3218143"},
{"recipientId":"666777","serviceId":"8956799"},
{"recipientId":"888999","serviceId":"15656200"},
{"recipientId":"000000","serviceId":"3721"}
];
const recipientObj = recipients.map(({id,isSubscribed,...recipient}) => {
//if (isSubscribed === true) {
return {
id,
isSubscribed: serviceSubscriptions.some(( subs ) => subs.recipientId ===id),
test:'test',
services: serviceSubscriptions.map(({recipientId,serviceId}) => serviceId+':'+serviceSubscriptions.some(({serviceId}) => recipientId === id)),
servicesExample2: '3218143,3218143',
...recipient,
}
//}
});
console.log(recipientObj)
I think also my structure for key services is wrong
services: ["3218143:false", "8956799:false", "15656200:true", "3721:false"],
How can it make it the correc structure? like below
services: [{"3218143:false"}, {"8956799:false"}, {"15656200:true"},{"3721:false"}],
UPDATE, FINAL VERSION
const result = recipients.map(recipient => {
// Create an array of the recipient's subscription IDs
const subscriptionIds = serviceSubscriptions
.filter(subscription => subscription.recipientId === recipient.id)
.map(subscription => subscription.serviceId);
return {
...recipient,
isSubscribed: subscriptionIds.some(i=>i),
services: subscriptionIds,
servicesMatrix: serviceSubscriptions.map(({recipientId,serviceId}) => serviceId+':'+serviceSubscriptions.some(({serviceId}) => recipientId === recipient.id)),
};
});
console.log(result);

Related

Recursively Add Elements To Array Based on Length

I'm trying to create a recursive function that will return a list of strings representing every attribute for a given schema. It must merge this with the attributes for a given document, to include elements for each time an array element occurs in the document.
For example. If you run the following code, I expect the result to include friends.1.addresses.1.country. However it does not. I think this is due to the fact that it's not recursively including all the array element possibilities. Since only 0 is set for the other array possibilities in the parentKey & postKey variables.
Any ideas how to fix this?
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
console.log(main()); // Should print `friends.1.addresses.1.country` as one of the elements in the array but does not.
function main() {
const result = schemaAttributes.reduce((accumulator, currentValue) => {
accumulator.push(currentValue);
const attributeParts = currentValue.split(".");
attributeParts.forEach((a, index) => {
const isLastPartNumber = !isNaN(parseInt(a));
if (isLastPartNumber) {
const parentKey = attributeParts.slice(0, index).join(".");
const postKey = attributeParts.slice(index + 1).join(".");
const numberOfItems = get(myDocument, parentKey).length;
for (let i = 1; i < numberOfItems; i++) {
accumulator.push([parentKey, i, postKey].filter((a) => Boolean(a)).join("."));
}
}
});
return accumulator;
}, []);
return [...new Set(result)];
}
function get(object, key) {
const keyParts = key.split(".");
let returnValue = object;
keyParts.forEach((part) => {
if (returnValue) {
returnValue = returnValue[part];
}
});
return returnValue;
}
Expected Result (order does not matter):
[
"id",
"friends",
"friends.0",
"friends.1",
"friends.0.name",
"friends.1.name",
"friends.0.addresses",
"friends.1.addresses",
"friends.0.addresses.0",
"friends.1.addresses.0",
"friends.1.addresses.1",
"friends.0.addresses.0.country",
"friends.1.addresses.0.country",
"friends.1.addresses.1.country",
"friends.0.addresses.0.zip",
"friends.1.addresses.0.zip",
"friends.1.addresses.1.zip"
]
Below traversing all paths matching your schemaAttribute, assuming a zero in your schemaAttribute is a wildcard for the position in array.
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
const out = new Set()
schemaAttributes.forEach(attr => {
out.add(attr)
traverse(myDocument, attr.split('.'), 0, [], path => out.add(path.join('.')))
})
function traverse (node, attrPath, idxAttrPath, outPath, cb) {
if (idxAttrPath === attrPath.length) {
return cb(outPath)
}
if (!node) { // can not explore further
return
}
const attr = attrPath[idxAttrPath]
if (attr === '0') {
if (!Array.isArray(node)) { // can not explore further
return
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], attrPath, idxAttrPath + 1, outPath, cb)
outPath.pop()
})
} else {
outPath.push(attr)
traverse(node[attr], attrPath, idxAttrPath + 1, outPath, cb)
outPath.pop()
}
}
console.log('out', [...out].sort((a, b) => a.localeCompare(b)))
An alternative (in the spirit more efficient) would be to consider a trie so we don't explore every schemaAttribute from the start. A 'nice' property being that the fields are printed in order and we don't have to sort as done in the first approach (although it does not matter to you)
Note that the traverse function is almost identical
Note2: Notice the cb(outPath) done for every traversal, not only for leaves.
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
function f2 (schemaAttributes, doc) {
// build a tree out of schema attributes
const root = {}
schemaAttributes.forEach(sa => {
node = root
sa.split('.').forEach(attr => {
node[attr] = node[attr] || {}
node = node[attr]
})
})
// explore the tree
function traverse (node, treeNode, outPath, cb) {
cb(outPath)
if (Object.keys(treeNode).length === 0) { // a leaf
return // cb(outPath)
}
if (!node) {
return
}
Object.keys(treeNode).forEach(attr => {
if (attr === '0') {
if (!Array.isArray(node)) { // can not explore further
return
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], treeNode[attr], outPath, cb)
outPath.pop()
})
} else {
outPath.push(attr)
traverse(node[attr], treeNode[attr], outPath, cb)
outPath.pop()
}
})
}
const out = []
traverse(doc, root, [], p => out.push(p.join('.')))
return out.slice(1) // discard the empty string
}
console.log(f2(schemaAttributes, myDocument))
Regarding the presence of friends.2.addresses.0.zip the underlying idea is that the path to the leaf should be present even if path in the document is at some point undefined.
So the adaptation is to fake the path on the document so we can continue traversing it up until the tree leaf is reached
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip',
'bob.moran.everywhere' // for properties as well
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
},
{
"name": "Pumba",
"addresses": [] // empty addresses! should show ..friends.2.adresses.0....
}
]
};
function f3 (schemaAttributes, doc) {
// build a tree out of schema attributes
const root = {}
schemaAttributes.forEach(sa => {
node = root
sa.split('.').forEach(attr => {
node[attr] = node[attr] || {}
node = node[attr]
})
})
// explore the tree
function traverse (node, treeNode, outPath, cb, virtualPath) {
cb(outPath)
if (Object.keys(treeNode).length === 0) { // a leaf
return //cb(outPath)
}
Object.keys(treeNode).forEach(attr => {
if (attr === '0') {
if (!node || node.length == 0) {
node = [{}] // fake the path for arrays
}
node.forEach((n, i) => {
outPath.push(i)
traverse(node[i], treeNode[attr], outPath, cb)
outPath.pop()
})
} else {
if (!node) { // fake the path for properties
node = {}
}
outPath.push(attr)
traverse(node[attr], treeNode[attr], outPath, cb)
outPath.pop()
}
})
}
const out = []
traverse(doc, root, [], p => out.push(p.join('.')))
return out.slice(1)
}
console.log(f3(schemaAttributes, myDocument))
You could use try out this piece of code:
const schemaAttributes = [
'id',
'friends',
'friends.0',
'friends.0.name',
'friends.0.addresses',
'friends.0.addresses.0',
'friends.0.addresses.0.country',
'friends.0.addresses.0.zip'
];
const myDocument = {
"id": 1,
"friends": [
{
"name": "Bob",
"addresses": [
{"country": "world"}
]
},
{
"name": "Tim",
"addresses": [
{"country": "moon"},
{"zip": 12345}
]
}
]
};
main(); // Should print `friends.1.addresses.1.country` as one of the elements in the array but does not.
function extractKeysRecursively(obj) {
const attributes = Object.keys(obj)
return attributes
flatMap(key =>
typeof obj[key] === 'object'
? [isNaN(key) && key, ...extractKeysRecursively(obj[key], ( ( parentKey && parentKey + '.' ) || '' ) + key )]
: ( ( parentKey && parentKey + '.' ) || '' ) + key
)
.filter(key => !!key)
}
function main() {
console.log(schemaAttributes.concat(extractKeysRecursively(myDocument)))
}
Lemme know you need some extra help (:

Relate and merge array of same Department

I am working on an application where I need to get combine the object of same department based on the
conditions provided in the second Array and attach the relation to the object.
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"}
]
Output
outArr ={
[{"ID":"1","NAME":"KEN","DEPT1":"CSE","REL":"AND"},
{"ID":"2","NAME":"MARK","DEPT2":"IT","REL":"AND"}], //Arr1
[{"ID":"3","NAME":"TOM","DEPT3":"ECE","REL":"OR"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB","REL":"OR"}], //Arr2
[{"ID":"5","NAME":"TIM","DEPT5":"SEC"}] //Arr3
}
Code:
let condArr=[],outArr,i=1;
inArr1.forEach(condt => {
let dept = Object.keys(condt)[0];
let tmparr = dept.split("D");
tmparr.shift()
condArr.push(tmparr)
});
inArr2.forEach(condt => {
if(condArr.includes(inArr2.D+i)){
i++;
outArr.push(inArr2);
}
});
Your code has a bit confused logic, i would suggest rather this
let inArr1 = [{"D1D2":"AND"},{"D3D4":"OR"},{"D5D6":"AND"}]
let inArr2 =[{"ID":"1","NAME":"KEN","DEPT1":"CSE"},
{"ID":"2","NAME":"MARK","DEPT2":"IT"},
{"ID":"3","NAME":"TOM","DEPT3":"ECE"},
{"ID":"4","NAME":"SHIV","DEPT4":"LIB"},
{"ID":"5","NAME":"TIM","DEPT5":"SEC"},
{"ID":"6","NAME":"TLA","DEPT6":"SEC"},
]
// first lets create object of ids as keys and conditions as values
const [keys, conditions] = inArr1.reduce((agg, cond, index) => {
Object.entries(cond).forEach(([key, value]) => {
key.split('D').forEach(v => { if (v) agg[0][v] = { value, index }})
agg[1].push([])
})
return agg
}, [{}, []]) // {1: "AND", 2: "AND", 3: "OR", 4: "OR"}
conditions.push([])
// and now just map over all elements and add condition if we found id from the keys
inArr2.forEach(item => {
const cond = keys[item.ID]
if (cond) conditions[cond.index].push({...item, REL: cond.value})
else conditions[conditions.length - 1].push(item)
})
const res = conditions.filter(v => v.length)
console.log(res)
You could store the goups by using the ID and use new objects.
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }],
inArr2 = [{ ID: "1", NAME: "KEN", DEPT1: "CSE" }, { ID: "2", NAME: "MARK", DEPT2: "IT" }, { ID: "3", NAME: "TOM", DEPT3: "ECE" }, { ID: "4", NAME: "SHIV", DEPT4: "LIB" }, { ID: "5", NAME: "TIM", DEPT5: "SEC" }],
groups = inArr1.reduce((r, o) => {
Object.entries(o).forEach(([k, REL]) => {
var object = { REL, group: [] };
k.match(/[^D]+/g).forEach(id => r[id] = object);
});
return r;
}, {}),
grouped = inArr2.reduce((r, o) => {
var { REL, group } = groups[o.ID] || {};
if (group) {
if (!group.length) r.push(group);
group.push(Object.assign({}, o, { REL }));
} else {
r.push([o]);
}
return r;
}, []);
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
can try other solution:
let inArr1 = [{ D1D2: "AND" }, { D3D4: "OR" }, { D6D7: "XOR" }];
let inArr2 = [
{ ID: "1", NAME: "KEN", DEPT1: "CSE" },
{ ID: "2", NAME: "MARK", DEPT2: "IT" },
{ ID: "3", NAME: "TOM", DEPT3: "ECE" },
{ ID: "4", NAME: "SHIV", DEPT4: "LIB" },
{ ID: "5", NAME: "TIM", DEPT5: "SEC" },
{ ID: "9", NAME: "BAR", DEPT5: "XYZ" },
{ ID: "6", NAME: "FOO", DEPT5: "XYZ" },
];
let unmatchedArr = []
let matchedArr = inArr2.reduce((acc, obj) => {
// getting index matched from inArr1 objects key
const indexMatched = getIndexMatch(obj.ID);
// creating index if not exists
if (!acc[indexMatched] && indexMatched !== null) acc[indexMatched] = [];
// if some index matched it merge current obj with DEL property with inArr1[indexMatched] key => value
return indexMatched !== null
? acc[indexMatched].push({
...obj,
DEL: inArr1[indexMatched][Object.keys(inArr1[indexMatched])[0]]
})
// pushing on unmatchedArr
: unmatchedArr.push(obj)
, acc
}, []);
function getIndexMatch(id) {
for (const [index, obj] of inArr1.entries()) {
for (const key of Object.keys(obj)) {
// spliting only digits of the current key of object
if (key.match(/\d/g).includes(id)) return index; // returning index of inArr1 if is included
}
}
return null;
}
// merging arrays
const result = [...matchedArr, unmatchedArr];
console.log(result);

Transform a nested object using lodash

Input:
const a = {
"8": [{
"strategy": 123,
"id": 1,
"config": {
"global_dag_conf": {
"algo_v2_conf": {
"features_to_combine": [],
"segments": [],
"force_performance": false,
"min_bid": 0,
"max_bid": 13
}
}
}
}],
"13": [{
"strategy": 456,
"id": 2,
"config": {
"global_dag_conf": {
"algo_v2_conf": {
"ivr_measured": []
}
}
}
}]
}
Output:
{
"8": [
{
"global_dag_conf": {
"algo_v2_conf": {
"features_to_combine": [],
"segments": [],
"force_performance": false,
"min_bid": 0,
"max_bid": 13
}
},
"algo_id": 1
}
],
"13": [
{
"global_dag_conf": {
"algo_v2_conf": {
"ivr_measured": []
}
},
"algo_id": 2
}
]
}
I tried below solution which works fine but need to know if is there any better way to do this using lodash and JS.
result = _.map(_.keys(addtionalAlgos), (algoType) => {
addtionalAlgos[algoType] = _.map(addtionalAlgos[algoType], v => _.assign(v.config, { algo_id: v.id }));
return addtionalAlgos;
})[0]
Here's a solution without using lodash:
Use Object.entries() to get an array of key-value pairs
Create a new object by using reduce over the array
Use map to create a new array of objects.
Destructure each object to get id and config. Spread the config variable to remove one level of nesting
const input = {"8":[{"strategy":123,"id":1,"config":{"global_dag_conf":{"algo_v2_conf":{"features_to_combine":[],"segments":[],"force_performance":false,"min_bid":0,"max_bid":13}}}}],"13":[{"strategy":456,"id":2,"config":{"global_dag_conf":{"algo_v2_conf":{"ivr_measured":[]}}}}]}
const output =
Object.entries(input)
.reduce((r, [key, value]) => {
r[key] = value.map(({ id, config }) => ({ algo_id: id, ...config }));
return r;
}, {})
console.log(output)
Use _.mapValues() to iterate the keys, and Array.map() with object destructuring and spread syntax to reformat the object:
const data = {"8":[{"strategy":123,"id":1,"config":{"global_dag_conf":{"algo_v2_conf":{"features_to_combine":[],"segments":[],"force_performance":false,"min_bid":0,"max_bid":13}}}}],"13":[{"strategy":456,"id":2,"config":{"global_dag_conf":{"algo_v2_conf":{"ivr_measured":[]}}}}]}
const result = _.mapValues(data,
arr => arr.map(({ id: algo_id, config }) =>
({ algo_id, ...config })
))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
A pure Lodash solution using mapValues, map and assign methods
let data = {"8":[{"strategy":123,"id":1,"config":{"global_dag_conf":{"algo_v2_conf":{"features_to_combine":[],"segments":[],"force_performance":false,"min_bid":0,"max_bid":13}}}}],"13":[{"strategy":456,"id":2,"config":{"global_dag_conf":{"algo_v2_conf":{"ivr_measured":[]}}}}]};
let res = _.mapValues(data, arr => _.map(arr, obj => _.assign({
'algo_id': obj.id,
'global_dag_conf': obj.config.global_dag_conf
})));
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
An alternative without lodash.
The function reduce allows to generate an object which will be filled using the function map which transforms the original objects to the desired structure.
const a = { "8": [{ "strategy": 123, "id": 1, "config": { "global_dag_conf": { "algo_v2_conf": { "features_to_combine": [], "segments": [], "force_performance": false, "min_bid": 0, "max_bid": 13 } } } }], "13": [{ "strategy": 456, "id": 2, "config": { "global_dag_conf": { "algo_v2_conf": { "ivr_measured": [] } } } }] };
let result = Object.entries(a).reduce((a, [key, arr]) => {
return Object.assign(a, {[key]: arr.map(({id: algo_id, config}) => ({algo_id, ...config}))});
}, Object.create(null));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

count all values on object array - javascript

I need to count each value on the object array , the desired output should be like below
[{
"question": "question1",
"USA": 2
}, {
"question": "question1",
"AUS": 1
},
{
"question": "question2",
"item1": 2
},
{
"question": "question2",
"item1,item2": 1
}, {
"question": "question4",
"3": 1
}, {
"question": "question4",
"2": 1
}
]
Below is the input I need to transform in to the above output. I have no clue how to do with n no of question and also got issue when one question has 2 answers . sample input
[{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
let arr=[{"question1":"USA","question2":["item1"],"question4":2},{"question1":"USA","question2":["item1"],"question4":3},{"question1":"AUS","question2":["item1","item2"]}];
//console.log(arr);
function solve(list){
var map = new Map();
var entry = null;
for(var item of list){
if(!map.has(item.question1))
map.set(item.question1, {question:'question1'});
entry = map.get(item.question1);
if(entry.hasOwnProperty(item.question1))
entry[item.question1] = entry[item.question1] + 1;
else
entry[item.question1] = 1;
if(!map.has(item.question2))
map.set(item.question2, {question: 'question2'});
entry = map.get(item.question2);
if(entry.hasOwnProperty(item.question2))
entry[item.question2] = entry[item.question2] + 1;
else
entry[item.question2] = 1;
}
return Array.from(map.values());
}
console.log(solve(arr))
You could take an object or what ever data structure you like which supports a key/value structure in a nested style and collect first all items and then reder the collected tree.
This approach uses objects, because the keys are strings, this is important for an array as key. This is joint with a comma which is sufficient for this use case.
var data = [{ question1: "USA", question2: ["item1"], question4: 2 }, { question1: "USA", question2: ["item1"], question4: 3 }, { question1: "AUS", question2: ["item1", "item2"] }],
hash = data.reduce((hash, o) => {
Object.entries(o).forEach(([question, value]) => {
var sub = hash[question] = hash[question] || Object.create(null);
sub[value] = sub[value] || { question, [value]: 0 };
sub[value][value]++;
});
return hash;
}, Object.create(null)),
result = Object.values(hash).reduce((r, sub) => [...r, ...Object.values(sub)], []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First, obtain the countries by using reduce. Then use some nested forEach loops for the rest:
const input = [{"question1":"USA","question2":["item1"],"question4":2},
{"question1":"USA","question2":["item1"],"question4":3},
{"question1":"AUS","question2":["item1","item2"]}];
const countriesOutput = input.reduce((acc, curr) => {
if (!acc.some(e => e[curr.question1])) {
acc.push({ question: "question1", [curr.question1]: 1 });
} else {
acc.find(e => e[curr.question1])[curr.question1]++;
}
return acc;
}, []);
let questionsOutput = [];
input.forEach(item => {
Object.keys(item).forEach(key => {
if (key != "question1") {
if (Array.isArray(item[key])) {
questionsOutput.push({ question: key, [item[key].join(",")]: 1 });
} else {
questionsOutput.push({ question: key, [item[key]]: 1 });
}
}
});
});
const finalOutput = [...countriesOutput, ...questionsOutput];
console.log(finalOutput);
.as-console-wrapper { max-height: 100% !important; top: auto; }
Its a matter of summarizing the input using a dictionary (like Object) and track the duplicates. The "name" of the name/value pair can be uniquely identified by combining the question and answer with some delimiter.
const input = [{
"question1": "USA",
"question2": ["item1"],
"question4": 2
},
{
"question1": "USA",
"question2": ["item1"],
"question4": 3
},
{
"question1": "AUS",
"question2": ["item1", "item2"]
}
];
//Sum the input to an array which we can easily search for duplciates
var repeatCounter = {};
input.forEach(objItem => {
Object.keys(objItem).forEach(propItem => {
//Get the counter and the string
var s = `${propItem}-${objItem[propItem]}`;
var c = repeatCounter[s] || 0;
//Modify it or introduce it if absent
repeatCounter[s] = c + 1;
})
})
var output = Object.keys(repeatCounter).map(element => {
var ret = {'question': element.split('-')[0]}
ret[element.split('-')[1]] = repeatCounter[element];
return ret;
})
console.log(output);
.as-console-wrapper {
max-height: 100% !important;
}
Subtle adjustments such as fortifying the delimiter, converting multiple strings in to array items(as shown in the question) needs to be done on practical grounds.

Algorithm to solve stores and products minimum total price

I have a JSON array of products with nested array of stores that supply them:
let arrayOfProducts =
[
{
"title": "ProductA",
"stores": [
{
"name": "Store1",
"price": 15.09
},
{
"name": "Store2",
"price": 16.30,
},
{
"name": "Store4",
"price": 16.55,
},
.
.
.
"title": "ProductB",
"stores": [
{
"name": "Store1",
"price": 8.06
},
{
"name": "Store3",
"price": 9.25,
},
{
"name": "Store4",
"price": 9.27,
},
.
.
.
]
I need to find the combination of the minimum number of store(s)(due to extra shipping constraint) that provide all of the products at the lowest TOTAL price.
e.g. lets say the array has five products ProductA-ProductE.There is no single store in their respective arrays that can supply all of them. Store2 supplies a subset of the products and so does any other store.
The output should be like that:
[
{
"name": "store1",
"price": total_price_for_this_store,
"products": [ "ProductC"]
},
{
"name": "store2",
"price": total_price_for_this_store,
"products": [ "ProductA", "ProductB", "ProductD"]
},
{
"name": "store3",
"price": total_price_for_this_store,
"products": [ "ProductE"]
}
]
I have managed to create the expected output using javascipt's forEach and filter functions, but only to find the solution if one or more stores have ALL the products and not a subset of them.
let results = []
arrayOfProducts.forEach((product) => {
product.stores.forEach(store => {
let storeIndex = results.findIndex(el => { return el.name === store.name })
if (storeIndex === -1) { // first occurence of the shop in the array of results
results.push({
name: store.name,
price: store.price,
products : [product.title]
})
} else {
results[storeIndex].price += store.price
results[storeIndex].products.push(product.title)
}
})
})
let allProducts = results.filter((store) => {
return store.products.length === arrayOfProducts.length
})
allProducts.sort(function (a, b) {
return parseFloat(a.price) - parseFloat(b.price)
})
How can i approach this problem?I dont know how to start.
Does it belong to the LP category of algorithms?
I've managed to come up with a solution:
const fs = require('fs')
const _ = require('lodash')
// loading the product JSON file
let product = JSON.parse(fs.readFileSync('product.json', 'utf8'))
// create a sorted array of the title products
let productTitles = product.map(el => {
return el.title
}).sort(sortAlphabetically)
let numberOfProducts = productTitles.length
let storeCombinations = []
let stores = []
// create the array of stores
product.forEach((product) => {
product.stores.forEach(store => {
let price = store.price
let productUrl = store.productUrl
let storeIndex = stores.findIndex(el => { return el.name === store.name })
if (storeIndex === -1) { // first occurence of the shop in the array of results
stores.push({
name: store.name,
products: [{
title: product.title,
price: (parseFloat(price) * product.quantity).toFixed(2)
}]
})
} else {
stores[storeIndex].products.push({
title: product.title,
price: (parseFloat(price) * product.quantity).toFixed(2),
})
}
})
})
let comboCnter = 0
// for each of the stores see if the missing product(s) can be complemented
// with any of the following stores.
// If true then merge the two store products
stores.forEach((el, index) => {
for (let i = index + 1; i < stores.length; i++) {
let currentStoreProducts = el.products.map(product => product.title).sort(sortAlphabetically)
let nextStoreProducts = stores[i].products.map(product => product.title).sort(sortAlphabetically)
let mergedArrays = _.uniq(currentStoreProducts.concat(nextStoreProducts))
if (mergedArrays.length === numberOfProducts) {
let products1 = []
let products2 = []
let store1Price = 0
let store2Price = 0
productTitles.forEach(title => {
let index1 = el.products.findIndex(x => x.title === title)
let index2 = stores[i].products.findIndex(x => x.title === title)
if (index1 !== -1 && index2 !== -1) {
if (parseFloat(el.products[index1].price) < parseFloat(stores[i].products[index2].price)) {
store1Wins()
} else {
store2Wins()
}
}
if (index2 === -1) {
store1Wins()
}
if (index1 === -1) {
store2Wins()
}
function store1Wins() {
store1Price = (parseFloat(el.products[index1].price) + parseFloat(store1Price)).toFixed(2)
products1.push({
title: el.products[index1].title,
productUrl: el.products[index1].productUrl
})
}
function store2Wins() {
store2Price = (parseFloat(stores[i].products[index2].price) + parseFloat(store2Price)).toFixed(2)
products2.push({
title: stores[i].products[index2].title,
productUrl: stores[i].products[index2].productUrl
})
}
})
storeCombinations.push({
totalPrice: (parseFloat(store1Price) + parseFloat(store2Price)).toFixed(2),
store1: {
name: el.name,
price: store1Price,
products: products1
},
store2: {
name: stores[i].name,
price: store2Price,
products: products2
}
})
comboCnter++
}
}
})
// sort the final result ascending prices
storeCombinations.sort((a, b) => {
return parseFloat(a.totalPrice) - parseFloat(b.totalPrice)
})
fs.writeFileSync('storeCombinations.json', JSON.stringify(storeCombinations))
console.log('comboCnter: ' + comboCnter)
function sortAlphabetically(a, b) {
let nameA = a.toLowerCase()
let nameB = b.toLowerCase()
if (nameA < nameB) { return -1 }// sort string ascending
if (nameA > nameB) { return 1 }
return 0 // default return value (no sorting)
}
This code finds the cheapest combination of two stores.

Categories

Resources