I have such an array:
let array = {
[1]: {
name: 'test 1',
count: 5
},
[2]: {
name: 'test 2',
count: 3
}
}
How can I sum the values in the "count" column? Examples from simple arrays do not work. I currently have such a loop. Can it be done somehow better?
let sum = 0
Object.entries(array).forEach(([key, val]) => {
sum += val.count
});
Use reduce
let array = { 1: { name: "test 1", count: 5, }, 2: { name: "test 2", count: 3, }, };
total = Object.values(array).reduce((t, { count }) => t + count, 0); //t accumulator accumulates the value from previous calculation
console.log(total);
if you want to use a forEach loop like in your method use Object.values() instead because you only need values to calculate the sum of count
let array = {
1: { name: "test 1", count: 5 },
2: { name: "test 2", count: 3 },
};
let sum = 0;
Object.values(array).forEach(({ count }) => {
sum += count;
});
console.log(sum);
Building on top of the answer provided by #Sven.hig
Since you are calling the object "array" you might want to use an actual array instead.
Creating some functions to abstract away the complexity will help you understand your code better, when you come back to it in the future.
const add = (a, b) => a + b;
const sum = arr => arr.reduce(add, 0);
const data = [{
name: "test 1",
count: 5,
}, {
name: "test 2",
count: 3,
}
];
const total = sum(
data.map(d => d.count)
);
console.log(total);
Related
I have an Object as below:
const boxOfFruits = {
apples: [
{
name: "Kashmiri",
},
{
name: "Washington",
},
{
name: "Himalayan",
},
{
name: "Fuji",
}
],
oranges: [
{
name: "Nagpur",
},
{
name: "Clementine",
},
],
mangoes: [
{
name: "Totapuri",
},
{
name: "Alphonso",
},
{
name: "Langda",
},
],
}
I want to divide these fruits into boxes; maximum of n each, let's say where n is 3 and apples, oranges and mangoes are equally distributed.
So the output in this case would be:
box_1 = [{name: "Kashmiri"}, {name: "Nagpur"},{name: "Totapuri"}];
box_2 = [{name: "Washington"}, {name: "Clementine"},{name: "Alphonso"}];
box_3 = [{name: "Himalayan"},{name: "Langda"}, {name: "Fuji"}];
The type of fruits(apple,oranges,etc)/keys in object can increase/decrease and n is also variable. In case total fruits are less than n, then it would be just 1 box of fruits.
What I have tried so far:
Using Lodash, I am calculating the minimum and the maximum fruits in a single type:
const minFruitType = _.min(Object.values(basket).map((eachBasket: any) => eachBasket.length));
Total teams will the sum of the fruits / n
Will distribute the minimum fruits (l) in the first l boxes and fill the rest with the remaining fruits at every iteration while at the start of every iteration will calculate the minimum type of fruits again.
You can use Object.values(), array#reduce and array#forEach to transform your object.
const boxOfFruits = { apples: [ { name: "Kashmiri", }, { name: "Washington", }, { name: "Himalayan", }, ], oranges: [ { name: "Nagpur", }, { name: "Clementine", }, ], mangoes: [ { name: "Totapuri", }, { name: "Alphonso", }, { name: "Langda", }, ], },
result = Object.values(boxOfFruits).reduce((r, arr) => {
arr.forEach((o,i) => {
const key = `box_${i+1}`;
r[key] ??= r[key] || [];
r[key].push(o)
});
return r;
},{});
console.log(result);
The easiest way would be to use lodash.js's zip() function:
const boxes = _.zip( Object.values(boxOfFruits) );
Note that _.zip() will give you undefined values when the source arrays are different lengths, so you'll need/want to filter those out:
const boxes == _.zip( Object.values(boxOfFruits) )
.map(
box => box.filter(
x => x !== undefined
)
);
But that will not distribute the fruits evenly. For that, it shouldn't get much for difficult than this:
function distribute(boxOfFruits, n) {
const boxes = [];
const fruits = Object.keys(boxOfFruits);
for ( const fruit of fruits ) {
let i = 0;
const items = boxOfFruits[fruit];
for (const item of items) {
boxes[i] = !boxes[i] ?? [];
boxes[i] = boxes[i].push(item);
++i;
i = i < n ? i : 0 ;
}
}
return boxes;
}
A modified version of #Nicholas Carey's answer worked for me:
function distribute(boxOfFruits, n) {
let boxes = [];
let totalFruits = Object.values(boxOfFruits)
.reduce((content, current) => content + current.length, 0);
let maxBoxes = Math.ceil(totalFruits / 4);
Object.values(boxOfFruits).forEach((fruits) => {
let i = 0;
fruits.forEach((fruit) => {
boxes[i] ??= boxes[i] || [];
boxes[i].push(fruit);
++i;
i = i < (n+1) ? i : 0;
});
});
// Extra boxes created, redistribute them to
// starting boxes
let newBoxes = teams.slice(0, maxBoxes);
let pendingBoxes = teams.slice(maxBoxes);
let pendingFruits = pendingBoxes.flat();
let distributedBoxes = newBoxes.map((eachBox) => {
let required = n - eachBox.length;
if (required > 0) {
eachBox.push(...pendingFruits.splice(0, required));
}
return eachBox;
});
return distributedBoxes;
}
Code is pretty much the same as Nicholas's accept the below changes:
Directly fetched the values and iterated over those
empty array creation was failing, this way works
and checking on the max box size with n+1 instead of n
I have an array of objects similar to:
[
{
number: 1,
name: "A"
},
{
number: 2,
name: "e",
},
{
number: 3,
name: "EE",
}
]
I need to be able to insert an object to the array at a particular position, but then I have to shift all the numbers of rest of the object, so the numbers are in sequence order.
Similarly I need to be able to remove an object, but be able to shift all the numbers back.
i.e. If I insert at location 1 with name: "F", the array became:
[
{
number: 1,
name: "A"
},
{
number: 2,
name: "F"
},
{
number: 3,
name: "e",
},
{
number: 4,
name: "EE",
}
]
I know a few ways that can do it, but none of them looks pretty.
Post some of my thoughts here:
To insert I did this.arr.splice(1, 0, newObj), then tried to loop through this.arr, for any index greater than 2, I did ++number, this works, but ugly.
To insert I did this.arr.splice(1, 0, newObj), then split this.arr with let newArr = this.arr.split(2), then newArr.map(a => {...}), use splice to replace part of original arr with newArr.
New Edit:
Share some of my code here, this works, but I'd like to simply it or make it prettier if possible. Please share thoughts.
const newObj = {
number: obj.number + 1,
name: 'S',
}
this.arr.splice(obj.number, 0, newObj)
if (this.arr.length > obj.number) {
const remaining = this.arr.slice(obj.number + 1).map( (t) => ({...t, ...{number: t.number + 1}}))
this.arr.splice(newObj.number, remaining.length, ...remaining)
}
To achieve expected result , use splice(index, deleteCount, item) and use index in map to assign to number of each object to get numbers in sequence after you and delete
let arr = [
{
number: 1,
name: "A"
},
{
number: 2,
name: "e",
},
{
number: 3,
name: "EE",
}
]
let newObj = {
name: "F",
}
arr.splice(1,0, newObj)//To add
// console.log(arr)
//arr.splice(2,1) //To delete
//console.log(arr);
console.log(arr.map((v,i) => {
v.number = i;
return v
}));
Reference link for Array.splice - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
You can use ... spread syntax and slice.
operation is used to decide whether the value is to be deleted or added.
if operation is del than we slice the first from 0 upto index and than from index+1 to end of array.
else we slice from 0 upto index and add the desired value and than add the remaining part back ( from index to end )
Finally map over the values to adjust number property accordingly
let arr = [{ number: 1,name: "A"},{number: 2,name: "e", }, {number: 3,name: "EE",}]
let handleArray = (array,operation,index,value) => {
if(operation === 'del') {
return [...array.slice(0, index), ...array.slice(index+1,)].map((value,index)=> (value.number = index+1, value))
} else {
return [...array.slice(0,index),value,...array.slice(index,)].map((value,index)=> (value.number = index+1,value))
}
}
console.log(handleArray(arr,'add',0,{a:1}))
console.log(handleArray(arr,'del',0))
var testArray = [{
value: john,
count: 5
},
{
value: henry,
count: 2
},
{
value: bill,
count: 10
}]
testArray.map(function(value) {
//otherfunctions
})
So i have a function where i am already mapping through an array of objects similar to the above. I want to add a third value to the objects of rank based upon the count.
My current thought is to finish my map that i am already doing and then re sort the data based upon the count and then assign a rank based upon the the sorted position in the array. But this seems to be long winded given i am already mapping the array?
Vanilla JS:
var testArray = [{
value: john,
count: 5
},
{
value: henry,
count: 2
},
{
value: bill,
count: 10
}]
let newArray = testArray.map(function(item) {
return {
value: item.value,
count: item.count,
newProperty: 'x'
}
}).sort(function(x, z) {
return x.count - z.count;
});
ES6:
let newArray = testArray
.map(item => {
return {
...item,
newProperty: 'x'
}
}).sort((x, z) => x.count - z.count);
P.S. this is the functional way of doing this computation, should have a o(n*nlog n) time, you could do it in a o(n) with an imperative approach, but this is easier to read/understand in my opinion.
EDIT 1
After comment from author: wants to add the current count to the items (cannot think of a case where this would be necessary) but just to indulge:
let newArray = testArray
.map((item, index) => {
return {
...item,
currentCount: index
}
}).sort((x, z) => x.count - z.count);
Read more about map
I am using Lodash in my Angular project and I was wondering if there is a better way to write the following code:
$scope.new_arr = _.map(arr1, function(item){
return _.assign(item, {new_id: _.find(arr2, {id: item.id})});
});
$scope.new_arr = _.filter($scope.new_arr, function (item) {
return item.new_id !== undefined;
});
I am trying to combine values from one array to same objects in other array, and I want to ignore the objects that not appear in both arrays (it is something like join or left outer join in the sql language).
Here is a fiddle with an example of this code: Click me!
i think is better to use chaining
$scope.new_arr = _.chain(arr1)
.map(function(item) {
return _.merge(
{}, // to avoid mutations
item,
{new_id: _.find(arr2, {id: item.id})}
);
})
.filter('new_id')
.value();
https://jsfiddle.net/3xjdqsjs/6/
try this:
$scope.getItemById = (array, id) => {
return array.find(item => item.id == id);
};
$scope.mergeArrays = () => {
let items_with_ids = arr1.filter(item => !_.isNil($scope.getItemById(arr2,item.id)));
return items_with_ids.map(item => _.assign(item, {new_id: $scope.getItemById(arr2,item.id)}));
};
The answers provided here are all runtime of O(n^2), because they first run an outer loop on the first array, with an inner loop on the second array. You can instead run this in O(n). First, create a hashmap of all the ids in arr2 in a single loop; this will allow us an order 1 lookup. In the second loop on arr1, check this hashmap to determine if those items exist with O(n). Total Complexity is n + n = 2n, which is just O(n).
// provision some test arrays
var arr1 = [
{
id: 2
},
{
id: 4
},
{
id: 6
}
]
var arr2 = [
{
id: 3
},
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
// First, we create a map of the ids of arr2 with the items. Complexity: O(n)
var mapIdsToArr2Items = _.reduce(arr2, function(accumulator, item) {
accumulator[item.id] = item;
return accumulator;
}, {});
// Next, we use reduce (instead of a _.map followed by a _.filter for slightly more performance.
// This is because with reduce, we loop once, whereas with map and filter,
// we loop twice). Complexity: O(n)
var combinedArr = _.reduce(arr1, function(accumulator, item) {
// Complexity: O(1)
if (mapIdsToArr2Items[item.id]) {
// There's a match/intersection! Arr1's item matches an item in arr 2. Include it
accumulator.push(item);
}
return accumulator;
}, []);
console.log(combinedArr)
You could first make a Map with arr1 and then map the items of arr2 with the properties of arr1.
var arr1 = [{ id: 1, title: 'z' }, { id: 2, title: 'y' }, { id: 3, title: 'x' }, { id: 4, title: 'w' }, { id: 5, title: 'v' }],
arr2 = [{ id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'd' }, { id: 5, name: 'e' }],
map = new Map(arr1.map(a => [a.id, a])),
result = arr2.map(a => Object.assign({}, a, map.get(a.id)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/