Good ways to convert 2d array to object - javascript

With ES7 ES2017 (ES8) I can convert an object to an array of arrays like this:
let obj = { a: '1', b: '2', c: '3' }
let arr = Object.entries(obj)
// [ [ 'a', '1' ], [ 'b', '2' ], [ 'c', '3' ] ]
But what if I want to go the other way around? I could use a for-loop, or the reduce-method of the Array like below, but is there a more direct approach?
let arr =[ [ 'a', '1' ], [ 'b', '2' ], [ 'c', '3' ] ]
let obj = arr.reduce((o, x)=>{o[x[0]]=x[1];return o}, {})
// { a: '1', b: '2', c: '3' }
A use case: I wanted to convert a string like "a=1;b=2;c=3" to an object like { a: '1', b: '2', c: '3' } and ended up with something like
"a=1;b=2;c=3"
.split(";")
.reduce((o, x)=>{
let parts=x.split("=")
o[parts[0]]=parts[1]
return o
}, {});
Edit: As pointed out in comments, the reduce syntax could be simplified (or shortened, at least) to:
arr.reduce((o, [k, v])=>(o[k]=v, o), {})
...or written with Object.assign like this:
arr.reduce((o, [k, v])=>Object.assign(o, {[k]: v}), {})
Either way, the answer to the question, according to the comments, is no. This is the best approach we've got.
Edit, Jan 2023: .fromEntries() is in stage 4 since 2019, and now supported in all modern browsers.

Do you want some one-line unentries function?
const detries = entries => entries.reduce((result, [key, value]) => Object.assign(result, {[key]: value}), {});
const entries = Object.entries({a: 1, b: 2, c: 3});
const detried = detries(entries);
console.info("ENTRIES", entries);
console.info("DETRIED", detried);
Edit: Object.fromEntries proposal.
You will probably be able to use this one instead.
Object.fromEntries is proposed to perform the reverse of Object.entries: it accepts an iterable of key-value pairs and returns a new object whose own keys and corresponding values are given by those pairs.
Object.fromEntries([["apple", 0], ["banana", 1]]) // {apple: 0, banana: 1}

Related

Transforming array of objects containing arrays into a unique object lookup with lodash

How would you transform codes into result using lodash ?
const codes = [
{a: 'aa', b: [ 8518 ], c: [ '2146' ]},
{a: 'bb', b: [ 120123 ], c: [ 'D835', 'DD3B' ]},
{a: 'cc', b: [ 168, 532 ], c: [ '00A8' ] }
]
const result = [
{a: 'aa', b:8518, c:'2146'},
{a: 'bb', b:120123, c:'D835'},
{a: 'bb', b:120123, c:'DD3B'},
{a: 'cc', b:168, c:'00A8'},
{a: 'cc', b:532, c:'00A8'}
]
Use nested Array.flatMap() calls (or lodash's _.flatMap()) to iterate the main array, and the b objects , and Array.map() to iterate the c arrays to create an array of objects with all the combinations of b and c values:
const codes = [{ a: 'aa', b: [8518], c: ['2146'] }, { a: 'bb', b: [120123], c: ['D835', 'DD3B'] }, { a: 'cc', b: [168, 532], c: ['00A8'] }]
const result = codes.flatMap(o =>
o.b.flatMap(b =>
o.c.map(c => ({ ...o, b, c }))
)
)
console.log(result)
With plain Javascript you could separate the problem into two task, one for build a cartesian product of an array with a nested structure and another for mapping the part result of the cartesian products.
getCartesian is a recursive function which separates all key/value pairs and build a new cartesian product by iterating the values, if an array with objects call getCartesian again and build new objects.
function getCartesian(object) {
return Object.entries(object).reduce((r, [k, v]) => {
var temp = [];
r.forEach(s =>
(Array.isArray(v) ? v : [v]).forEach(w =>
(w && typeof w === 'object' ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [k]: x }))
)
)
);
return temp;
}, [{}]);
}
var input = [{ a: 'aa', b: [8518], c: ['2146'] }, { a: 'bb', b: [120123], c: ['D835', 'DD3B'] }, { a: 'cc', b: [168, 532], c: ['00A8'] }],
cartesian = input.flatMap(o => getCartesian(o));
console.log(cartesian);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Duplicating an array's elements using functional programming

