remove and count duplicates from an groupedBy array in Javascript - javascript

I have an array which should be modified in a way that it removes and counts duplicate lamps for each room. Currently I have the following array:
I want to have them grouped by room. I use the lodash function for that:
groupByAndCount(lamps) {
const groupedArray = groupBy(lamps, function(n) {
return n.pivot.room;
});
return groupedArray;
},
This returns the array like this:
Basically each grouped Object looks the same like the objects in the first array. But now I want to use the function I wrote to count and remove the duplicates:
removeAndCountDuplicates(lamps) {
// map to keep track of element
// key : the properties of lamp (e.g name, fitting)
// value : obj
let map = new Map();
// loop through each object in Order.
lamps.forEach(data => {
// loop through each properties in data.
let currKey = JSON.stringify(data.name);
let currValue = map.get(currKey);
// if key exists, increment counter.
if (currValue) {
currValue.count += 1;
map.set(currKey, currValue);
} else {
// otherwise, set new key with in new object.
let newObj = {
id: data.id,
name: data.name,
fitting: data.fitting,
light_color_code: data.light_color_code,
dimmability: data.dimmability,
shape: data.shape,
price: data.price,
watt: data.watt,
lumen: data.lumen,
type: data.type,
article_number: data.article_number,
count: 1,
image: data.image
// room: data.pivot.room
};
map.set(currKey, newObj);
}
});
// Make an array from map.
const res = Array.from(map).map(e => e[1]);
return res;
},
The result should be an array like the first one. But should contain this:
1 Lamp for each room with the count of how many times it occurs
So the object should be like this:
id
name:
fitting:
light_color_code:
dimmability:
shape:
price:
watt:
lumen:
type:
article_number:
room:
count:
But I couldn't manage to get the functions work together so it returns this array. Can anyone help me in the right direction? Thanks in advance, any help is appreciated. It feels like i'm almost there.
Some sample data:
{
"id": 3,
"name": "Noxion Lucent LED Spot PAR16 GU10 4W 827 36D | Extra Warm Wit - Vervangt 50W",
"fitting": "GU10",
"light_color_code": "2700K - 827 - Zeer warm wit",
"dimmability": 0,
"shape": "Spot",
"price": 2.44,
"watt": 4,
"lumen": 370,
"type": "LED ",
"article_number": 234987,
"pivot": {
"order_id": 2,
"lamp_id": 3,
"room": "Garage"
},
"image": {
"id": 3,
"lamp_id": 3,
"name": "234987",
"path": "/storage/234987.jpg"
}
}

Since in the group by, you are creating an object with keys and arrays as it's values, you have to first iterate on the object (you can use Object.entries()) and then it's arrays.
Try passing the grouped object to the function below and you should get the array with correct counts.
Here's a sample to play with - https://stackblitz.com/edit/js-d2xcn3
NOTE: I have used pivot.lamp_id (and added it in the object as well) to use as the key for the map. You can choose to change it to some other unique property.
const removeDuplicates = (lamps) => {
let map = new Map();
Object.entries(lamps).forEach(([key, value])=> {
// loop through each object in Order.
lamps[key].forEach(data => {
// loop through each properties in data.
let currKey = JSON.stringify(data.pivot.lamp_id);
let currValue = map.get(currKey);
// if key exists, increment counter.
if (currValue) {
currValue.count += 1;
map.set(currKey, currValue);
} else {
// otherwise, set new key with in new object.
let newObj = {
lamp_id: data.pivot.lamp_id,
id: data.id,
name: data.name,
fitting: data.fitting,
light_color_code: data.light_color_code,
dimmability: data.dimmability,
shape: data.shape,
price: data.price,
watt: data.watt,
lumen: data.lumen,
type: data.type,
article_number: data.article_number,
count: 1,
image: data.image
// room: data.pivot.room
};
map.set(currKey, newObj);
}
});
})
// Make an array from map.
const res = Array.from(map).map(e => e[1]);
return res;
}

Related

How to write function that filters a dictionary when given a key/value pair in Javascript?

