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 }))
),
Related
at least one of the value's in the array1 obj "data" array matches the "data" array in array2 object
> array1
array1 = [
{
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
},
{
id: '3',
name: 'voon',
data: ['p4']
}
];
> array2
array2 = [
{
id: '1',
name: 'fgr',
data:['p1','p2','p3']
},
{
id: '2',
name: 'gone',
data:['p1','p2','p3']
}
]
output: {
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
}
With the below assumption:
array1 has objects each of which always have prop data which is an array
array2 also has objects and these objects will always have prop data which is an array as well
the desired objective is to generate an array with objects from array1 whose data have at least one common element with any of the data of array2 objects.
this solution may work:
const findNeedleInHaystack = (needle = array1, haystack = array2) => (
needle.reduce((acc, itm) => (
haystack
.map(x => x.data)
.flat()
.some(x => itm.data.includes(x))
? [...acc, itm]
: [...acc]
), [])
);
Explanation
iterate over the needle array (ie, array1) using .reduce
use .map on haystack array (ie, array2) to separate the data from each object
use .flat() to transform the 2-dimensional resulting array into 1-dimension
use .some to see if any element in the 1-dimensional array is also present in itm's data array
if found, concatenate itm to the result (using ... spread operator on acc - the aggregator/accumulator)
if not found, skip concatenating itm.
Code Snippet
const array1 = [
{
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
},
{
id: '3',
name: 'voon',
data: ['p4']
}
];
const array2 = [
{
id: '1',
name: 'fgr',
data:['p1','p2','p3']
},
{
id: '2',
name: 'gone',
data:['p1','p2','p3']
}
];
const findNeedleInHaystack = (needle = array1, haystack = array2) => (
needle.reduce((acc, itm) => (
haystack.map(x => x.data).flat().some(x => itm.data.includes(x))
? [...acc, itm]
: [...acc]
), [])
);
console.log(findNeedleInHaystack());
I have the following data array:
const data = [
{
value: [
'a',
'b',
'a',
'a'
]
},
{
value: [
'c',
'c',
'd',
'c'
]
}
];
So there's is 4 combination here based on index:
combination 1 : a - c (index 0 in each value arrays)
combination 2 : b - c (index 1 in each value arrays)
combination 3 : a - d (index 2 in each value arrays)
combination 4 : a - c (index 3 in each value arrays)
As you can see the first and the last combinations are the same, so i want to remove the second occurrence from each array, the result should be:
[
{
value: [
'a',
'b',
'a'
]
},
{
value: [
'c',
'c',
'd'
]
}
]
You can zip the values arrays from both objects to form an array which looks like:
["a-c", "b-c", ...]
As these are now strings, you can turn this array into a Set using new Set(), which will remove all duplicate occurrences. You can then turn this set back into an array which you can then use .reduce() on to build you array of objects from. For each value you can obtain the list of values by using .split() on the '-', and from that, populate your reduced array.
See example below:
const data = [{ value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c' ] } ];
const unq = [...new Set(
data[0].value.map((_,c)=> data.map(({value})=>value[c]).join('-'))
)];
const res = unq.reduce((acc, str) => {
const values = str.split('-');
values.forEach((value, i) => acc[i].value.push(value));
return acc;
}, Array.from({length: data.length}, _ => ({value: []})))
console.log(res);
Limitations of the above method assume that you won't have a - character as your string value. If this is an issue, you can consider using a different delimiter, or find unique values within your array using .filter() instead of a Set.
You could save a lookup object for unique pairs of value based with index
Given your input is, below solution could help you
const data = [
{
value: ["a", "b", "a", "a"],
},
{
value: ["c", "c", "d", "c"],
},
]
const lookup = {}
data[0].value.forEach((_, index) => {
lookup[`${data[0].value[index]}-${data[1].value[index]}`] = true
})
const res = Object.keys(lookup).reduce(
(acc, key) => {
const [val1, val2] = key.split("-")
acc[0].value.push(val1)
acc[1].value.push(val2)
return acc
},
[{ value: [] }, { value: [] }]
)
console.log(res)
Below is a two step solution with a generator function and a single pass.
const data = [ { value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c', ] } ];
const zipDataValues = function* (data) {
const iterators = data.map(item => item.value[Symbol.iterator]())
let iterations = iterators.map(iter => iter.next())
while (iterations.some(iteration => !iteration.done)) {
yield iterations.map(iteration => iteration.value)
iterations = iterators.map(iter => iter.next())
}
}
const filterOutDuplicateCombos = function (values) {
const combosSet = new Set(),
resultData = [{ value: [] }, { value: [] }]
for (const [valueA, valueB] of values) {
const setKey = [valueA, valueB].join('')
if (combosSet.has(setKey)) {
continue
}
combosSet.add(setKey)
resultData[0].value.push(valueA)
resultData[1].value.push(valueB)
}
return resultData
}
console.log(
filterOutDuplicateCombos(zipDataValues(data))
) // [ { value: [ 'a', 'b', 'a' ] }, { value: [ 'c', 'c', 'd' ] } ]
Here is a reference on generators and iterators
Filter combinations + sorting by the first occurrence:
const data = [{
value: ['a', 'b', 'a', 'a']
},{
value: ['c', 'c', 'd', 'c']
}];
var res = {}, i, t;
for (i = 0; i < data[0].value.length; ++i) {
res[data[0].value[i]] = res[data[0].value[i]] || {};
res[data[0].value[i]][data[1].value[i]] = true;
}
data[0].value = [];
data[1].value = [];
for (i in res) {
for (t in res[i]) {
data[0].value[data[0].value.length] = i;
data[1].value[data[1].value.length] = t;
}
}
console.log(data);
This question already has answers here:
How to sort in specific order - array prototype(not by value or by name)
(4 answers)
Closed 3 years ago.
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
Trying to figure out the most efficient way to sort the objects array by name using the manual order array.
Here is a quick use of sort plus indexOf.
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const sortedObjects = objects.sort((o1, o2) => order.indexOf(o1.name) - order.indexOf(o2.name));
console.log(sortedObjects);
With cached indices:
const order = ['b', 'c', 'a'].reduce((acc, elt, index) => (acc[elt] = index, acc), {});
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const sortedObjects = objects.sort((o1, o2) => order[o1.name] - order[o2.name]);
console.log(sortedObjects);
You can cache the indices using Object.entries() and Object.fromEntries() to re-arrange the order object into a lookup table:
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const lut = Object.fromEntries(
Object.entries(order).map(entry => entry.reverse())
);
objects.sort((a, b) => lut[a.name] - lut[b.name]);
console.log(objects);
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' ]
I'd love an explanation as to why the result of this function don't match my expectation.
const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]
async function(domain) {
let matrix = []
objects.forEach((object) => {
numbers.forEach((number) => {
object.number = number
matrix.push(object)
})
})
return matrix
}()
Actual
The result, once the promise is resolved returns:
[
{ f: 'a', number: 2 },
{ f: 'a', number: 2 },
{ f: 'b', number: 2 },
{ f: 'b', number: 2 }
]
Expected
But, my expectation is that it would return:
[
{ f: 'a', number: 1 },
{ f: 'a', number: 2 },
{ f: 'b', number: 1 },
{ f: 'b', number: 2 }
]
One of the things that puzzles me the most is that if I console.log the value of object just before I push, it logs out each of the objects in my expected result.
Because objects are passed by reference and you are modifying original object, instead you can push new object.
const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]
let matrix = []
objects.forEach((object) => {
numbers.forEach((number) => {
matrix.push({...object, number})
})
})
console.log(matrix)
Use Array.flatMap() to iterate the objects, with a nested Array.map() to iterate the numbers and return an array of new objects with the relevant number. The flatMap will flatten the arrays produced by the map to a single array:
const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]
const matrix = objects.flatMap(o => // map the objects and flatten
numbers.map( // map the numbers
number => ({ ...o, number }) // return a new object with the number
)
);
console.log(matrix);
Old answer:
Use nested Array#map to iterate the objects and the numbers, and Object#assign to create new objects that include the relevant number. Flatten resulting nested arrays by using the spread syntax in Array#concat:
const numbers = [ 1, 2 ]
const objects = [{f: 'a'}, {f: 'b'}]
const matrix = [].concat(... // flatten the nested arrays
objects.map((o) => // map the objects
numbers.map( // map the numbers
(number) => Object.assign({}, o, { number }) // return a new object with the number
)
));
console.log(matrix);
#Nenad is correct. You can also do it with reduce and map.
const numbers = [ 1, 2 ];
const objects = [{f: 'a'}, {f: 'b'}];
const matrix = numbers.reduce((acc, nxt) => {
const numberedObjs = objects.map(obj => ({...obj, number:nxt}));
acc.push(...numberedObjs);
return acc;
}, []);