I have this simple array:
const arr = [
{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
I want to create a hash map where I want to add new colors on that key.
E.g I want to added color: green on id: 3
Now here you can see there is no id: 3
Now here I am expecting:
{
2: [{color: "red"}]
1: [{color: "blue"}, {color: "yellow"}],
3: [{color: "green"}]
}
Now if I want to add color: brown on id: 2
In that case I am expecting:
{
2: [{color: "red"}, {color: "brown"}]
1: [{color: "blue"}, {color: "yellow"}],
3: [{color: "green"}]
}
I have created a Playground:
const arr = [
{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
function addItem(id: number, colors: any) {
let newArr = {[id]: colors};
arr.forEach(function (obj) {
newArr[obj.id].push({id: obj.color});
});
return newArr;
}
console.log(addItem(3, [{color: "green"}]))
console.log(addItem(1, [{color: "brown"}]))
Here I also want to avoid duplicates
const arr = [{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
const grouped = arr.reduce((groups, current) => {
if (!(current.id in groups)) {
groups[current.id] = []
}
groups[current.id].push({
color: current.color
})
return groups
}, {})
addItem(3, {
color: "green"
})
addItem(1, {
color: "brown"
})
console.log(grouped)
function addItem(id, item) {
if (!(id in grouped)) {
grouped[id] = []
}
grouped[id].push(item)
}
function test(id, color) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id == id) {
arr[i].color.push(color)
}
}
}
Basically it will loop through your array, and if its id matches, it will add your color.
Following code can help you to solve this problem
const hashMap = new Map([
[1, [{ color: "red" }]],
[2, [{ color: "blue" }]],
[3, [{ color: "yellow" }]],
]);
function addItem(id, colors) {
hashMap.set(
id,
hashMap.has(id) ? [...hashMap.get(id).concat(colors)] : colors
);
return hashMap;
}
console.log(hashMap);
console.log(addItem(3, [{ color: "green" }]));
console.log(addItem(4, [{ color: "pink" }]));
let arr = [{
"id": 2,
"color": "red"
},
{
"id": 1,
"color": "blue"
},
{
"id": 2,
"color": "yellow"
},
];
const groupBy = (xs, key) => {
return xs.reduce(function(rv, x) {
const y = {...x};
delete y[key];
(rv[x[key]] = rv[x[key]] || []).push(y);
return rv
}, {})
}
const addItem = (id, colors) => {
// const newArr = arr... etc if you don't want to modify the existing array
arr = arr.concat(colors.map(c => {
c.id = id;
return c
}))
const grouped = groupBy(arr, 'id')
return grouped
}
console.log(addItem(3, [{
color: "green"
}]))
console.log(addItem(1, [{
color: "brown"
}]))
You may find a class useful here to encapsulate your data.
On initialisation pass in your array of objects and convert it to a Map with array values. (Note: you can use an object here as an alternative).
Create a method that adds new colours to the appropriate map array if they don't already exist (i.e. testing for duplicates).
const arr=[{id:2,color:"red"},{id:1,color:"blue"},{id:2,color:"yellow"}];
class ColorMap {
// `reduce` over the array to create a `colors` Map.
// If the id doesn't exist on the map as a key,
// create it, and assign an empty array to it.
// Then push in the color to the array if
// it doesn't already exist
constructor(arr) {
this.colors = arr.reduce((acc, obj) => {
const { id, color } = obj;
if (!acc.has(id)) acc.set(id, []);
if (!this.colorExists(id, color)) {
acc.get(id).push({ color });
}
return acc;
}, new Map());
}
// Simple check to see if the color already
// exists in the target array
colorExists(id, color) {
return this.colors?.get(id)?.find(obj => {
return obj.color === color;
});
}
// Similar to the `reduce` function, if the id doesn't have
// a key on the map create one, and initialise an empty array,
// and if the color doesn't already exist add it
addColor(id, color) {
if (!this.colors.has(id)) this.colors.set(id, []);
if (!this.colorExists(id, color)) {
this.colors.get(id).push({ color });
}
}
// Return the colors map as a readable object
showColors() {
return Object.fromEntries(this.colors);
}
}
const colorMap = new ColorMap(arr);
colorMap.addColor(3, 'green');
colorMap.addColor(1, 'brown');
colorMap.addColor(1, 'brown');
console.log(colorMap.showColors());
Additional documentation
Logical nullish assignment
reduce
Spread syntax
Related
I am having the below array of object on which I want to perform group based on entity name and I have see if two objects have same entity Name if yes then I have to see the color if color of both objects are same then I have to group them into one and combine the details of both.
If entity Name of two objects are same and color is different then I have to group them and set color as yellow and have to combine the details and return back the new array of objects.
let data = [
{entityName: "Amazon", color: "red", details: "Hello"}
{entityName: "Amazon", color: "green", details: "World"}
{entityName: "Flipkart", color: "green", details: "1234567"}
]
My excepted output from the above array should be this.
result = [
{entityName: "Amazon", color: "yellow", details: "Hello world"}
{entityName: "Flipkart", color: "green", details: "1234567"}
]
Could anyone please tell me how can I do it?
You can just iterate over items and find a matching item and implement your logic.
let data = [{
entityName: "Amazon",
color: "red",
details: "Hello"
},
{
entityName: "Amazon",
color: "green",
details: "World"
},
{
entityName: "Flipkart",
color: "green",
details: "1234567"
}
];
var result = [];
for (const value of data) {
const item = result.find(f => f.entityName === value.entityName);
if (!item) {
result.push(value);
} else {
if (item.color !== value.color) {
item.color = 'yellow';
}
item.details += ' ' + value.details;
}
}
console.log(result);
You can loop through data and search for the index of the item that has the same name but different color (the if statement checks if indexOfFound >= 0 because if an item is not found, findIndex returns -1) mutate the item and then remove the item that was found.
So something like this:
let data = [{
entityName: "Amazon",
color: "red",
details: "Hello"
}, {
entityName: "Amazon",
color: "green",
details: "World"
}, {
entityName: "Flipkart",
color: "green",
details: "1234567"
}];
data.forEach(item => {
const indexOfFound = data.findIndex(diffItem => diffItem.entityName == item.entityName && diffItem.color !== item.color);
if (indexOfFound >= 0) {
item.color = "yellow";
item.details = `${item.details} ${data[indexOfFound].details.toLowerCase()}`
data.splice(indexOfFound, 1);
}
});
console.log(data);
I already figured out the way to grouping object by its category using the code below:
let groupBy = (element, key) => {
return element.reduce((value, x) => {
(value[x[key]] = value[x[key]] || []).push(x);
return value;
}, {});
};
let items = groupBy(results, 'category')
And, the result would be like so:
{
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": "#000000",
"shp_prop": "Batu Ampar"
},
{
"shp_name": "Village Boundary",
"color_prop": "#FFFFFF",
"shp_prop": "Sungai Jawi"
}
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": "#000000",
"shp_prop": "Grassland"
},
]
}
I want to group them again by merging the color_prop and shp_prop to an array inside the object like below:
{
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": ["#000000","#FFFFFF"],
"shp_prop": ["Batu Ampar","Sungai Jawi"]
},
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": ["#000000"],
"shp_prop": ["Grassland"]
},
]
}
I really appreciate it if someone could help me to solve this problem. Thank you.
You could introduce a helper that converts "rows" into "columns".
function toColumns(rows) {
const columns = {};
const keys = Object.keys(rows[0]);
for (const key of keys) columns[key] = [];
for (const row of rows) {
for (const key of keys) {
columns[key].push(row[key]);
}
}
return columns;
}
This helper converts a structure like:
[
{ a: 1, b: 2 },
{ a: 3, b: 4 },
]
Into:
{ a: [1, 3], b: [2, 4] }
With this helper defined you can convert your groups into the desired structure in the following manner:
for (const category in items) {
const columns = toColumns(items[category]);
// use the shp_name of the first item instead of an array
columns.shp_name = columns.shp_name[0];
items[category] = columns;
}
function toColumns(rows) {
const columns = {};
const keys = Object.keys(rows[0]);
for (const key of keys) columns[key] = [];
for (const row of rows) {
for (const key of keys) {
columns[key].push(row[key]);
}
}
return columns;
}
const items = {
"Administration": [
{
"shp_name": "Village Boundary",
"color_prop": "#000000",
"shp_prop": "Batu Ampar"
},
{
"shp_name": "Village Boundary",
"color_prop": "#FFFFFF",
"shp_prop": "Sungai Jawi"
}
],
"Land_use": [
{
"shp_name": "Land Use 2019",
"color_prop": "#000000",
"shp_prop": "Grassland"
},
]
}
for (const category in items) {
const columns = toColumns(items[category]);
columns.shp_name = columns.shp_name[0];
items[category] = columns;
}
console.log(items);
Replace items[category] = columns with result[category] = columns (where result is defined as const result = {} before the loop) if you don't want to mutate the original object.
Get the corresponding type in the object, and then traverse the array of push objects, but I can't think of a better way to solve the desired result below.
I want a good return as follows:
[{
"id": 1,
"type": "one",
"name": ["apple","apricot"]
},
{
"id": 3,
"type": "two",
"name": ["avocado"]
}]
var result = [{
"id": 1,
"type": "one",
"name": "apple"
}, {
"id": 2,
"type": "one",
"name": "apricot"
},
{
"id": 3,
"type": "two",
"name": "avocado"
}
]
Array.prototype.unique = function() {
var hash = {},
len = this.length,
result = [];
for (var i = 0; i < len; i++) {
if (!hash[this[i].type]) {
result.push(this[i].type);
hash[this[i].type] = true;
}
}
return result;
}
console.log(result)
console.log(result.unique())
var cArr = result.unique()
var arr = []
cArr.forEach(function(prop) {
result.map(function(item) {
if (prop == item.type) {
console.log(item)
arr.push({
...item,
[`user_${item.id}`]: item.user,
})
}
})
})
console.log(arr)
You can do this with reduce quite easily:
var input = [
{ id: 1, type: "one", name: "apple"},
{ id: 2, type: "one", name: "apricot" },
{ id: 3, type: "two", name: "avocado" }
];
// Make sure `unique` doesn't already exist on the Array prototype
if (!('unique' in Array.prototype)) {
Array.prototype.unique = function () {
// iterate over the array
const temp = this.reduce((acc, current) => {
// Desstructure the id, type, and name from the current object
const { id, type, name } = current;
// If an key with the value of `type` doesn't exist
// on the accumulator, add a new object with name set
// to an empty array
acc[type] = acc[type] || { id, type, name: [] };
// Push the name in the current object to the name array
acc[type].name.push(name);
// Return the accumulator for the next iteration
return acc;
// Note: the initial accumulator value is an object
}, {});
// Then simply return the values from the accumulated object
return Object.values(temp);
}
}
console.log(input.unique())
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
Here field color is of type String, and Location is an array.
I have persons, and then there is a filter criteria. I need an output so that all the values selected in the filter needs to match with the data. So in the data provided, only those records should be visible if Silver, Up and Down are available in a record. (Note the AND parameter, there is no OR condition anywhere).
So the output will be:
{ Color: "Silver", Location: ["Up", "Down"] }
Now if the filter criteria is:
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up"] }
];
the output will be:
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
So you see all the values in the filter should match with the records.
I broke down the problem into separate functions. It's way more verbose than your solution but I do think it's more readable.
Also: it does work.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
const arraysEqual = (arr1, arr2) => {
// Very simple array comparison.
if (arr1.length !== arr2.length) return false;
arr1 = arr1.sort();
arr2 = arr2.sort();
for(let i=0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
};
let result = persons.filter(person => {
// All criteria must match.
for (let criterium of criteria) {
if (criterium.Field === 'Color') {
if (person.Color !== criterium.Values[0]) return false;
}
if (criterium.Field === 'Location') {
if (!arraysEqual(person.Location, criterium.Values)) return false;
}
}
// We didn't *not* match for anything, so we matched!
return true;
});
console.log(result);
/*
{ Color: "Silver", Location: ["Down", "Up"] }
*/
See https://repl.it/#niels_bom/GoldenIdealMicrobsd for a running example.
After third edit, now I am clear on what you want - to have an array as a subset of the other.
var result = persons.filter(function (person) {
return criteria.every(function (c) {
var value = person[c.Field];
if (typeof value === 'object') {
return c.Values.length<=value.length &&
c.Values.every(function(v,i){return value.includes(v) ;});
}
else
return c.Values.indexOf(value) > -1;
})
})
I also updated your jsfiddle : https://jsfiddle.net/gzL42dna/1/
Also checkout: How to compare arrays in JavaScript?
I believe this works. Using a Set helps a lot with partial matches. Tell me if you would like an explanation of the code.
var persons = [
{ Color: "Gold", Location: ["Down"] },
{ Color: "Silver", Location: ["Up", "Down"] },
{ Color: "Silver", Location: ["Up"] }
];
var criteria = [
{ Field: "Color", Values: ["Silver"] },
{ Field: "Location", Values: ["Up", "Down"] }
];
console.log(match(persons, criteria));
function match(persons, criteria) {
let personMatches = [...persons]
for (let i=0; i < criteria.length; i++) {
let {Field, Values} = criteria[i]
personMatches = personMatches.filter(obj => {
if (Array.isArray(obj[Field])) {
return hasMatches(obj[Field], Values)
} else {
return Values.includes(obj[Field])
}
})
}
return personMatches
}
function hasMatches(arr1, criteria) {
let criteriaSet = new Set(criteria)
let personsSet = new Set(arr1)
for (let el of criteriaSet) {
if (!personsSet.has(el)) return false
}
return true
}
Input :
"options": [
{
"name": "Color",
"values": [
"Blue",
"Black"
]
},
{
"name": "Size",
"values": [
"Small",
"Large"
]
}
]
Output: "variants": [
{
"option1": "Blue",
"option2": "Small"
},
{
"option1": "Blue",
"option2": "Large"
},
{
"option1": "Black",
"option2": "Small"
},
{
"option1": "Black",
"option2": "Large"
}
]
How to solve this using recursion ?The options array can contain multiple name and i need the above out to be displayed. Can it be done using cartesian product i guess
You could take an iterative and recursive approach for getting all option combinations.
function getCombinations(array) {
function iter(i, temp) {
if (i === array.length) {
result.push(temp.reduce(function (o, v, i) {
o['option' + (i + 1)] = v;
return o;
}, {}));
return;
}
array[i].values.forEach(function (a) {
iter(i + 1, temp.concat(a));
});
}
var result = [];
iter(0, []);
return result;
}
var options = [{ name: "Color", values: ["Blue", "Black"] }, { name: "Size", values: ["155", "159"] }, { name: 'Material', values: ['Sand', 'Clay', 'Mud'] }],
variants = getCombinations(options);
console.log(variants);
.as-console-wrapper { max-height: 100% !important; top: 0; }
ES6
function getCombinations(array) {
function iter(i, temp) {
if (i === array.length) {
result.push(temp);
return;
}
array[i].values.forEach(a => iter(i + 1, Object.assign({}, temp, { ['option' + (i + 1)]: a })));
}
var result = [];
iter(0, {});
return result;
}
var options = [{ name: "Color", values: ["Blue", "Black"] }, { name: "Size", values: ["155", "159"] }, { name: 'Material', values: ['Sand', 'Clay', 'Mud'] }],
variants = getCombinations(options);
console.log(variants);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use nested Array.map() calls, create the objects, and flatten the sub arrays using Array.concat():
const options = [{"name":"Color","values":["Blue","Black"]},{"name":"Size","values":["155","159"]}]
const [{ values: colors }, { values: sizes }] = options
const result = [].concat(...colors.map((option1) => sizes.map((option2) => ({
option1,
option2
}))))
console.log(result)
var myarray = {"options": [
{
"name": "Color",
"values": [
"Blue",
"Black"
]
},
{
"name": "Size",
"values": [
"155",
"159"
]
}
]};
const key = myarray.options[0].values;
const value =myarray.options[1].values;
const output = _.zipWith(key, value, (key, value)=> ({ key, value }));
console.log(output);
<script src="https://cdn.jsdelivr.net/lodash/4.16.6/lodash.min.js"></script>