I have a dictionary called teamData
var teamData = {
app: {
sortCol:"name",
sortDir:"asc"
},
data: [
{
id: 1,
name:"Raptors",
coachId: 1,
coachFirst: "Ken",
coachLast: "jenson",
coachPhone: "801-333-4444",
coachEmail: "ken.jenson#uvu.edu",
coachLicenseLevel: 1,
league: 1,
division: 1
},
{
id: 2,
name:"Killer Bunnies",
coachId: 2,
coachFirst: "Peter",
coachLast: "Rabbit",
coachPhone: "801-333-4444",
coachEmail: "peter.rabbit#uvu.edu",
coachLicenseLevel: 1,
league: 1,
division: 2
},
{
id: 3,
name:"Thunderbirds",
coachId: 3,
coachFirst: "Harry",
coachLast: "DirtyDog",
coachPhone: "801-333-4444",
coachEmail: "harry.dirty.dog#uvu.edu",
coachLicenseLevel: 2,
league: 1,
division: 2
}
]
}
I'm trying to write a function that takes a key/value object and returns a filtered dictionary. So if the function is
let teams = filter({coachLicenseLevel:1});
then the expected result is to return a filtered dictionary with only two elements that have that key/value pair
Here is the function I have so far, but I'm stuck on how to get the key object.
filter(filterObj) {
const v = Object.values(filterObj);
const k = Object.keys(filterObj);
const res = teamData.filter(({???}) => v.includes(???));
}
any help would be appreciated.
If you want to filter only the data array, you could do something like this:
function filterArrayByParamAndValue(arr, itemParam, value) {
return arr.filter(item => item.itemParam === value)
}
And in your code just replace the data property, if
let teamData = {
....,
data: [...filterArrayByParamAndValue(teamData.data, coachLicenseLevel, 1)],
....
}
Of course you should also add all necessary checks in the filter function, or even add an object property to check for and pass the whole object.
Instead of passing an object, you may consider using the filter function with your custom filter logic. Here is an example for your specific case:
let teams = teamData.data.filter(item => item.coachLicenseLevel == 1)

How can I push data to an array with fixed length in js

I have an array of object something like this.
[
{
channelName: "WhatsApp"
count: 1
date: "2021-06-05"
},{
channelName: "RCS"
count: 1
date: "2021-06-09"
}
]
There are two types of channel names 1. WhatsApp and 2nd are RCS. I want to filter out count with specific channel names and store it in a separate array. But the problem here is I want both the array length should be the same. If there is data for WhatsApp then it will add the count otherwise it will add 0 in place of it.
For that, I did something like this but this does not work .
const filterData = (data: any) => {
const category: any = [];
const whatsAppCount: any = [];
const rcsCount: any = [];
data.filter((item: any, i: number) => {
if (item.channelName === "WhatsApp") {
whatsAppCount[i] = item.count;
} else if (item.channelName === "RCS") {
rcsCount[i] = item.count;
}
category.push(item.date);
});
setGraphData({
category: category,
whatsApp: whatsAppCount,
rcs: rcsCount,
});
console.log("handleRun", { category, whatsAppCount, rcsCount });
};
Here the console log gives something like this.
whatsAppCount: [1, 2, 13, 21, empty × 2, 8, 5, empty, 18, empty, 12, 4]
rcsCount: [empty × 4, 1, 12, empty × 2, 1, empty, 8]
Here in the place of empty, I want 0. I am not sure how to do that any help would be great.
When you create the arrays, but before populating them, there are two functions that can help with initialization:
// create an array with 10 slots preallocated but empty (not `undefined`)
let arr = new Array(10);
// set all allocated slots to a value (`0` in this case)
arr = arr.fill(0);
Since you know the lengths you want ahead of time, you can use that to pre-size the arrays on construction. Then use .fill to initialize the values to 0. Once, that's done, you can continue with your counting and updating the arrays.
Reference:
Array constructor
Array.prototype.fill()
I would suggest you use the map-function, mapping the unwanted values to undefined, letting the other values "pass through" (unmodified), eg.:
const filtered = data.map((each) => {
if (wantToKeep) {
return each;
} else {
return undefined;
}
});
Note, this is not the exact solution - but a general idea.
You can use forEach and push(0) for the empty records.
const data = [
{
channelName: "WhatsApp",
count: 1,
date: "2021-06-05",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-01",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-06",
},
{
channelName: "WhatsApp",
count: 5,
date: "2021-06-11",
},
{
channelName: "WhatsApp",
count: 7,
date: "2021-06-23",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-09",
},
];
const category = [];
const whatsAppCount = [];
const rcsCount = [];
data.forEach(x => {
if (x.channelName === "WhatsApp") {
whatsAppCount.push(x.count);
rcsCount.push(0);
} else if (x.channelName === "RCS") {
whatsAppCount.push(0);
rcsCount.push(x.count);
}
category.push(x.date);
});
console.log({ whatsAppCount });
console.log({ rcsCount });
console.log({ category });

