How can I reduce elements in object? - javascript

I have an object like this:
result:
> rows:
> 0: {key: Array(4), value: 3}
> key: (4) ["Person", "2020-06-24", "Product, "00000000008"]
value: 3
> 1: {key: Array(4), value: 10}
> key: (4) ["Person", "2020-06-25", "Product, "00000000009"]
value: 10
> 2: {key: Array(4), value: 10}
> key: (4) ["Person", "2020-06-25", "Product, "00000000008"]
value: 10
Now, what I need to do is to reduce this result checking for the same code (for example 00000000008) and sum the value, to obtain:
(for example)
00000000008 value: 13
Now, my problem is how to do, I have tried to use first a map and then a reduce, but I don't understand how can I check for the same code and sum the value.
How can I do?
I have tried in this way, but it doesn't work:
res is the object with the values
let example = res.rows.map((element)=> {
console.log("ELEMENT IS ", element)
let example1 = element.key[3].reduce(function(element, v){
if(ref.hasOwnProperty(v))
element[ref[v]] += v;
else {
ref[v] = element.length;
element.push(prev = v)
}
return element
}, [])
})
console.log("element", element)

The Array.map method is useful for data transformations, but if you have to aggregate is mostly expensive because you have also to Array.filter the non-aggregated values.
You can use Array.reduce (MDN) instead in order to build your own object:
let result = {
rows: [
{
key: ["Person", "2020-06-24", "Product", "00000000008"],
value: 3
},
{
key: ["Person", "2020-06-25", "Product", "00000000009"],
value: 10
},
{
key: ["Person", "2020-06-25", "Product", "00000000008"],
value: 10
}
]
}
let output1 = result.rows.reduce((acc, current) => {
let key = current.key[3];
// adding value to the accumulator
acc[key] = (acc[key] || 0) + current.value;
return acc;
}, {});
console.log(output1);
let output2 = result.rows.reduce((acc, current) => {
// check if key is already present
let found = acc.find(v => v.key == current.key[3])
// if it is, update the current value
if (found) {
found.value += current.value;
}
// otherwise create a new one
else {
acc.push({ key: current.key[3], value: current.value });
}
return acc;
}, []);
console.log(output2)

create your own hashmap and loop over the result object once for all values
const hashmap = {};
rows.forEach(v => {
hashmap[v.product] = (hashmap[v.product] || 0) + v.value;
});
// then are you able to access any product value on O(1)
const total = hashmap['00000000008'];
console.log({total});
// total: 13

Related

Unable to add key value pairs in Javascript dictionary

I have some REACT code that generates a dictionary for me
My sample code is
var dict = []; // create an empty array
dict.push({
key: "keyName",
value: "the value"
});
and my dictionary looks like this
{key: "2021-03-31", value: 1}
{key: "2021-03-26", value: 1}
{key: "2021-03-27", value: 1}
{key: "2021-03-26", value: 1}
{key: "2021-03-27", value: 1}
Now I am trying to implement a logic like this.
If a key exists in the dictionary then I want to increase the number in the value by 1, or else insert a new key-value pair in the dictionary
var xkey=<<some value>>
if (xkey in dict) {
console.log("key found");
dt[xkey] +=1
}
else
{
dict.push(
{key: xkey
,value: 1 })
}
My If the condition is always failing and the code gets into the else block every time, hence the values for all keys are set to 1 whereas, in my example here, two of the keys should have had the value equal to 2.
My dictionary should have become
{key: "2021-03-31", value: 1}
{key: "2021-03-26", value: 2}
{key: "2021-03-27", value: 2}
Can someone please help me understand what I am doing wrong here?
You used Arrays and Objects in a mixed way. You can not acces an array elements by a key like you did. In other words you declared an Array not a Dictionory (called an Object in Javascript). The in operator only works aa s real members of the object dict.
Try this example:
var dict = {}; // create a map / object
...
var xkey=<<some key>>;
if (xkey in dict)
{
console.log("key found");
dict[xkey] +=1
}
else
{
dict[xkey] = 1;
}
There is also a shorter way.
var dict = {}; // create a map / object
...
var xkey=<<some key>>;
dict[xkey] = (xkey in dict) ? dict[xkey] + 1 : 1;
BTW your code is not compilable.
dict is an array and you probably want to find a specific date by key of a specific object in the dict array for modifying it's value . For finding your specific object in the array you can use simple find() method
let array = [
{ key: "2021-03-31", value: 1 },
{ key: "2021-03-26", value: 1 },
{ key: "2021-03-27", value: 1 },
]
const myKey = '2021-03-26'
let goalObject = array.find(object => object.key === myKey)
goalObject ? goalObject.value++ : array.push({ key: myKey, value: 1 })
You are trying to traverse to all records or else you can find an index and increase the value if the key is present. I have one solution maybe this will helps you to get your answer.
var dict = [];
function createDict(keyval) {
if (dict.length === 0) {
dict.push({
key: keyval,
value: 1
})
} else {
const index = dict.findIndex(x => x.key === keyval);
if (index > -1) {
dict[index].value += 1;
} else {
dict.push({
key: keyval,
value: 1
})
}
}
}
createDict("2021-03-31");
createDict("2021-03-26");
createDict("2021-03-27");
createDict("2021-03-26");
createDict("2021-03-27");
console.log(dict);
Final Results get like you want
[
{ key: '2021-03-31', value: 1 },
{ key: '2021-03-26', value: 2 },
{ key: '2021-03-27', value: 2 }
]
if (xkey in dict)
This is the problem. here dict is an array. So if you will check that x in dict it will look at the keys of an array.
In JavaScript arrays are just type of objects with keys that goes from 0 to length of the array and one extra key is length.
Let's see in your example:
your dict is like this:
dict = [
{key: "2021-03-31", value: 1},
{key: "2021-03-26", value: 1},
{key: "2021-03-27", value: 1},
{key: "2021-03-26", value: 1},
]
So if you run
for(xkey in dict){
console.log(xkey);
}
you will get output like this.
0
1
2
3
length
So if you check that "1" in dict then it will return true.
Solution to your problem might be look like this.
func addValue(newValue){ // newValue is value to add if does not exist and increment if exists
var isExist = false;
for(var i = 0; i < dict.length; i++){
if(dict[i].key === newValue){
dict[i].value++;
isExist = true;
break;
}
}
if(!isExist){
dict.push({key: newValue, value: 1})
}
}
More optimized solution is posted already by #Bhushi-da. This is for better understanding.

merging like objects and values with Array.reduce(}

I am using vanilla js for this helper function inside of a react app / typescript.
Json data is fetched this holds every letter of the alphabet which is assigned a value and a key. These are layed out into a grid of tiles. When a user selects a tile, this is added to gameData array in React which is used for a list. If a user clicks onto the same tile this is merged so instead of multiple list elements with same values they are merged with quantity + quantity and value + value
The structure is as such
const apiData = [
{key: 'A', value: 50, quantity: 1, color: '#3498db', ...etc},
{key: 'B', value: 40, quantity: 1, color: '#e67e22', ...etc},
...
]
const gameData = [
{key: 'A', value: 200, quantity: 4, color: '#3498db', ...etc},
{key: 'E', value: 10, color: '#fa0', ...etc},
]
export function groupBy(array: GameData[]) {
const group: GameData[] = Object.values(
array.reduce((acc: any, { value, quantity, ...r }) => {
const key = Object.entries(r).join("-");
acc[key] = acc[key] || { ...r, quantity: 0, value: 0 };
return ((acc[key].value += value), (acc[key].quantity += 1)), acc;
}, {})
);
return group;
}
The reducer works and merges properly but I just feel like there must be a better way to do this. Any ideas?
You can clean your groupBy reduce function as follows:
function groupBy(data: GameData[]) {
const result = data.reduce((total, item) => {
const { key, value, quantity } = item;
const prevItem = total[key] || {};
const {value: prevValue = 0, quantity: prevQuantity = 0} = prevItem;
total[key]= {
...item,
value: prevValue + value,
quantity: prevQuantity + quantity,
};
return total;
}, {});
return Object.values(result);
}
For the given input it produces the folowing result:
const gameData = [
{"key":"A","value":50,"quantity":1,"color":"#3498db"},
{"key":"A","value":50,"quantity":1,"color":"#3498db"},
{"key":"A","value":50,"quantity":1,"color":"#3498db"},
{"key":"B","value":50,"quantity":1,"color":"#3498db"},
{"key":"B","value":40,"quantity":1,"color":"#e67e22"}
];
const result = groupBy(gameData);
/*
result = [
{"key":"A","value":150,"quantity":3,"color":"#3498db"},
{"key":"B","value":90,"quantity":2,"color":"#e67e22"}
]
*/
Your solution seems ok, just a little hard to read. Here are my suggestions.
function groupBy(array){
return Object.values(array.reduce((grouped, {value, quantity,...rest}) => {
const key = Object.entries(rest).join('-');
if(!grouped[key]){
grouped[key] = {...rest, value: 0, quantity: 0};
}
grouped[key].quantity++;
grouped[key].value += value;
return grouped;
},{}));
}
Or using a simple for:
function groupBy(array) {
const grouped = {};
for(let obj of array){
const {value, quantity, ...rest} = obj;
const key = Object.entries(rest).join('-');
if(!grouped[key]){
grouped[key]= {...rest, value: 0, quantity: 0};
}
grouped[key].quantity++;
grouped[key].value+=value;
}
return Object.values(grouped);
}

retriving values from javascript object and then convert it to one object

I have a problem! I am creating an rating app, and I have come across a problem that I don't know how to solve. The app is react native based so I am using JavaScript.
The problem is that I have multiple objects that are almost the same, I want to take out the average value from the values of the "same" objects and create a new one with the average value as the new value of the newly created object
This array in my code comes as a parameter to a function
var arr = [
{"name":"foo","value":2},
{"name":"foo","value":5},
{"name":"foo","value":2},
{"name":"bar","value":2},
{"name":"bar","value":1}
]
and the result I want is
var newArr = [
{"name":"foo","value":3},
{"name":"bar","value":1.5},
]
If anyone can help me I would appreciate that so much!
this is not my exact code of course so that others can take help from this as well, if you want my code to help me I can send it if that's needed
If you have any questions I'm more than happy to answer those
Iterate the array with Array.reduce(), and collect to object using the name values as the key. Sum the Value attribute of each name to total, and increment count.
Convert the object back to array using Object.values(). Iterate the new array with Array.map(), and get the average value by dividing the total by count:
const arr = [{"name":"foo","Value":2},{"name":"foo","Value":5},{"name":"foo","Value":2},{"name":"bar","Value":2},{"name":"bar","Value":1}];
const result = Object.values(arr.reduce((r, { name, Value }) => {
if(!r[name]) r[name] = { name, total: 0, count: 0 };
r[name].total += Value;
r[name].count += 1;
return r;
}, Object.create(null)))
.map(({ name, total, count }) => ({
name,
value: total / count
}));
console.log(result);
I guess you need something like this :
let arr = [
{name: "foo", Value: 2},
{name: "foo", Value: 5},
{name: "foo", Value: 2},
{name: "bar", Value: 2},
{name: "bar", Value: 1}
];
let tempArr = [];
arr.map((e, i) => {
tempArr[e.name] = tempArr[e.name] || [];
tempArr[e.name].push(e.Value);
});
var newArr = [];
$.each(Object.keys(tempArr), (i, e) => {
let sum = tempArr[e].reduce((pv, cv) => pv+cv, 0);
newArr.push({name: e, value: sum/tempArr[e].length});
});
console.log(newArr);
Good luck !
If you have the option of using underscore.js, the problem becomes simple:
group the objects in arr by name
for each group calculate the average of items by reducing to the sum of their values and dividing by group length
map each group to a single object containing the name and the average
var arr = [
obj = {
name: "foo",
Value: 2
},
obj = {
name: "foo",
Value: 5
},
obj = {
name: "foo",
Value: 2
},
obj = {
name: "bar",
Value: 2
},
obj = {
name: "bar",
Value: 1
}
]
// chain the sequence of operations
var result = _.chain(arr)
// group the array by name
.groupBy('name')
// process each group
.map(function(group, name) {
// calculate the average of items in the group
var avg = (group.length > 0) ? _.reduce(group, function(sum, item) { return sum + item.Value }, 0) / group.length : 0;
return {
name: name,
value: avg
}
})
.value();
console.log(result);
<script src="http://underscorejs.org/underscore-min.js"></script>
In arr you have the property Value and in newArr you have the property value, so I‘ll assume it to be value both. Please change if wished otherwise.
var map = {};
for(i = 0; i < arr.length; i++)
{
if(typeof map[arr[i].name] == ‘undefined‘)
{
map[arr[i].name] = {
name: arr[i].name,
value: arr[i].value,
count: 1,
};
} else {
map[arr[i].name].value += arr[i].value;
map[arr[i].name].count++;
}
var newArr = [];
for(prop in map)
{
map[prop].value /= map[prop].count;
newArr.push({
name: prop,
value: map[prop].value
});
}
delete map;

Sort a list by property and add an object before each first letter changes in JavaScript

So I am trying to make a UI like this:
And I have an array of users
[{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
What I am trying to do is to sort the array by first letter of the name property, and add a header object before each. For example in the picture, you can see the letter A, B, I, and J as the headers.
For now, I got it working like this:
let final = []
// sort by first letter
const sortedUsers = state.test_list.sort((a, b) => a.name.localeCompare(b.name))
for (let x = 0; x < sortedUsers.length; x++) {
const user = sortedUsers[x].name
if (user.charAt(0) === 'A') {
const checkIfExists = final.findIndex((f) => f.header === 'A')
// add the header A if it doesn't exist
if (checkIfExists < 0) final.push({header: 'A'})
}
else if (user.charAt(0) === 'B') {
const checkIfExists = final.findIndex((f) => f.header === 'B')
// add the header B if it doesn't exist
if (checkIfExists < 0) final.push({header: 'B'})
}
// else if up to the letter Z
final.push(user)
}
and if I log the final array, I get:
which is correct.
My concern is that the code is very long, and I have no idea if it can be optimized or make the code smaller.
Is there any other option to do something like this? Any help would be much appreciated.
Why don't you create a collection of names, which is grouped by the first letter? You can then loop on it, and create your list. Use Array#reduce to create the grouped collection.
And then use Object#keys to iterate over the grouped collection and render your results:
let data = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Alison'
}, {
name: 'Andrea'
}, {
name: 'Betty'
}];
let combined = data.reduce((result, item) => {
let letter = item.name[0].toUpperCase();
if (!result[letter]) {
result[letter] = [];
}
result[letter].push(item);
return result;
}, {});
console.log(combined);
// Iterate over the result
Object.keys(combined).forEach(key => {
// key will be the first letter of the user names and
// combined[key] will be an array of user objects
console.log(key, combined[key]);
});
One thing still to do is to sort the user arrays by user name, which you can do easily using Array#sort.
Simple enough, try sorting them and then using .reduce:
const unsortedPeople = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
const sortedUsers = unsortedPeople.sort((a, b) => a.name.localeCompare(b.name))
const final = sortedUsers.reduce((finalSoFar, user) => {
const thisUserFirstChar = user.name[0];
if (finalSoFar.length === 0) addHeader();
else {
const lastUserFirstChar = finalSoFar[finalSoFar.length - 1].name[0];
if (lastUserFirstChar !== thisUserFirstChar) addHeader();
}
finalSoFar.push(user);
return finalSoFar;
function addHeader() {
finalSoFar.push({ header: thisUserFirstChar });
}
}, []);
console.log(final);
Why don't you just keep track of the current abbreviation as you loop. Then you can add a head when it changes:
var users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
const sortedUsers = users.sort((a, b) => a.name.localeCompare(b.name))
var currentHeader
let final = sortedUsers.reduce((a, user) => {
if (currentHeader !== user.name[0]) {
currentHeader = user.name[0]
a.push({header: currentHeader})
}
a.push(user)
return a
},[])
console.log(final)
Here's one way to do it:
const users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
let lastIndex;
let result = [];
users.sort((a, b) => {
return a.name > b.name;
}).forEach((user) => {
const index = user.name.charAt(0);
if (index !== lastIndex) {
result.push({
header: index
});
}
lastIndex = index;
result.push(user.name);
}, []);
console.log(result);
You can use _.orderBy(collection, [iteratees=[_.identity]], [orders]) and _.groupBy(collection, [iteratee=_.identity]) method of lodash.
This orderBy is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.
groupBy will creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).
example
// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
// Sort by `user` in ascending order and by `age` in descending order.
_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
With lodash
let myArr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
myArr = _.orderBy(myArr, ['name'], ['asc']);
let r = _.groupBy(myArr, o => {
return o.name.charAt(0).toUpperCase();
})
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
IN ES5
var arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}],
fChar = '';
arr = arr.sort(function(a, b) {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce(function(r, o) {
fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(arr);
IN ES6
const arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
let result = arr.sort((a, b) => {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce((r, o) => {
let fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(result);

Get Maximum value from JSON object using javascript?

I have JSON like
var JObject = [
{
a:"A1",
b:100,
c:800
},
{
a:"B1",
b:300,
c:400
}
];
I need maximum value from this JSON...it has to return 800 if it return key and column index
Since this is tagged in d3.
I will give a d3 answer.
Working code below
var kary = [
{
a:"A1",
b:100,
c:800
},
{
a:"B1",
b:1300,
c:400
},
{
a:"D1",
b:300,
c:400
}
];
var max = d3.max(kary, function(d){ return d3.max(d3.values(d).filter(function(d1){ return !isNaN(d1)}))});
console.log(max)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps!
You can do something like this
var JObject = [{
a: "A1",
b: 100,
c: 800
}, {
a: "B1",
b: 300,
c: 400
}];
var res = Math.max.apply(null,JObject.map(function(v) {
// iterate over array element and getting max value from result array
var r = [];
for (var val in v) {
// iterate over object inside array
if (v.hasOwnProperty(val)) {
var num = parseInt(v[val], 10);
// parsing the value for integer output
r.push(isNaN(num) ? 0 : num);
// pushing value to array, in case of `Nan` pushing it as 0
}
}
return Math.max.apply(null,r);
// getting max value from object values
}));
console.log(res);
You could make this more or less generic - and probably shorten it into a single reduce statement.
var data = [
{a:"A1",b:100,c:800},
{a:"B1",b:300,c:400}
];
data
.reduce(function(acc, x, i) {
if (x.b > x.c) {
return acc.concat({key: 'b', value: x.b });
}
return acc.concat({key: 'c', value: x.c });
}, [])
.reduce(function(acc, x, i) {
if (x.value > acc.value) {
return {
key: x.key,
value: x.value,
index: i
};
}
return acc;
}, {key: '', value: Number.MIN_VALUE, index: -1});
If you are using a library like lodash or underscore you can simply do:
_.max(_.map(JObject, function(a){
return _.max(_.values(a));
}));
You can also solve it with reduce:
_.reduce(JObject, function(val, a){
return _.max(_.values(a)) > val ? _.max(_.values(a)) : val;
},0);

Categories