Loop over object es6 - javascript

I have an object which looks like this:
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
}
I want to loop over this object and this and log out each key name e.g. eyes for the amount of the value.
this would result in:
head
eyes
eyes
arms
arms
legs
legs
legs
Currently I have this solution but it feels like it could be done more neatly and readible.
Object.keys(object)
.map(key => {
return [...Array(object[key])].map( (_, i) => {
return console.log(key)
})
Any suggestions?

You could use Object.entries() and map() method and return new array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = [].concat(...Object.entries(object).map(([k, v]) => Array(v).fill(k)))
console.log(res)
Or you could use reduce() with spread syntax in array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = Object
.entries(object)
.reduce((r, [k, v]) => [...r, ...Array(v).fill(k)], [])
// Or you can use push instead
// .reduce((r, [k, v]) => (r.push(...Array(v).fill(k)), r), [])
console.log(res)

Object.entries(object)
.forEach(([key, times]) => console.log((key + "\n").repeat(times)));
One may use String.prototype.repeat...

"...it feels like it could be done more neatly and readible."
Recursion makes it pretty clean and understandable.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
if (v) {
console.log(k);
f([k, --v]);
}
})
You can rearrange things a bit if you know the value will always be greater than 0.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
console.log(k);
if (--v) f([k, v]);
})

Related

curr is undefined in reduce on array of objects

As an input, I have an array of objects , each one of these objects has a number of properties,
I would like to have an output that is also an array of objects with the same properties unchanged except for one property that I like to be changed into an accumulative value.
input example :
let input_array = [{ a:'a',b:'b',c: 1},{a:'d',b:'e',c:2},{a:'g',b:'h',c: 3}];
output example
let output_array = [{ a:'a',b:'b',c: 1},{a:'d',b:'e',c:3},{a:'g',b:'h',c: 6}];
Here 's what I tried :
let output_array = [];
input_array.reduce((acc, curr) => {
output_array.push({
a: curr.a,
b: curr.b,
c : acc.c + curr.c
})
},0);
I keep getting c NaN and acc undefined .
In this case I'd prefer to .map one array to the other, and keep a variable with the accumulated c:
const input = [{ a:'a',b:'b',c: 1},{a:'d',b:'e',c:2},{a:'g',b:'h',c: 3}];
const output = input.map(((acc) => ({ c, ...rest }) => ({ ...rest, c: acc += c }))(0));
console.log(output);
For sure your solution also works if you actually return the new accumulator from the reducer:
const input = [{ a:'a',b:'b',c: 1},{a:'d',b:'e',c:2},{a:'g',b:'h',c: 3}];
const output = [];
input.reduce((acc, curr) => {
acc = acc + curr.c;
output.push({ ...curr, c: acc });
return acc;
},0);
console.log(output);

How can I do total count of values accordingly selected feild in array

I have a array of objects, for exemple:
let arr = [
{title:apple,quantity:2},
{title:banana,quantity:3},
{title:apple,quantity:5},
{title:banana,quantity:7}
];
array containe many same objects, and i want recived array with uniqe object :
let result = [
{title:apple,quantity:7},
{title:banana,quantity:10}
]
How can I do this?
You can iterate over your array and filter out all the object with same title. Then use reduce to add all the quantity and return a new object. Code is below,
let newArr = [];
arr.forEach((currentObj) => {
const alreadyExists = newArr.findIndex(item => currentObj.title === item.title) > -1;
if(!alreadyExists) {
const filtered = arr.filter(item => item.title === currentObj.title);
const newObject = filtered.reduce((acc, curr) => { return {...acc, quantity: acc.quantity += curr.quantity}}, {...currentObj, quantity: 0})
newArr.push(newObject);
}
})
console.log(newArr);
This is done on a phone so may have some typos but the gist is there:
const resultObj = arr.reduce((acc,curr) =>{
acc[curr.title] = acc[curr.title]== undefined? curr.quantity: acc[curr.title] + curr.quantity
return acc
},{})
const resultArr = Object.entries(resultObj).map([key,value]=>({title:key,quantity:value}))
You could do that in "one line" using arrow function expressions but it won't be very readable unless you know what's happening inside:
let arr = [
{title: "apple",quantity:2},
{title: "banana",quantity:3},
{title: "apple",quantity:5},
{title: "banana",quantity:7}
];
let newArr = [...arr.reduce((acc, {title, quantity}) =>
(acc.set(title, quantity + acc.get(title) || 0), acc), new Map())
].map(([title, quantity]) => ({title, quantity}));
console.log(newArr);
So basically the first part is the reduce method:
arr.reduce((acc, {title, quantity}) =>
(acc.set(title, quantity + acc.get(title) || 0), acc), new Map())
That will returns a Map object, where each title is a key (e.g. "apple") and the quantity is the value of the key.
At this point you have to convert the Map object into an array again, and you do it using the spread syntax.
After you got an array back, you will have it in the following form:
[["apple", 7], ["banana", 10]]
But that is not what you want yet, not in this form, so you have to convert it using the array's map method:
<array>.map(([title, quantity]) => ({title, quantity}))
To keep it concise it uses the destructuring assignment