Algorithm + ES6 - Compare 4 arrays of Objects and remove all repeated items

I am trying to delete all repeated objects between four arrays by preference. All the arrays have unique elements, and may not be ordered. Here is a picture that tries to explain the problem:
As you can see, if the array has a lower preference, the elements will stay inside it. For example, the object with id "6" is repeated in the arrays with preference 2, 3, and 4. So, the algorithm has to detect this and remove these objects from the arrays with preference 3 and 4, because 2 < 3 < 4.
So, if the input data is:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }, { id: "892d" }]
arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
the output must be:
arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
arr_p2 = [{id: "saa1" }]
arr_p3 = [{id: "pp182" }]
Any ideas on how to solve this situation in a good complexity order?
All arrays have a limited size of 40 objects.
The only thing I can think of is to sort all the objects, in each array, by identifier. Then, take the lowest identifier of an object moving with the pointer of each list, from the lowest preference (1) to the highest (4), and if it is in one of the higher preference lists, delete it... but I need to do it without altering the order of the elements ...
Pd: I am using JS and ES6.
Combine all items to a single array, and then reduce them to a Map in a reversed order using Array.reduceRight(). The reversed order will cause the 1st items to override the last items.
Now you can filter each array by using the Map, and keeping only items that exist on the Map.
Complexity is O(N1 + N2 + N3) where Nx is the length of that array.
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
// create an array of all items and reduce it in a reversed order to a Map
const dupsMap = [...arr_p1, ...arr_p2, ...arr_p3]
// create the Map by using the `id` as the key, and the object as the value
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
const filterArr = arr => arr.filter(o =>
dupsMap.get(o.id) === o // keep the item if it was the object that was used as value
)
const arr_p1f = filterArr(arr_p1)
const arr_p2f = filterArr(arr_p2)
const arr_p3f = filterArr(arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You can easily create a generic function that can handle any number of arrays, and get the individual arrays from it's returned value using destructuring.
const dedupArrays = (...arrs) => {
const dupsMap = arrs.flat() // convert arrays to a single array
// a reduce right to create a Map of [id, object]
.reduceRight((acc, o) => acc.set(o.id, o), new Map())
// map the array of arrays, and filter each sub array
return arrs.map(arr => arr.filter(o => dupsMap.get(o.id) === o))
}
const arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }]
const arr_p2 = [{id: "saa1" }, { id: "892d" }]
const arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }]
const [arr_p1f, arr_p2f, arr_p3f] = dedupArrays(arr_p1, arr_p2, arr_p3)
console.log({ arr_p1f, arr_p2f, arr_p3f })
You could generate a preference object (hash map) to map the id to preference. Run it from 3rd array to the first so that lower order overrides the higher one.
Then when you have the preference map, you can filter all arrays by checking if the id's preference matches the current array.
let arr_p1 = [{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
let arr_p2 = [{id: "saa1" }, { id: "892d" }];
let arr_p3 = [{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
let pref = {};
arr_p3.forEach(e => pref[e.id] = 3);
arr_p2.forEach(e => pref[e.id] = 2);
arr_p1.forEach(e => pref[e.id] = 1);
arr_p1 = arr_p1.filter(e => pref[e.id] === 1);
arr_p2 = arr_p2.filter(e => pref[e.id] === 2);
arr_p3 = arr_p3.filter(e => pref[e.id] === 3);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);
I have several tips for you, rather than a full answer, since I assume this is a homework question?
Strategy
Build a set of "items already seen"
Check each new array against that, deleting any duplicate entries (in the new array).
Start with the most preferred array
That way, whenever something is deleted, it is being deleted from the less-preferred array.
For example, in pseudocode
let elementsSeen = new Set( most preferred array of elements )
for array in listOfArraysInDecreasingOrderOfPreference {
for element in array {
if element is in elementsSeen, delete it from array
}
elementsSeen = union of elementsSeen and array
}
Complexity
Every item has to be looked at. It has to be compared with every other item, but the complexity of that need not be enormous, because the `Set` process can make use of hashes, i.e. not have to do an individual comparison of each incoming object with each existing object. Almost all incoming objects will have a hash table value that is different from those of existing objects, which is quick, at the expense of some time spent on hashing and some memory spent on the table.
In the worst case, where hashing is no longer helping you, it is O(N x M) where N is the number of arrays, and M is the size of each.
Your question implies you want to mutate the original arrays.
So if you still want to mutate the arrays you could.
create a SET of the ID's for each level.
Loop each level backward, if any id's in higher level then remove from array.
A couple of optimisation here too, eg. slice(0, -1), is so we don't need to create a SET for the last level, as were check previous ones. Inside the loop once item is known to be deleted, use a break to then go onto next. To be honest, I've no idea what the complexity on this is.. :)
eg.
const arr_p1 =
[{ id: "892d" }, {id: "kla8x" }, {id: "sys32" }];
const arr_p2 =
[{id: "saa1" }, { id: "892d" }];
const arr_p3 =
[{ id: "kla8x" }, {id: "saa1" }, {id: "pp182" }];
function dedupe(alist) {
const hasList = alist.map(
m => new Set(m.slice(0, -1).map(i => i.id)));
for (let l = alist.length -1; l > 0; l --) {
for (let i = alist[l].length -1; i >= 0; i --) {
for (let h = 0; h < l; h += 1) {
if (hasList[h].has(alist[l][i].id)) {
alist[l].splice(i, 1);
break;
}
}
}
}
}
dedupe([arr_p1, arr_p2, arr_p3]);
console.log(arr_p1);
console.log(arr_p2);
console.log(arr_p3);

Javascript - Create and populate associative array containing sub arrays

I'm trying to collate some data. I would like to populate an array containing sub arrays, for example, I have some json data that I am iterating over:
{
"name": "name1",
"prices": "209.67"
},
{
"name": "name1",
"prices": "350"
},
{
"name": "name2",
"price": "195.97"
},
I would like to create an array that ends up looking something like the following:
myArray['name1']prices[0] = 209.67,
prices[1] = 350,
['name2']prices[0] = 195.97
I thought that the code below would achieve what I wanted but it doesn't work. It throws an exception. It doesn't seem to recognise the fact that the prices are an array for a given index into the main array. Instead the prices appear at the same level as the names. I want the main array for a given name to contain an inner array of prices.. Does anybody have any idea how I could modify to make this work?
function doStuff() {
var cryptoData = getData();
var datasetValues = {};
datasetValues.names = [];
datasetValues.names.prices = [];
for (var result = 0; result < cryptoData.length; result++) {
var data = cryptoData[result];
if (datasetValues.names.indexOf(data.cryptoname) === -1)
{
datasetValues.names.push(data.cryptoname);
}
// This works
//datasetValues.names.prices.push(data.prices);
// This doesn't!
datasetValues.cryptoNames[data.cryptoname].prices.push(data.prices);
}
}
You could reduce the array by using an object and take a default object if the property is not set. Then push the price.
var data = [{ name: "name1", price: "209.67" }, { name: "name1", price: "350" }, { name: "name2", price: "195.97" }],
result = data.reduce((r, { name, price }) => {
r[name] = r[name] || { name, prices: [] };
r[name].prices.push(+price);
return r;
}, Object.create(null));
console.log(result);
Try this
function parseData(input){
return input.reduce(function(o,i){
o[i.name] = {};
if(!o[i.name]['prices']){
o[i.name]['prices'] = [];
}
o[i.name]['prices'].push(i.prices);
return o;
},{});
}

Create a key value pair for both the current key and value

I currently have an array of simple objects:
data = [{ "alternative hip hop": 3 }, { "escape room": 4 }, ...]
...to which I'd like to transform into
newData = [
{ "name": "alternative hip hop", "count": 3 },
{ "name": "escape room", "count" 4 }
]
Is this doable with a map? I've failed at figuring out how to do so with one.
lodash responses are welcome as well
Assuming each item in the data has a single key/value pair, you'd want:
data.map(obj => {
const key = Object.keys(obj)[0]; //Just get the first key we find
return {
name: key,
count: obj[key]
};
})
I hope I didn't write it false on my Mobile, but here is a way:
let newData = data.map(e => {
for(let key in e) {
return {
name: key,
count: e[key]
};
}
});
Im not familiar with loadsh, but with javascript you can do the following:
var data = [{ "alternative hip hop": 3 }, { "escape room": 4 }]
var newData = [];
data.forEach((item)=>{ //loop over the array
Object.keys(item) //get the object keys
.map((keyName)=>{ //loop over each key
newData.push({name:keyName, count:item[keyName]}) //insert it into the new array
})
})
console.log(newData)

Categories

Resources