How to classify arrays and form new arrays - javascript

In my project, I need to classify an array and convert it to another type of array.
The difficulty I encountered was that there was no way to use concise and efficient execution. The following are my input and output:
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
]
const output = [
"Type 1: Red(268), Blue(583)",
"Type 2: Blue(185)",
"Type 4: Red(326), Blue(967)",
"Type 5: Red(863)"
]
The following is my method. I use the set() to find out the number of types, and then use for loop to convert it into a string and push into the array, but it cannot be executed continuously, so my function cannot get the correct result, and it is not efficient.
this.ty = 1;
this.result = [];
const set = new Set();
const length = input.filter(item => !set.has(item.type) ? set.add(item.type) : false).length + 1;
for (let i = 1; i < length; i++) {
const temp = input.filter(x => {
return x.type === ty;
})
if (temp.length < 2) {
this.result.push(`Type ${ty}: ${temp[0].color}`);
} else {
this.result.push(`Type ${ty}: ${temp[0].color}, ${temp[1].color}`);
}
this.ty = i + 1;
}
This problem has troubled me for a long time. Can someone provide an easier way to convert this array? Thank you for your help.
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
]
console.log('input', input);
this.ty = 1;
this.result = [];
const set = new Set();
const length = input.filter(item => !set.has(item.type) ? set.add(item.type) : false).length + 1;
for (let i = 1; i < length; i++) {
const temp = input.filter(x => {
return x.type === ty;
})
if (temp.length < 2) {
this.result.push(`Type ${ty}: ${temp[0].color}`);
} else {
this.result.push(`Type ${ty}: ${temp[0].color}, ${temp[1].color}`);
}
this.ty = i + 1;
}
console.log('result', this.result);
// output
/* const output = [
"Type 1: Red(268), Blue(583)",
"Type 2: Blue(185)",
"Type 4: Red(326), Blue(967)",
"Type 5: Red(863)"
] */

You can use the Array.reduce() function to iterate your array and construct a new object.
const input = [{
"type": 1,
"color": "Red(268)"
},
{
"type": 1,
"color": "Blue(583)"
},
{
"type": 2,
"color": "Blue(185)"
},
{
"type": 4,
"color": "Red(326)"
},
{
"type": 4,
"color": "Blue(967)"
},
{
"type": 5,
"color": "Red(863)"
}
];
const mappedInput = input.reduce((grouped, {
type,
color
}) => {
if (!grouped.hasOwnProperty(type)) {
grouped[type] = `Type ${type}: ${color}`;
} else {
grouped[type] += `, ${color}`;
}
return grouped;
}, {});
console.log(Object.values(mappedInput));
We use an object to provide efficient key lookup and at the end, retrieve just the array of strings that we need.

You could reduce over the array to create an object that uses the type as a key and an array as a value, pushing new instances into the array with each iteration.
Then map over the Object.entries to produce a new array of strings.
const input = [{"type":1,"color":"Red(268)"},{"type":1,"color":"Blue(583)"},{"type":2,"color":"Blue(185)"},{"type":4,"color":"Red(326)"},{"type":4,"color":"Blue(967)"},{"type":5,"color":"Red(863)"}];
const out = input.reduce((acc, c) => {
const [ key, value ] = Object.values(c);
acc[key] = acc[key] || [];
acc[key].push(value);
return acc;
}, {});
const result = Object.entries(out).map(([key, value]) => {
return `Type ${key}: ${value.join(', ')}`
});
console.log(result);

Here's a simple, functional solution:
// Get list of unique input types
const types = Array.from(new Set(input.map(x => x.type)));
// Map over unique types, filter for matching inputs, yield all colors
const output = types.map(type => `Type ${type}: ${input.filter(x => x.type == type).map(x => x.color).join(', ')}`);

This is the Dynamic Solution for your problem.
let output = input.map(d => {
let k = Object.keys(d);
let v = Object.values(d)
let text = '';
for (var i in k) {
text += `${k[i]}: ${v[i]}, `
}
text = text.substring(0, text.length - 1);
return text })

Related

Fastest way to loop over 2 mixed arrays and return the matching value on intersection