Is there a `.map` like function for objects? To create a new object with the same keys

With an array we can use the .map method to create a new map with the same array structure (ie. the same number of elements).
eg.
const array = [2, 4, 6];
const newArray = array.map(v => v *2); //[4, 8, 12];
I believe in functional programming this makes the array object what is called a functor.
With objects I want to do something similar - I want to create a new object with the same structure (same keys), as well as the same functions etc as the original object.
eg.
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = obj.map(v => v *2); // {foo: 4, bar: 8, biz: 12}
The way I'm currently doing this is by using Object.entries and .reduce:
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = Object.entries(obj).reduce((acc,cur) => {
return {
...acc,
[cur[0]]: cur[1] * 2
}
}, {});
console.log(newObj);
I'm wondering - is there currently an object method that would let me do this that I'm missing? Or a proposal to add one?
The map function for objects is easy enough to define.
Object.defineProperty(Object.prototype, "map", {
value(mapping) {
const oldEntries = Object.entries(this);
const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]);
return Object.fromEntries(newEntries);
}
});
const obj = { foo: 2, bar: 4, baz: 6 };
const result = obj.map(x => 2 * x);
console.log(result);
Note that this is different from kornieff's objMap function because the mapping function can't access or change the key. Hence, it's the correct implementation of the Functor type class for objects.
As a bonus, let's implement some other useful type classes for objects. First, the Representable type class.
Object.tabulate = table => new Proxy({}, {
get: (_, key) => table(key)
});
const object = Object.tabulate(key => {
if (key === "foo") return 10;
if (key === "bar") return 20;
return 30;
});
console.log(object.foo); // 10
console.log(object.bar); // 20
console.log(object.baz); // 30
The tabulate function can be used to define various useful type classes such as Monad and Comonad. For example, let's implement the Distributive type class. I'll leave the implementation of the other type classes as an exercise for the reader.
Object.tabulate = table => new Proxy({}, {
get: (_, key) => table(key)
});
const distribute = functor => Object.tabulate(key =>
functor.map(object => object[key]));
const result1 = distribute([ { foo: 10, bar: 20 }, { foo: 30, bar: 40 } ]);
console.log(result1.foo); // [ 10, 30 ]
console.log(result1.bar); // [ 20, 40 ]
Object.defineProperty(Object.prototype, "map", {
value(mapping) {
const oldEntries = Object.entries(this);
const newEntries = oldEntries.map(([key, val]) => [key, mapping(val)]);
return Object.fromEntries(newEntries);
}
});
const result2 = distribute({ a: { foo: 10, bar: 20 }, b: { foo: 30, bar: 40 } });
console.log(result2.foo); // { a: 10, b: 30 }
console.log(result2.bar); // { a: 20, b: 40 }
Note that distribute expects an object that has a map method, which is why we defined the map method on Object.prototype. However, we could have used the Yoneda lemma to get around this restriction.
There is no native map for objects, but can easily make one. I think entries is the simplest.
const obj = { foo: 2, bar: 4, biz: 6 }
const objMap = (obj, fn) =>
Object.fromEntries(
Object.entries(obj).map(
([k, v], i) => [k, fn(v, k, i)]
)
)
console.log(
objMap(obj, v => v * 2),
objMap(obj, (v, k) => `${k}-${v}`),
objMap(obj, (v, _, i) => v * i)
)
One way to do it:
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const x = Object.keys(obj).map(o => {
return {[o]: obj[o] * 2}
})
console.log(JSON.stringify(x, null, 2))
Another way to do it using .reduce() is like this, making use of destructuring to read a little bit easier ( I think )
const obj = {
foo: 2,
bar: 4,
biz: 6
};
const newObj = Object.entries(obj).reduce(
(acc, [key, value]) => ({...acc, [key]: value * 2}), {}
);
console.log(newObj)
If you fancy experimenting a bit of functional apis in your code,
then you could consider to use a functional library (such as Ramda),
It doesn't only introduce the functor api for objects (map), but also other cool stuff!
const input = { a: 1, b: 44, c: 77 };
const doubled = R.map(R.multiply(2), input);
console.log(
`doubling input`,
doubled,
);
// and a lot more...
const odds = R.filter(R.modulo(R.__, 2), input);
console.log(
`filtering odds only of input`,
odds,
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
you can use the function mapValues of lodash
const array = [2, 4, 6];
_.mapValues(array, v => v * 2)

Two object arrays: merge with same key

I have two object arrays. I want to merge with key with value
var a = [{"fit":["34","32","30","28"],"size":["x"]}]
var b = [{"size":["s","m","xl"],"fit":["36"]}]
Expected Output should be
Obj=[{"fit":["34","32","30","28","36"],"size":["x,"s","m","xl"]}]
My Code is
let arr3 = [];
b.forEach((itm, i) => {
arr3.push(Object.assign({}, itm, a[i]));
});
alert(JSON.stringify(arr3))
it gives [{"size":["x"],"fit":["34","32","30","28"]}] which wrong.
Use Array.reduce().
// Combine into single array (spread operator makes this nice)
const myArray = [...a, ...b];
// "reduce" values in array down to a single object
const reducedArray = myArray.reduce((acc, val) => {
return [{fit: [...acc.fit, ...val.fit], size: [...acc.size, ...val.size]}];
});
Edit: if you want the reducer to merge objects regardless of what keys and fields it has then you can do by iterating over the keys of the objects and merging them dynamically:
const reducedArray = myArray.reduce((acc, val) => {
const returnObject = {};
for (const eaKey in acc) {
returnObject[eaKey] = [...acc[eaKey], ...val[eaKey]];
}
return [returnObject];
});
If the fields of the objects aren't guaranteed keys then you will need to get even more dynamic in detecting the type of merge and how to do it, but it's possible and I will leave that as an exercise for you to figure out. :)
Note that if there are duplicate values in each of the "fit" and "size" arrays, they will not be deduplicated. You'd have to do that manually as a separate step either with extra logic in the reduce function or afterwards.
combine a and b in a single array then reduce it starting with an array having an object with empty fit and size arrays:
var a = [{ fit: ["34", "32", "30", "28"], size: ["x"] }];
var b = [{ size: ["s", "m", "xl"], fit: ["36"] }];
var obj = [...a, ...b].reduce(
(acc, curr) => {
Object.keys(curr).forEach(k => {
acc[0][k] = [...new Set([...(acc[0][k] || []), ...curr[k]])];
});
return acc;
},
[{}]
);
console.log(obj);
You can create a combine function that takes fit and size from any two objects and merges them.
Use it as a reducer to combine everything.
let combine = ({fit, size}, {fit: fit2, size: size2}) =>
({ fit: [...fit, ...fit2], size: [...size, ...size2] });
let result = [...a, ...b].reduce(combine);
Example:
var a = [{"fit":["34","32","30","28"],"size":["x"]}, {"fit": ["10", "11"], "size":["xxxxxxxxl"]}]
var b = [{"size":["s","m","xl"],"fit":["36"]}];
let combine = ({fit, size}, {fit: fit2, size: size2}) =>
({ fit: [...fit, ...fit2], size: [...size, ...size2] });
let result = [...a, ...b].reduce(combine);
console.log(result);
If you don't want to use the keys directly you could try
const arr3 = b.reduce((carry, current, index) => {
Object.keys(current)
.forEach(key => {
Object.assign(carry, { [key]: Array.prototype.concat.call(current[key], a[index][key])});
});
return carry;
}, {});

How to push elements into array based on dynamic variable

I have an object and I want to fill an array with the object property and repeat each property a number of times, based on its value. An example:
obj = {
watches: 3
rings: 1
}
// => ['watches', 'watches', 'watches', 'rings']
Below is what I have so far. I'm having a hard time figuring how to repeat each property based on the associated value?
function arrayBuilder(obj) {
let objToArr = [];
for (let [property, value] of Object.entries(obj)) {
objToArr.push(property);
}
return objToArr;
}
console.log(arrayBuilder({watches: 3, rings: 1}));
// => [ 'watches', 'rings' ]
You were just missing an inner loop:
function arrayBuilder(obj) {
let objToArr = [];
for (let [property, value] of Object.entries(obj)) {
for(var i=0; i<value; i++){
objToArr.push(property);
}
}
return objToArr;
}
console.log(arrayBuilder({watches: 3, rings: 1}));
You can use Array.flatMap() (note the support) with Array.fill():
const obj = {
watches: 3,
rings: 1
}
const result = Object.entries(obj).flatMap(([k, v]) => Array(v).fill(k));
console.log(result);
Or Array.reduce() with array spread, if flatMap is not supported:
const obj = {
watches: 3,
rings: 1
}
const result = Object.entries(obj)
.reduce((r, [k, v]) => [...r, ...Array(v).fill(k)], []); // or r.concat(Array(v).fill(k)) instead of the spread
console.log(result);
Just add another loop:
for (let [property, value] of Object.entries(obj)) {
for(let i = 0; i < value; i++) {
objToArr.push(property);
}
}
To achieve expected result, use below option using repeat and split methods
var obj = {
watches: 3,
rings: 1
}
let result = []
for(key in obj){
result.push(...key.concat(" ").repeat(obj[key]).trim().split(" "))
}
console.log(result)
codepen - https://codepen.io/nagasai/pen/ZVzoGB?editors=0010
Go through the keys, for each key, create an array of the required length, fill the resulting array with the names of the key, and combine the resulting arrays:
function arrayBuilder(obj) {
return [].concat.apply(
[],
Object
.entries(obj)
.map(([key, value]) => new Array(value).fill(key))
)
}
console.log(arrayBuilder({watches: 3, rings: 1}))
You can do this via Array.reduce and Object.keys:
const obj = { watches: 3, rings: 1 }
const result = Object.keys(obj).reduce((acc, key) =>
[...acc, ...new Array(obj[key]).fill(key)], [])
console.log(result)
The idea is to get the keys from the object and use them to get the length of the array needed for the Array.fill. Using Array.fill you can fill the array with the same values once you have it setup with the correct length.

Categories

Resources