I'm trying to duplicate each element in an array, but using functional style.
I have this currently:
["a", "b", "c"]
And I'm getting this:
["a","a","b","b","c","c"]
So far I have tried the following, mapping each element to an array, then using flat() to get a 1d array. Is there a cleaner way because it feels like I'm abusing map and flat.
["a", "b", "c"].map(item => [item, item]).flat();
Is there a better way to do this?
I was trying to provide a example as simple as possible but left some details out. The real input is not sorted because elements are not comparable.
It's something like:
[
{
a:"a"
b:"b"
},
{
c: 1
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
]
The duplicated result should be something like this, where duplicated elements are next to each other:
[
{
a:"a"
b:"b"
},
{
a:"a"
b:"b"
},
{
c: 1
d: 2
},
{
c: 1
d: 2
},
{
apple: {},
sellers: ["me", "her"]
},
{
apple: {},
sellers: ["me", "her"]
}
]
Array.reduce is semantically the appropriate method here: take an object (in this case an array) and return an object of a different type, or with a different length or shape (note: edited to use Array.push for faster performance per #slider suggestion):
EDIT: I've edited my answer to reflect OP's updated input data. Note also, that this solution is cross-browser and NodeJS compatible without requiring transpilation.
let data = [
{
a:"a",
b:"b",
},
{
c: 1,
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
];
let result = data
.reduce((acc, el) => {
acc.push(el, el);
return acc;
}, []);
console.log(JSON.stringify(result, null, 2));
Otherwise you could map each element, duplicating it, then combine them:
let data = [
{
a:"a",
b:"b",
},
{
c: 1,
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
];
let result = data.map(item => [item, item]).reduce((acc, arr) => acc.concat(arr));
console.log(JSON.stringify(result, null, 2));
As mentioned in other answers here, either of these approaches have the advantage of not requiring the original array to have been sorted.
You can use the function reduce and concatenate the same object on each iteration.
let array = ["a", "b", "c"],
result = array.reduce((a, c) => a.concat(c, c), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would recommend Array.prototype.flatMap -
const twice = x =>
[ x, x ]
console .log
( [ 'a', 'b', 'c' ] .flatMap (twice) // [ 'a', 'a', 'b', 'b', 'c', 'c' ]
, [ 1, 2, 3, 4, 5 ] .flatMap (twice) // [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ]
)
flatMap is useful for all kinds of things -
const tree =
[ 0, [ 1 ], [ 2, [ 3 ], [ 4, [ 5 ] ] ] ]
const all = ([ value, ...children ]) =>
[ value ] .concat (children .flatMap (all))
console .log (all (tree))
// [ 0, 1, 2, 3, 4, 5 ]
really cool things -
const ranks =
[ 'J', 'Q', 'K', 'A' ]
const suits =
[ '♡', '♢', '♤', '♧' ]
console .log
( ranks .flatMap (r =>
suits .flatMap (s =>
[ [ r, s ] ]
)
)
)
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]
flatMap is just a specialised Array.prototype.reduce and is easy to implement in environments where Array.prototype.flatMap is not already supported -
const identity = x =>
x
const flatMap = (xs = [], f = identity) =>
xs .reduce ((r, x) => r . concat (f (x)), [])
const ranks =
[ 'J', 'Q', 'K', 'A' ]
const suits =
[ '♡', '♢', '♤', '♧' ]
console.log
( flatMap (ranks, r =>
flatMap (suits, s =>
[ [ r, s ] ]
)
)
)
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]
You could just do this:
var arr = ["a", "b", "c"];
arr = arr.concat(arr).sort();
This is one of the simplest methods to do what you are asking to do.
The simplest solution is to use flatMap():
const source = ["a", "b", "c"];
const result = source.flatMap(item => [item, item]);
[ 'a', 'a', 'b', 'b', 'c', 'c' ]
A little bit of classic:
let source = ["a", "b", "c"];
const originalLength = source.length;
for(let i = 0; i <= originalLength + (originalLength - 2); i++) {
source.splice(i, 0, source[i++]);
}
[ 'a', 'a', 'b', 'b', 'c', 'c' ]

Array.reduce - strange behaviour with objects

myArr = ['a', 'b', 'c' ];
myArr.reduce((obj, val) => ({ ...obj, [val]: val }));
Based on my understanding, you would expect the reduce to return { a: 'a', b: 'b', c: 'c' }
What we actually get back is { 0: 'a', b: 'b', c: 'c' }
I tried putting a log inside to see what is going on with that first item, but the output is:
b
c
{0: "a", b: "b", c: "c"}
So now the behaviour is even more strange because we don't get any logs for the first val iteration.
let myArr = ['a', 'b', 'c' ];
let result = myArr.reduce((obj, val) => ({ ...obj, [val]: val }), {});
console.log(result);
You missed the initial value to reduce. When no initial value is supplied, reduce pops off the first element for this purpose (and indeed no iteration happens; because 1+2+3 has two additions, not three, unless you specify we have to start from 0).
The first element is "a", which deceptively becomes the misnamed obj; when you execute {..."a", b: "b"}, you will see that ..."a" expanded in the object context will yield the characters' index as the key; thus, ..."a" is equivalent to ...{0: "a"}.
Good thing you didn't try with myArr = ['hello', 'world'] - that'd be much more of a surprise, I imagine (the result from that being {0: "h", 1: "e", 2: "l", 3: "l", 4: "o", world: "world"}).

Add consecutive alphabet letters as object keys

I want to create an object with the given structure:
const questions = [
{
id: '1',
instruction: 'Question1',
options: {
'a': 'SomeText1',
'b': 'SomeText2',
'c': 'SomeText3'
},
correct: ['c']
},
{
id: '2',
instruction: 'Question2',
options: {
'a': 'SomeText1',
'b': 'SomeText2',
'c': 'SomeText3',
'd': 'SomeText4'
},
correct: ['a']
},
{
id: '3',
instruction: 'Question3,
options: {
'a': 'SomeText1',
'b': 'SomeText2'
},
correct: ['b']
}
]
I have an arrays containing necessary information to fill this object and create it using .map() .
const questions =
[ 'Question1',
'Question2',
'Question3'
]
const answers =
[
'Answer1',
'Answer2',
'Answer3'
]
const options = [
[ 'Option1', 'Option2', 'Option3' ],
[ 'Option4', 'Option5', 'Option6' ],
[ 'Option7', 'Option8', 'Option9' ]
]
function toJson()
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => (
{
id: index + 1,
instruction: question,
options: Object.assign({}, options[index]),
correct: answers[index]
}
))
}
I have only problem with options key. As You see I want to have a letters as keys, depending on how many answers question has.
This function gives me numbers as keys when I use Object.assign(), and I don't know how to replace them with letters from alphabet array.
EDIT:
So the solution for the options key in desired object is:
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),
Now I'm able to create an object with consecutive alphabet letters with assigned answer.
options[index] returns an array. It contains values by index. By passing it to Object.assign, you add all values by their array index as a string: "0", "1", etc.
If we map the array in to a list of { "a": option } first, and spread the result in to the Object.assign call, we can change those indexes to the letters you want:
const questions =
[ 'Question1',
'Question2',
'Question3'
]
const answers =
[
'Answer1',
'Answer2',
'Answer3'
]
const options = [
[ 'Option1', 'Option2', 'Option3' ],
[ 'Option4', 'Option5', 'Option6' ],
[ 'Option7', 'Option8', 'Option9' ]
]
function toJson() {
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => (
{
id: index + 1,
instruction: question,
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),
correct: answers[index]
}
));
return json;
}
console.log(toJson());
I suggest using something like zip and objectFromPairs (both snippets from 30secondsofcode, a project/website I am a maintainer of). From the website:
zip
Creates an array of elements, grouped based on the position in the original arrays.
Use Math.max.apply() to get the longest array in the arguments. Creates an array with that length as return value and use Array.from() with a map-function to create an array of grouped elements. If lengths of the argument-arrays vary, undefined is used where no value could be found.
objectFromPairs
Creates an object from the given key-value pairs.
Use Array.reduce() to create and combine key-value pairs.
The only extra step I took was to trim each zipped array to the length of options[index].
const questions = ['Question1',
'Question2',
'Question3'
]
const answers = [
'Answer1',
'Answer2',
'Answer3'
]
const options = [
['Option1', 'Option2', 'Option3'],
['Option4', 'Option5', 'Option6'],
['Option7', 'Option8', 'Option9']
]
const zip = (...arrays) => {
const maxLength = Math.max(...arrays.map(x => x.length));
return Array.from({
length: maxLength
}).map((_, i) => {
return Array.from({
length: arrays.length
}, (_, k) => arrays[k][i]);
});
};
const objectFromPairs = arr => arr.reduce((a, [key, val]) => ((a[key] = val), a), {});
function toJson() {
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => ({
id: index + 1,
instruction: question,
options: objectFromPairs(zip(alphabet, options[index]).slice(0, options[index].length)),
correct: answers[index]
}))
console.log(json);
}
toJson();
the below should work (i believe)
options: alphabet.reduce((acc, letter, i) => {
let option = options[index][i] || 'DefaultText' + i;
acc[letter] = option;
return acc;
}, {})
Edit: Corrected typos
EDIT:
So the solution for the options key in desired object is:
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),