I'm trying to create a JavaScript method which loops over 2 arrays and returns an array of the matched value.
My a1 parameter in the 'getMatchedArray' method is an array of strings and objects, while arr2 is always array of objects.
However, a2 parameter in the 'getMatchedArray' method is an array that can contain an object with value property or without value property as seen in the sample arrays used.
I'm very close to it but somehow not able to figure out, what is the mistake I'm making?
Is there a faster way using intersection to achieve this?
const arr1 = ["red", {
"code": "red",
"label": "test"
}, {
"code": "blue",
"label": "test1"
}, "white", "blue", {
"code": "red",
"label": "test2"
}];
const arr2 = [{
"code": "red",
"value": "test2"
}];
const arr3 = [{
"code": "blue"
}];
const arr4 = [{
"code": "red",
"value": "test3"
}]
function getMatchedArray(a1, a2) {
return a1.reduce((memo, opt) => {
const isOptionFound = a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return !opt.code && opt === obj.code;
}
});
if (isOptionFound) {
memo.push(opt);
}
return memo;
}, []);
}
const result1 = getMatchedArray(arr1, arr2);
const result2 = getMatchedArray(arr1, arr3);
const result3 = getMatchedArray(arr1, arr4);
console.log(result1);
console.log(result2);
console.log(result3);
Expected output:
result1:
[{
"code": "red",
"label": "test2"
}]
result2: ["blue"]
result3: ["red"]
result1, result 2 are fine, but my result3 is incorrect.
Any help on this?
//Try this
function findMatchingValues(arr1, arr2) {
const hashTable = {};
const matchingValues = [];
// Populate hash table with values from arr1
for (let i = 0; i < arr1.length; i++) {
const val = arr1[i];
hashTable[val] = true;
}
// Check arr2 for matching values
for (let i = 0; i < arr2.length; i++) {
const val = arr2[i];
if (hashTable[val]) {
matchingValues.push(val);
}
}
return matchingValues;
}
You can also achieve this requirement by separating the string and object elements from an array and then applied filter on those arrays based on the passed 2nd parameter in the function by stringify the passed parameters in the function.
Live Demo :
const arr1 = ["red", {
"code": "red",
"label": "test"
}, {
"code": "blue",
"label": "test1"
}, "white", "blue", {
"code": "red",
"label": "test2"
}];
const arr2 = [{
"code": "red",
"value": "test2"
}];
const arr3 = [{
"code": "blue"
}];
const arr4 = [{
"code": "red",
"value": "test3"
}];
function getMatchedArray(a1, a2) {
let strA2 = JSON.stringify(a2);
const strArrayFromA1 = a1.filter(item => typeof item === 'string');
const objArrayFromA1 = a1.filter(item => typeof item === 'object');
const matchedObject = objArrayFromA1.filter(elem => {
strA2 = strA2.replaceAll('value', 'label');
return strA2.includes(JSON.stringify(elem));
});
const matchedString = strArrayFromA1.filter(elem => strA2.includes(elem));
return matchedObject.length ? matchedObject : matchedString.length ? matchedString : 'No match found.';
}
const result1 = getMatchedArray(arr1, arr2);
const result2 = getMatchedArray(arr1, arr3);
const result3 = getMatchedArray(arr1, arr4);
console.log(result1);
console.log(result2);
console.log(result3);
The issue with the third result is that the expected output is an array of objects with the matching values, but the current implementation is returning a single object. To fix this, you can modify the function to push the opt value to memo instead of the opt object when there is a match in the arr4.
Here is the modified function:
function getMatchedArray(a1, a2) {
return a1.reduce((memo, opt) => {
const isOptionFound = a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return !opt.code && opt === obj.code;
}
});
if (isOptionFound) {
memo.push(opt.label || opt);
}
return memo;
}, []);
}
With this modification, the output for result3 will be ["red"], which is the expected result.
Regarding the second part of the question, there is a faster way to achieve this using the filter and includes array methods. Here is an example implementation:
function getMatchedArray(a1, a2) {
return a1.filter(opt => {
return a2.some(obj => {
if (obj.value) {
return obj.value === opt.label;
} else {
return opt.code && obj.code && opt.code === obj.code;
}
});
});
}
This implementation uses filter to create a new array with all the elements that match the condition, and includes to check if the a2 array includes the opt value.

Object Assign values from array rather than hard code

