I need to create one single array from 3 arrays,
I already implemented the logic and it's working but i think with Array.prototype i can achieve the same with better performance
let classrooms = [
1,
2
]
let modules = [
5,
6
]
let weeks = [
7,
8
]
let avalArray = [];
classrooms.forEach(classroomId => {
modules.forEach(moduleId => {
weeks.forEach(week => {
avalArray.push({
classroomId: classroomId,
moduleId: moduleId,
week: week
});
});
});
});
This is the expected output:
[ { classroomId: 1, moduleId: 5, week: 7 },
{ classroomId: 1, moduleId: 5, week: 8 },
{ classroomId: 1, moduleId: 6, week: 7 },
{ classroomId: 1, moduleId: 6, week: 8 },
{ classroomId: 2, moduleId: 5, week: 7 },
{ classroomId: 2, moduleId: 5, week: 8 },
{ classroomId: 2, moduleId: 6, week: 7 },
{ classroomId: 2, moduleId: 6, week: 8 } ] ```
There was a request some time ago for the cartesian product back in #852 (January 2015!). As you see, it's not implemented.
As the others said, performing a simple loop without external arrays will definitely be faster. To be sure: just benchmark it. I've prepared a simple suite on perf.link and here are the results:
for-loop: 175us
for-of-loop: 175us
forEach: 290us
map-map-map-flat: 465us
flatMap: 5635us
Exact numbers are not important here, but here's one takeaway: for-of loop (not transpiled!) is one of the fastest and still very elegant:
const result = [];
for (const classroomId of classrooms)
for (const moduleId of modules)
for (const week of weeks)
result.push({classroomId, moduleId, week});
This is a more functional solution that uses Array.flatMap() with Array.map() to generate the array of objects.
However, I think that your performance should be better, since yours doesn't generate temporal arrays that are then flattened to other temporal arrays, that are flattened to a single array.
const classrooms = [1, 2]
const modules = [5, 6]
const weeks = [7, 8]
const result = classrooms.flatMap(classroomId =>
modules.flatMap(moduleId =>
weeks.map(week => ({
classroomId,
moduleId,
week
}))))
console.log(result)
You can use map and flat. It will be more compact, but it will be very slow. Use for loop for better performance.
classrooms
.map(classroomId => modules
.map(moduleId => weeks.map(week => ({classroomId, moduleId, week})))
).flat(2)
Related
I have some data like this, an array of objects:
source = [{
day: 1,
deliveries: 16,
hours: 9
}, {
day: 2,
deliveries: 19,
hours: 11
}]
Which I would like to have in this format:
source = (['day', 'deliveries', 'hours'],
['1', '16', '9'],
['2', '19', '11'])
Sort of like a table. I read up a little on mapping arrays and tried this:
const datatable = source.map(d => Array.from(Object.keys(d)))
console.log(datatable)
// [["day", "deliveries", "hours"], ["day", "deliveries", "hours"]]
And this:
const datatable = source.map(d => Array.from(Object.values(d)))
console.log(datatable)
// [[1, 16, 9], [2, 19, 11]]
Each gives me half of what I want.
I tried this:
let datatable = source.map(d => Array.from(Object.keys(d)))
let datatable2 = source.map(d => Array.from(Object.values(d)))
datatable = datatable[1]
let combined = datatable.concat(datatable2);
console.log(combined)
///["day", "deliveries", "hours", [1, 16, 9], [2, 19, 11]]
But even here the column names are not being combined correctly, and this way seems a little messy. How do I have the keys be on top (like column names would be) and the values following them?
Assuming you want source to be an array of arrays (common for table-like structures), get the keys once, then add each row array mapping the object's properties to the key for that index:
const keys = Object.keys(source[0]);
const result = [keys, ...source.map(obj => keys.map(key => obj[key]))];
Live Example:
const source = [{
day: 1,
deliveries: 16,
hours: 9
}, {
day: 2,
deliveries: 19,
hours: 11
}];
const keys = Object.keys(source[0]);
const result = [keys, ...source.map(obj => keys.map(key => obj[key]))];
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
Note that this assumes a couple of things:
The source array always has at least one object in it.
The objects in the source array all have the same set of properties.
You want the keys to be in the order in which they appear in the first object in the array. (JavaScript object properties do have a defined order now, but using that order is almost never a good idea, so you might want to do some kind of sort operation on keys.)
You are almost there. Just use [0] to get the first item to get only keys. And then combine the array using ....
let source = [{
day: 1,
deliveries: 16,
hours: 9
}, {
day: 2,
deliveries: 19,
hours: 11
}]
let ansArray = Object.keys(source[0]);
let datatable = source.map(d => Array.from(Object.values(d)))
let combined = [ansArray,...datatable];
console.log(combined);
Array.from(Object.keys(d)) and Array.from(Object.values(d)) will return the wrong data if the order of property is wrong.
const source = [{
day: 1,
deliveries: 16,
hours: 9
}, {
day: 2,
deliveries: 19,
hours: 11
}]
const datatable = source.reduce((acc, item, index) => {
if (index == 0) {
acc.push(Object.keys(item));
}
const newRow = acc[0].reduce((rowAcc, prop) => {
rowAcc.push(item[prop]);
return rowAcc;
}, []);
acc.push(newRow);
return acc;
}, [])
console.log(datatable);
with my current project, I am dealing with large streams of numerical data and transformations that have to take place on them in a data-flow-programmable fashion.
I stumbled upon the idea of transducers, which promised to solve the difficulties to handle multiple transformations on large arrays. It seems that transducers don't suit exactly for what I'm trying to solve here.
I am looking for a pattern / concept for transducers which only collect a needed amount of lookback to then process out a result. Similar to the browser version of tensorflow, reaktor, max-msp (input outputs, flow-graphs, node-based, visual-programming)
Most of these modules, should be connected to a source, but should also be able to act as a source to chain those to other modules
source ( a stream ) =[new-value]|=> module1 => module2 => ...
|=> module3 => module4 // branch off here to a new chain
From my understanding, the transducers as explained in most blogs takes the whole array, and feeds each individual values trough chosen transformers.
Yet my modules/transformers don't require so much data to work, say the example of a simple moving average with a look back of 4 steps.
I imagine that module to collect enough data until it starts it's output.
I also don't need to hold the whole array in memory, I should only deal with the exact amounts needed. Results/Outputs would be optionally stored in a database.
stream =[sends-1-value]=> module[collects-values-until-processing-starts] =[sends-one-value]=>...
It should also be possible to connect multiple sources into a module (which transducers didn't seem to provide.
Would the transducer pattern here still apply or is something else out there?
To be honest, every programmer would have an idea to make this work, yet I am asking for some established way of doing it, just like transducers came to be.
The transducer pattern certainly applies here. You can create a floating point processor with transducers paired with the right data structure. I'll give you a baseline example, with one assumption:
the stream you are working with implements Symbol.asyncIterator
Consider a simple queue
function SimpleQueue({ size }) {
this.size = size
this.buffer = []
}
SimpleQueue.prototype.push = function(item) {
this.buffer.push(item)
if (this.buffer.length > this.size) {
this.buffer.shift()
}
return this
}
SimpleQueue.prototype[Symbol.iterator] = function*() {
for (const item of this.buffer) {
yield item
}
}
Our simple queue has one method push that pushes an item into its internal buffer (an array). The simple queue is also iterable, so you could do for (const x of simpleQueue) {/* stuff */}
We'll now use our SimpleQueue in our floating point processor.
const average = iterable => {
let sum = 0, count = 0
for (const item of iterable) {
sum += item
count += 1
}
return sum / count
}
const floatingPointAverage = ({ historySize }) => {
const queue = new SimpleQueue({ size: historySize })
return item => {
queue.push(item)
const avg = average(queue)
console.log(queue, avg) // this shows the average as the process runs
return avg
}
}
floatingPointAverage takes an item, pushes it into our SimpleQueue, and returns the current average of items in the queue.
Finally, we can implement and consume our transducer
const { pipe, map, transform } = require('rubico')
const numbersStream = {
[Symbol.asyncIterator]: async function*() {
for (let i = 0; i < 1000; i++) yield i
},
}
transform(
pipe([
map(floatingPointAverage({ historySize: 4 })),
/* transducers that do stuff with floating point average here */
]),
null,
)(numbersStream)
The transducer in this case is map(floatingPointAverage({ historySize: 4 })). This transducer is courtesy of rubico, a library I wrote to solve my own async problems. I write about transducers in the context of rubico here
Your output should look like this
SimpleQueue { size: 4, buffer: [ 0 ] } 0
SimpleQueue { size: 4, buffer: [ 0, 1 ] } 0.5
SimpleQueue { size: 4, buffer: [ 0, 1, 2 ] } 1
SimpleQueue { size: 4, buffer: [ 0, 1, 2, 3 ] } 1.5
SimpleQueue { size: 4, buffer: [ 1, 2, 3, 4 ] } 2.5
SimpleQueue { size: 4, buffer: [ 2, 3, 4, 5 ] } 3.5
SimpleQueue { size: 4, buffer: [ 3, 4, 5, 6 ] } 4.5
SimpleQueue { size: 4, buffer: [ 4, 5, 6, 7 ] } 5.5
SimpleQueue { size: 4, buffer: [ 5, 6, 7, 8 ] } 6.5
SimpleQueue { size: 4, buffer: [ 6, 7, 8, 9 ] } 7.5
I have a score array containing two objects: Liga and Premier. These 2 objects are an array of a list of teams.
I was able to define the greater string when score was previously a single array of objects.
This is the demo i have reproduced where the comparison works fine.
This is the code calculating the higher value comparing the 2 objects.
const maxAverage = teams => {
return teams.map(team => {
return {
team:team,
avg: getAverage(team)
}
}).reduce((a,b)=>a.avg>b.avg?a:b).team
}
<p>Stronger Team:{maxAverage([this.state.homeCity,this.state.awayCity])</p>
The problem now is that now score is an array of the 2 object as i said and i am trying to change my function in something like
const maxAverage = (league, teams) => {
return teams.map(team => {
return {
team:team,
avg: getAverage(league,team)
}
}).reduce((a,b)=>a.avg>b.avg?a:b).team
}
I am not able to pass to my function maxAverage the parameter of one of the two leagues selected and then the 2 objects ( teams ) i want to compare.
i want to do something like this:
<p>Stronger Team:{maxAverage([this.state.selectedLeague], this.state.selectedHomeTeam,this.state.selectedAwayTeam])}
This is the other demo i have reproduced with the current situation.
Given the signature const maxAverage = (league, teams) => ..., following code would match the expected arguments (not sure about the business logic though):
maxAverage(
this.state.selectedLeague,
[this.state.selectedHomeTeam, this.state.selectedAwayTeam]
)
I looked at your second demo and I think you have two choices to get the correct team selected and you can reuse your previous getAverage method for both. Either
const maxAverage = (league, teams) => {
const currentLeague = [scores][0][league]
return teams
.map(team => {
return {
team: team,
avg: getAverage(currentLeague, team)
};
})
.reduce((a, b) => (a.avg > b.avg ? a : b)).team;
};
alternatively you could keep the original maxAverage code and change how you implement the league value eg.
<p>
Stronger Team:
{maxAverage(scores[this.state.selectedLeague], [
this.state.selectedHomeTeam,
this.state.selectedAwayTeam
])}
</p>
Why not simply extract team when selected, save in the state and use the same method used before?
What is a problem?
const scores = {'liga':[
{ day: "1", Barcelona: 1, Real: 3, Valencia: 0 },
{ day: "2", Barcelona: 4, Real: 6, Valencia: 3 },
{ day: "3", Barcelona: 7, Real: 7, Valencia: 3 },
{ day: "4", Barcelona: 7, Real: 8, Valencia: 6 }
], 'primier':[
{ day: "1", Barcelona: 1, Real: 3, Valencia: 0 },
{ day: "2", Barcelona: 4, Real: 6, Valencia: 3 },
{ day: "3", Barcelona: 7, Real: 7, Valencia: 3 },
{ day: "4", Barcelona: 7, Real: 8, Valencia: 6 }]};
const getAverage = (type, team) => {
if (isNaN(scores[type][0][team])) return null;
return scores[type].map(x => x[team]).reduce((a, c) => a + c) / scores[type].length;
};
getAverage('liga',this.state.homeCity);
src:
https://codesandbox.io/s/recharts-examples-d9qy0
I'm a Javascript beginner (more or less).
I've created a new array:
var genres = [
"metal",
"rockroll",
"funk",
"punk",
"country",
];
However, I'd like to put each genre in the array a specific number of times, not just once. I know I can just repeat each line as many times as I need, but I'm sure there's a better way.
It would be great if I could do something like this:
var genres = [
"metal" * 3,
"rockroll" * 5,
"funk" * 1,
"punk" * 0,
"country" * 4,
];
...but of course I've tried that, and it doesn't work.
Can anyone help me out? I wasn't able to find anything by googling.
Thanks!
You can build an array like this with reduce() if you start with some data structure that holds your counts and categories:
let cats = [[3, "metal"], [5, "rockroll"], [1, "funk"], [0, "punk"], [4, "country"] ]
// etc..
let arr = cats.reduce((arr, [n, cat]) => arr.concat(Array(n).fill(cat)), [])
console.log(arr)
let item = [
{
genres: "metal",
count: 3
},
{
genres: "rockroll",
count: 5
},
{
genres: "funk",
count: 1
},
{
genres: "punk",
count: 0
}
];
console.log(item);
item.map(i => {
for(let n = 0; n < i.count; n++){
console.log(i.genres);
}
});
How do you think about using the Object?
There's no built-in way to do this, but you could easily write a function to do it. For instance:
function addMultiples (input) {
const output = []
for (let key in input) {
for (let i = 0; i < input[key]; i++) {
output.push(key)
}
}
return output
}
Then you would pass in your values as an object:
console.log(addMultiples({
"metal": 3,
"rockroll": 5,
"funk": 1,
"punk": 0,
"country": 4
}).join(", "))
// prints "metal, metal, metal, rockroll, rockroll, rockroll, rockroll, rockroll, funk, country, country, country, country"
You can also use Array.from and keep your sub arrays filled. And only spread them when needed:
let cats = [[3, "metal"], [5, "rockroll"], [1, "funk"], [0, "punk"], [4, "country"]]
const filled = Array.from(cats, ([v,k]) => new Array(v).fill(k)) // fill arrays
console.log(filled.reduce((r,c) => [...r, ...c])) // spread for output
When Promise.all completes it returns an array of arrays that contain data. In my case the arrays are just numbers:
[
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
]
The array can be any size.
Currently I'm doing this:
let nums = []
data.map(function(_nums) {
_nums.map(function(num) {
nums.push(num)
})
})
Is there an alternative way of doing this? Does lodash have any functions that are able to do this?
ES2019 introduced Array.prototype.flat which significantly simplifies this to:
const nums = data.flat();
const data = [
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
];
const nums = data.flat();
console.log(nums);
Original Answer
Use reduce and concat:
data.reduce(function (arr, row) {
return arr.concat(row);
}, []);
Or alternatively, concat and apply:
Array.prototype.concat.apply([], data);
I would do as follows;
var a = [
[ 1, 4, 9, 9 ],
[ 4, 4, 9, 1 ],
[ 6, 6, 9, 1 ]
],
b = [].concat(...a)
console.log(b)
You actually don't need any sort of library to do it, you can use concat with apply:
Promise.all(arrayOfPromises).then((arrayOfArrays) => {
return [].concat.apply([], arrayOfArrays);
});
If you are using lodash, though, you can use _.flatten(arrayOfArrays) for the same effect.
If using async/await, to expand on #Retsam's answer, you can do it like so
const mergedArray = []
.concat
.apply([], await Promise.all([promise1, promise2, promiseN]));
A real world example I did using the AWS SDK, getting a list of usernames from multiple IAM user groups
const users = await getActiveUsersByGroup(['group1', 'group2'])
async function getActiveUsersByGroup(groups = []) {
getUsersByGroupPromises = groups.map(group => getUsersByGroup(group));
const users = []
.concat
.apply([], await Promise.all(getUsersByGroupPromises)) // Merge (concat) arrays
.map(users => users.UserName); // Construct new array with just the usernames
return users;
}
async function getUsersByGroup(group) {
const params = {
GroupName: group,
MaxItems: 100 // Default
};
const { Users: users } = await iam.getGroup(params).promise();
return users;
}