add up all value from array of objects's key after parseFloat()

so I have an array of objects inside like this:
let ary = [
{
a : 'something',
b: '2',
c: 'something'
},
{
a : 'something',
b: '2',
c: 'something'
},
{
a : 'something',
b: '2',
c: 'something'
}
]
I need to add up all key b's value on each object so I first change them to number with parseFloat like this:
ary.forEach( item => {
item.b = parseFloat(item.b)
})
Next I use reduce() inside a .map function.
let total = ary.map((item, index) => {
return ary.reduce((a, b) => ({x: a.b + b.b}))
})
but I did not get what I want, it return an array of objects with both same total value.
array = [
{x : total},
{x : total}
]
How should I get just single total value? like
total = 2+2+2 and each 2 is from the b key in ary.
You can try with:
const total = ary.reduce((sum, item) => (sum + parseFloat(item.b)), 0);
With one loop you'll invoke parseFloat and summ all b properties.
You could chain some methods by separating the value of the objects, take numbers and add all values with reduce.
var array = [{ a: 'something', b: '2', c: 'something' }, { a: 'something', b: '2', c: 'something' }, { a: 'something', b: '2', c: 'something' }],
sum = array
.map(({ b }) => b)
.map(Number)
.reduce((a, b) => a + b);
console.log(sum);

Categories