I am trying to group data by multiple properties and sum their values.
Here is what I tried as per this question
I had a follow up to this question:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I wanted to make this more reusable with the numerical values. In this for example, I want the
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
part especially to be reusable.
I got the numerical value keys in an array: let gee = ['used', 'instances'];
I am not sure how to use it with Object.assign. I tried to do this:
const result = [...arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
// console.log(o);
const item = gee.forEach(v => o[v] += o[v]);
// const item = r.get(key) || Object.assign({}, o, {
// used: 0,
// instances: 0
// });
// item.used += o.used;
// item.instances += o.instances;
return r.set(key, item);
}, new Map).values()];
But this is not working. How can I use an array for this bit of code:
const item = r.get(key) || Object.assign({}, o, {
used: 0,
instances: 0
});
item.used += o.used;
item.instances += o.instances;
If the Map object has the key, loop through the totalKeys and increment the object in the accumulator with current object's data. If it is new key, add a copy of the object to the Map
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, totalKeys) {
const group = arr.reduce((r, o) => {
const key = o.shape + '-' + o.color;
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['used', 'instances'])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You can make it even more dynamic by providing an array of keys to group by. Create the key using the values of the object separated by a |
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
Here's a snippet:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}];
function groupSum(array, groupKeys, totalKeys) {
const group = arr.reduce((r, o) => {
const key = groupKeys.map(k => o[k]).join("|");
if (r.has(key)) {
const item = r.get(key)
totalKeys.forEach(k => item[k] += o[k])
} else {
r.set(key, { ...o })
}
return r;
}, new Map);
return Array.from(group.values())
}
console.log(
groupSum(arr, ['shape', 'color'], ['used', 'instances'])
)
You could vastly simplify the dataset too by not using the combination of array.reduce() with a map()... and instead just build your new array by looping through all elements of the original array with array.forEach().
I added your use of the gee array as being a list of numeric fields you want to have added... to include making sure they exist on every object of the result array...whether or not they existed on each of the previous objects in arr.
const arr = [{
"shape": "square",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}, {
"shape": "circle",
"color": "blue",
"used": 0,
"instances": 0
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 4
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 1
}, {
"shape": "circle",
"color": "red",
"used": 1,
"instances": 0,
"testProp": 1
}, {
"shape": "square",
"color": "blue",
"used": 4,
"instances": 5
}, {
"shape": "square",
"color": "red",
"used": 2,
"instances": 1
}];
let gee = ['used', 'instances', 'testProp'];
let result = [];
arr.forEach((o) => {
// Setup TempSource since not all o may have all elements in gee
let tempSource = {};
gee.forEach((key) => {
if (o.hasOwnProperty(key)) {
tempSource[key] = o[key];
} else {
tempSource[key] = 0;
}
});
// Look to see if the result array already has an object with same shape/color
const matchingObject = result.find(element => {
let returnValue = true;
returnValue &= (element.shape == o.shape);
returnValue &= (element.color == o.color);
return returnValue;
});
if (matchingObject) {
// Matching Object already exists... so increment values
gee.forEach((key) => {
matchingObject[key] += tempSource[key];
});
} else {
// Matching Object missing, so merge newObject and insert
let newObj = {};
Object.assign(newObj, o, tempSource);
result.push(newObj);
}
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Maybe this would be a way to go:
const arr = [{"shape":"square","color":"red","used":1,"instances":1},{"shape":"square","color":"red","used":2,"instances":1},{"shape":"circle","color":"blue","used":0,"instances":0},{"shape":"square","color":"blue","used":4,"instances":4},{"shape":"circle","color":"red","used":1,"instances":1},{"shape":"circle","color":"red","used":1,"instances":0},{"shape":"square","color":"blue","used":4,"instances":5},{"shape":"square","color":"red","used":2,"instances":1}],
nums=["used","instances"]
function summationOn(ar,cnts){ // cnts: add up counts on these properties
const grp=Object.keys(ar[0]).filter(k=>cnts.indexOf(k)<0) // grp: group over these
return Object.values(ar.reduce((a,c,t)=>{
const k=grp.map(g=>c[g]).join("|");
if (a[k]) cnts.forEach(p=>a[k][p]+=c[p])
else a[k]={...c};
return a
},{}))
}
const res=summationOn(arr,nums);
console.log(res);
re-write
Similar to #adiga I now expect the "countable" properties to be given in the array cnts. With this array I collect all other properties of the first object of input array ar into array grp. These are the properties I will group over.

How can I add a unique ID to each object in an array?

I have this array of objects and I am trying to add a unique id to each object to have the desired output as shown below .But since I am new to Javascript this is a bit hard for me please can someone help me .
This is my Array Object Input:
const list = [
{"value": "Tape Measure"},
{"value": "Lawn Mower"}
],
]
This is my desired output with unique id's:
const desiredOuput = [
{
"id": "ur95tnnt949003",
"value": "Tape Measure",
},
{
"id": "0698080805kg",
"value": "Lawn Mower",
},
]
const list = [{
"data": [{
"value": "Tape Measure"
},
{
"value": "Lawn Mower"
}
],
"name": "Garden Todo",
}]
const res = list.map(o => {
o.data = o.data.map(d => ({ ...d,
id: randomId()
}));
return o;
})
console.log(res)
// Change this as desired
function randomId() {
return Math.random()
}
Here is sample method to generate randId.
In the method, 1) considering mix of numbers (0-9) and lower case alphabets (a-z). 2) required length of randId (size param)
const randId = (size) => {
const nums = Array.from({ length: 10 }, (_, i) =>
String.fromCharCode("0".charCodeAt(0) + i)
);
const alphabets = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode("a".charCodeAt(0) + i)
);
const chars = [...nums, ...alphabets];
const rand = (length) => Math.floor(Math.random() * length);
return Array.from({ length: size }, () => chars[rand(chars.length)]).join("");
};
const list = [{ value: "Tape Measure" }, { value: "Lawn Mower" }];
const newlist = list.map(({ value }) => ({ value, id: randId(6) }));
console.log(newlist);
Try this...
const list = [{
"data": [{
"value": "Tape Measure"
},
{
"value": "Lawn Mower"
}
],
"name": "Garden Todo",
}]
const result = list.map(l => {
l.data = l.data.map(d => ({id:Math.floor(Math.random() * Date.now()), ...d}));
return l;
})
console.log(result);
This function will give you fully unique id
function genID() {
const timeStamp = Date.now();
let str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
let Id = '';
for (let i = 0; i < 7; i++) {
let rom = Math.floor(1 +(str.length -1)*Math.random());
Id += str.charAt(rom);
}
Id += timeStamp.toString();
return Id;
}
You can also use only timeStamp
let id = Date.now();
npm install uuid
then:
const { v4: uuidv4 } = require('uuid');
list.forEach(el=> el.id = uuidv4());

Why does my plain JS loop not sum values to populate a new object?

I have an object which contains an array of ~130 objects which again have a field called orderProfit.
I now try to create a new object and populate it with the (chronological) sum of orderProfit.
Input (sample figures):
[{
"trades": [
{
"fields": {
"orderProfit": "100.000,00",
[...]
},
},
{
"fields": {
"orderProfit": "-500,00",
[...]
}
},
{
"fields": {
"orderProfit": "1.500,00",
[...]
}
},
[...]
}]
Desired output as array:
balanceByTrades = [100.000, 99.500, 101.000, ..., ]
Current attempt:
var balanceByTrades = [];
for (var i = 0; i < trades.length; i++) {
trades[i].fields.orderProfit = parseFloat(trades[i].fields.orderProfit);
// typeof returns number
if (i == 0) {
balanceByTrades[i] = trades[i].fields.orderProfit
} else {
balanceByTrades[i] = trades[i].fields.orderProfit + trades[i-1].fields.orderProfit
}
}
Which outputs
balanceByTrades = [100.000,00, -500,00, 1.500,00]
So why it doesn't sum?
When you need to parse a string to a number, don't use thousands separators, and to denote a decimal value, use a decimal point .
Remove all .s, and then replace the , with a .:
const arr = [{
"fields": {
"orderProfit": "100.000,00",
}
}, {
"fields": {
"orderProfit": "-500,00",
}
}, {
"fields": {
"orderProfit": "1.500,00",
}
}];
const orderProfits = arr.map(({ fields }) => fields.orderProfit);
let currentBalance = 0;
const balanceByTrades = orderProfits.map((profitStr) => {
const num = Number(
profitStr.replace(/\./g, '').replace(',', '.')
);
currentBalance += num;
return currentBalance;
});
console.log(balanceByTrades);

Single variable to stand in for multiple key names - javascript

I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');

Categories