javascript recursive dice combination and store the result in a matrix - javascript
what I'm trying to achieve is one of the classical results of the combination of the launch of N-dices,but saving the results in a matrix with M-N fields (where N is the number of dices and M is the total number of possible combinations - obtained by 6^N). So far I've written the following code:
function Dice (commonFace, singleFace){
this.diceFaces = ["critical", commonFace, commonFace, singleFace, "support1", "support2"]
this.numCases = function(){
return Math.pow(this.diceFaces.length, numberDices)
}
}
//create the attack dice
var attackDice = new Dice("smash", "fury");
//create the defence dice
var defenceDice = new Dice("block", "dodge");
//create a function that rolls the dice results and returns the number of results results
function rollDiceResults(diceTypeRolled, numberDicesRolled) {
//total possible results of the rolls of that number of dices
var totalPossibilites = diceTypeRolled.numCases(numberDicesRolled);
//store the dice results
var diceResults = new Array;
function rollDice(diceType, iteration, array) {
if (iteration == 1) {
//return the base case
for (i = 0; i < diceType.diceFaces.length; i++) {
array[i] = (diceType.diceFaces[i]);
}
} else {
//continue
for (i = 0; i < diceType.diceFaces.length; i++) {
array[i] = diceType.diceFaces[i];
rollDice(diceType, iteration - 1, tempResult);
}
}
}
for (i = 0; i < numberDicesRolled; i++) {
rollDice(diceTypeRolled, numberDicesRolled, diceResults);
}
}
what I'm getting is
an error in the declaration of the function
I'm missing how can I call the array inside the function, while maintaining the m-n structure
thanks for your help
Fixed-length combinations
Recursion is a functional heritage and so using it with functional style will yield the best results. Recursion is all about breaking a large problem down into smaller sub problems until a base case is reached.
Below, we use the proposed Array.prototype.flatMap but include a polyfill for environments that don't support it yet. When n = 0 our base case has been reached and we return the empty result. The inductive case is n > 0 where choices will be added to the result of the smaller problem combination (choices, n - 1) – We say this problem is smaller here because n - 1 is closer to the base case of n = 0
Array.prototype.flatMap = function (f)
{
return this.reduce ((acc, x) => acc.concat (f (x)), [])
}
const combinations = (choices, n = 1) =>
n === 0
? [[]]
: combinations (choices, n - 1) .flatMap (comb =>
choices .map (c => [ c, ...comb ]))
const faces =
[ 1, 2, 3 ]
// roll 2 dice
console.log (combinations (faces, 2))
// [ [ 1, 1 ], [ 2, 1 ], [ 3, 1 ], [ 1, 2 ], ..., [ 2, 3 ], [ 3, 3 ] ]
// roll 3 dice
console.log (combinations (faces, 3))
// [ [ 1, 1, 1 ], [ 2, 1, 1 ], [ 3, 1, 1 ], [ 1, 2, 1 ], ..., [ 2, 3, 3 ], [ 3, 3, 3 ] ]
Using combinations with your program
Writing rollDice would look something like this
const rollDice = (dice, numberOfDice) =>
combinations (dice.diceFaces, numberOfDice)
console.log (rollDice (attackDice, 2))
// [ [ 'critical', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'fury', 'critical' ]
// , [ 'support1', 'critical' ]
// , [ 'support2', 'critical' ]
// , [ 'critical', 'smash' ]
// , [ 'smash', 'smash' ]
// , ...
// , [ 'critical', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'fury', 'support2' ]
// , [ 'support1', 'support2' ]
// , [ 'support2', 'support2' ]
// ]
Without dependencies
If you're curious how flatMap and map are working, we can implement them on our own. Pure recursion, through and through.
const None =
Symbol ()
const map = (f, [ x = None, ...xs ]) =>
x === None
? []
: [ f (x), ...map (f, xs) ]
const flatMap = (f, [ x = None, ...xs ]) =>
x === None
? []
: [ ...f (x), ...flatMap (f, xs) ]
const combinations = (choices = [], n = 1) =>
n === 0
? [[]]
: flatMap ( comb => map (c => [ c, ...comb ], choices)
, combinations (choices, n - 1)
)
const faces =
[ 1, 2, 3 ]
// roll 2 dice
console.log (combinations (faces, 2))
// [ [ 1, 1 ], [ 2, 1 ], [ 3, 1 ], [ 1, 2 ], ..., [ 2, 3 ], [ 3, 3 ] ]
// roll 3 dice
console.log (combinations (faces, 3))
// [ [ 1, 1, 1 ], [ 2, 1, 1 ], [ 3, 1, 1 ], [ 1, 2, 1 ], ..., [ 2, 3, 3 ], [ 3, 3, 3 ] ]
Nerfed
Ok, so combinations allows us to determine the possible combinations of a repeated, fixed set of choices. What if we had 2 unique dice and wanted to get the all the possible rolls?
const results =
rollDice (attackDice, defenceDice) ???
We could call rollDice (attackDice, 1) and then rollDice (defenceDice, 1) and then somehow combine the answers. But there's a better way; a way that allows for any number of unique dice, even with a varying number of sides on each die. Below, I show you the two versions of combinations we wrote alongside the necessary changes to access untapped potential
// version 1: using JS natives
const combinations = (choices, n = 1) =>
const combinations = (choices = None, ...rest) =>
n === 0
choices === None
? [[]]
: combinations (choices, n - 1) .flatMap (comb =>
: combinations (...rest) .flatMap (comb =>
choices .map (c => [ c, ...comb ]))
// version 2: without dependencies
const combinations = (choices = [], n = 1) =>
const combinations = (choices = None, ...rest) =>
n === 0
choices === None
? [[]]
: flatMap ( comb => map (c => [ c, ...comb ], choices)
, combinations (choices, n - 1)
, combinations (...rest)
)
With this new version of combinations, we can roll any number of dice of any size – even the physically impossible 3-sided die is possible in this program ^_^
// version 3: variadic dice
const combinations = (choices = None, ...rest) =>
choices === None
? [[]]
: flatMap ( comb => map (c => [ c, ...comb ], choices)
, combinations (...rest)
)
const d1 =
[ 'J', 'Q', 'K' ]
const d2 =
[ '♤', '♡', '♧', '♢' ]
console.log (combinations (d1, d2))
// [ [ 'J', '♤' ], [ 'Q', '♤' ], [ 'K', '♤' ]
// , [ 'J', '♡' ], [ 'Q', '♡' ], [ 'K', '♡' ]
// , [ 'J', '♧' ], [ 'Q', '♧' ], [ 'K', '♧' ]
// , [ 'J', '♢' ], [ 'Q', '♢' ], [ 'K', '♢' ]
// ]
And of course you can roll a collection of the same dice
console.log (combinations (d1, d1, d1))
// [ [ 'J', 'J', 'J' ]
// , [ 'Q', 'J', 'J' ]
// , [ 'K', 'J', 'J' ]
// , [ 'J', 'Q', 'J' ]
// , [ 'Q', 'Q', 'J' ]
// , [ 'K', 'Q', 'J' ]
// , [ 'J', 'K', 'J' ]
// , ...
// , [ 'K', 'Q', 'K' ]
// , [ 'J', 'K', 'K' ]
// , [ 'Q', 'K', 'K' ]
// , [ 'K', 'K', 'K' ]
// ]
Tapping into this potential with your program, you can write rollDice as
const rollDice = (...dice) =>
combinations (...dice.map (d => d.diceFaces))
console.log (rollDice (attackDice, defenceDice))
// [ [ 'critical', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'smash', 'critical' ]
// , [ 'fury', 'critical' ]
// , [ 'support1', 'critical' ]
// , [ 'support2', 'critical' ]
// , [ 'critical', 'block' ]
// , [ 'smash', 'block' ]
// , ...
// , [ 'support2', 'support1' ]
// , [ 'critical', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'smash', 'support2' ]
// , [ 'fury', 'support2' ]
// , [ 'support1', 'support2' ]
// , [ 'support2', 'support2' ]
// ]
Or with all sorts of dice
const rollDice = (...dice) =>
combinations (...dice.map (d => d.diceFaces))
console.log (rollDice (defenceDice, attackDice, attackDice, attackDice))
// [ [ 'critical', 'critical', 'critical', 'critical' ]
// , [ 'block', 'critical', 'critical', 'critical' ]
// , [ 'block', 'critical', 'critical', 'critical' ]
// , [ 'dodge', 'critical', 'critical', 'critical' ]
// , [ 'support1', 'critical', 'critical', 'critical' ]
// , [ 'support2', 'critical', 'critical', 'critical' ]
// , [ 'critical', 'smash', 'critical', 'critical' ]
// , [ 'block', 'smash', 'critical', 'critical' ]
// , [ 'block', 'smash', 'critical', 'critical' ]
// , [ 'dodge', 'smash', 'critical', 'critical' ]
// , [ 'support1', 'smash', 'critical', 'critical' ]
// , ...
// ]
Going high-level
It's cool to see how we can accomplish so much with just a few pure functions in JavaScript. However, the above implementation is slow af and severely limited in terms of how many combinations it could produce.
Below, we try to determine the combinations for seven 6-sided dice. We expect 6^7 to result in 279936 combinations
const dice =
[ attackDice, attackDice, attackDice, attackDice, attackDice, attackDice, attackDice ]
rollDice (...dice)
// => ...
Depending on the implementation of combinations you picked above, if it doesn't cause your environment to hang indefinitely, it will result in a stack overflow error
To increase performance here, we reach for a high-level feature provided by Javascript: generators. Below, we rewrite combinations but this time using some imperative style required to interact with generators.
const None =
Symbol ()
const combinations = function* (...all)
{
const loop = function* (comb, [ choices = None, ...rest ])
{
if (choices === None)
return
else if (rest.length === 0)
for (const c of choices)
yield [ ...comb, c ]
else
for (const c of choices)
yield* loop ([ ...comb, c], rest)
}
yield* loop ([], all)
}
const d1 =
[ 'J', 'Q', 'K', 'A' ]
const d2 =
[ '♤', '♡', '♧', '♢' ]
const result =
Array.from (combinations (d1, d2))
console.log (result)
// [ [ 'J', '♤' ], [ 'J', '♡' ], [ 'J', '♧' ], [ 'J', '♢' ]
// , [ 'Q', '♤' ], [ 'Q', '♡' ], [ 'Q', '♧' ], [ 'Q', '♢' ]
// , [ 'K', '♤' ], [ 'K', '♡' ], [ 'K', '♧' ], [ 'K', '♢' ]
// , [ 'A', '♤' ], [ 'A', '♡' ], [ 'A', '♧' ], [ 'A', '♢' ]
// ]
Above, we use Array.from to eagerly collect all the combinations into a single result. This is often not necessary when working with generators. Instead, we can use the values as they're being generated
Below, we use for...of to interact with each combination directly as it comes out of the generator. In this example, we show any combination that includes a J or a ♡
const d1 =
[ 'J', 'Q', 'K', 'A' ]
const d2 =
[ '♤', '♡', '♧', '♢' ]
for (const [ rank, suit ] of combinations (d1, d2))
{
if (rank === 'J' || suit === '♡' )
console.log (rank, suit)
}
// J ♤ <-- all Jacks
// J ♡
// J ♧
// J ♢
// Q ♡ <-- or non-Jacks with Hearts
// K ♡
// A ♡
But of course there's more potential here. We can write whatever we want in the for block. Below, we add an additional condition to skip Queens Q using continue
const d1 =
[ 'J', 'Q', 'K', 'A' ]
const d2 =
[ '♤', '♡', '♧', '♢' ]
for (const [ rank, suit ] of combinations (d1, d2))
{
if (rank === 'Q')
continue
if (rank === 'J' || suit === '♡' )
console.log (rank, suit)
}
// J ♤
// J ♡
// J ♧
// J ♢
// K ♡ <--- Queens dropped from the output
// A ♡
And perhaps the most powerful thing here is we can stop generating combinations with break. Below, if a King K is encountered, we halt the generator immediately
const d1 =
[ 'J', 'Q', 'K', 'A' ]
const d2 =
[ '♤', '♡', '♧', '♢' ]
for (const [ rank, suit ] of combinations (d1, d2))
{
if (rank === 'K')
break
if (rank === 'J' || suit === '♡' )
console.log (rank, suit)
}
// J ♤
// J ♡
// J ♧
// J ♢
// Q ♡ <-- no Kings or Aces; generator stopped at K
You can get pretty creative with the conditions. How about all the combinations that begin or end in a Heart
for (const [ a, b, c, d, e ] of combinations (d2, d2, d2, d2, d2))
{
if (a === '♡' && e === '♡')
console.log (a, b, c, d, e)
}
// ♡ ♤ ♤ ♤ ♡
// ♡ ♤ ♤ ♡ ♡
// ♡ ♤ ♤ ♧ ♡
// ...
// ♡ ♢ ♢ ♡ ♡
// ♡ ♢ ♢ ♧ ♡
// ♡ ♢ ♢ ♢ ♡
And to show you generators work for large data sets
const d1 =
[ 1, 2, 3, 4, 5, 6 ]
Array.from (combinations (d1, d1, d1, d1, d1, d1, d1)) .length
// 6^7 = 279936
Array.from (combinations (d1, d1, d1, d1, d1, d1, d1, d1)) .length
// 6^8 = 1679616
We can even write higher-order functions to work with generators such as our own filter function. Below, we find all combinations of three 20-sided dice that form a Pythagorean triple - 3 whole numbers that make up the side lengths of a valid right triangle
const filter = function* (f, iterable)
{
for (const x of iterable)
if (f (x))
yield x
}
const d20 =
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ]
const combs =
combinations (d20, d20, d20)
const pythagoreanTriple = ([ a, b, c ]) =>
(a * a) + (b * b) === (c * c)
for (const c of filter (pythagoreanTriple, combs))
console.log (c)
// [ 3, 4, 5 ]
// [ 4, 3, 5 ]
// [ 5, 12, 13 ]
// [ 6, 8, 10 ]
// [ 8, 6, 10 ]
// [ 8, 15, 17 ]
// [ 9, 12, 15 ]
// [ 12, 5, 13 ]
// [ 12, 9, 15 ]
// [ 12, 16, 20 ]
// [ 15, 8, 17 ]
// [ 16, 12, 20 ]
Or use Array.from with a mapping function to simultaneously transform each combination into a new result and collect all results in an array
const allResults =
Array.from ( filter (pythagoreanTriple, combs)
, ([ a, b, c ], index) => ({ result: index + 1, solution: `${a}² + ${b}² = ${c}²`})
)
console.log (allResults)
// [ { result: 1, solution: '3² + 4² = 5²' }
// , { result: 2, solution: '4² + 3² = 5²' }
// , { result: 3, solution: '5² + 12² = 13²' }
// , ...
// , { result: 10, solution: '12² + 16² = 20²' }
// , { result: 11, solution: '15² + 8² = 17²' }
// , { result: 12, solution: '16² + 12² = 20²' }
// ]
What the func?
Functional programming is deep. Dive in!
const None =
Symbol ()
// Array Applicative
Array.prototype.ap = function (args)
{
const loop = (acc, [ x = None, ...xs ]) =>
x === None
? this.map (f => f (acc))
: x.chain (a => loop ([ ...acc, a ], xs))
return loop ([], args)
}
// Array Monad (this is the same as flatMap above)
Array.prototype.chain = function chain (f)
{
return this.reduce ((acc, x) => [ ...acc, ...f (x) ], [])
}
// Identity function
const identity = x =>
x
// math is programming is math is ...
const combinations = (...arrs) =>
[ identity ] .ap (arrs)
console.log (combinations ([ 0, 1 ], [ 'A', 'B' ], [ '♡', '♢' ]))
// [ [ 0, 'A', '♡' ]
// , [ 0, 'A', '♢' ]
// , [ 0, 'B', '♡' ]
// , [ 0, 'B', '♢' ]
// , [ 1, 'A', '♡' ]
// , [ 1, 'A', '♢' ]
// , [ 1, 'B', '♡' ]
// , [ 1, 'B', '♢' ]
// ]
Related
Find path in tree by direct path
I have similiary problem like here: JavaScript: Find all parents for element in tree recursive But I don't find path by name but by direct path. const path = ["name1", "name4", "name5"]; const data = [ { 'name': 'name1', 'tree': [ {'name': 'name2'}, {'name': 'name3'}, { 'name': 'name4', 'tree': [ {'name': 'name5'}, {'name': 'name6'} ] }, {'name': 'name7'} ] }, { 'name': 'name8', 'tree': [ {'name': 'name9'} ] } ]; It returns every possible path or nothing. When path is too short, it returns nothing. When path is too long, it returns nothing. Thanks for help! Examples of desired output: const path = ["name1", "name4", "name5"]; findAPath(data, path) Returns: ["name1", "name4", "name5"] const path = ["name1", "name7", "name5"]; findAPath(data, path) Returns [] const path = ["name1", "name4", "name5", "name5"]; findAPath(data, path) Returns [] My trying: let index = 0; function find(data, index) { let index = index; data.some((o) => { if(o.name == path[index]) { index++; find(o.tree, index); } }); // I don't know what return here. // I need to probably return path where I am. return <>; }
using Array.prototype.flatMap Here's a functional solution using a mutual recursion technique - const None = Symbol () const findPath = (tree = [], names = [], r = []) => tree.length && names.length // base: and ? tree.flatMap(branch => findPath1(branch, names, r)) : tree.length || names.length // inductive: xor ? [] : [ r ] // inductive: nor // inductive: nor const findPath1 = ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) => name === "" && q === None // base: and ? [ r ] : name === "" || q === None || name !== q // inductive: xor ? [] : findPath(tree, more, [ ...r, q ]) // inductive: nor findPath(data, ["name1", "name4", "name5"]) // => [ [ "name1", "name4", "name5" ] ] NB if your data contains multiple paths to the input values, all paths will be returned - const data = [ { 'name': 'name1', // name1 'tree': [ {'name': 'name2'}, {'name': 'name3'}, { 'name': 'name4', // name1->name4 'tree': [ {'name': 'name5'}, // name1->name4->name5 {'name': 'name6'} ] }, { 'name': 'name4', // name1->name4 'tree': [ {'name': 'name5'}, // name1->name4->name5 {'name': 'name6'} ] }, {'name': 'name7'} ] }, { 'name': 'name8', 'tree': [ {'name': 'name9'} ] } ] Just like you asked, it returns every possible path, or nothing - findPath(data, ["name1", "name4", "name5"]) // => [ [ "name1", "name4", "name5" ], // [ "name1", "name4", "name5" ] ] findPath(data, [ "name1", "name7" ]) // => [ [ "name1", "name7" ] ] findPath(data, [ "name1", "name9" ]) // => [] When a path is too short or too long, it will return nothing - findPath(data, [ "name1", "name4" ]) // => [] findPath(data, [ "name1", "name4", "name5", "name6" ]) // => [] Expand the snippet below to verify the results in your own browser - const None = Symbol () const findPath = (tree = [], names = [], r = []) => tree.length && names.length ? tree.flatMap(branch => findPath1(branch, names, r)) : tree.length || names.length ? [] : [ r ] const findPath1 = ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) => name === "" && q === None ? [ r ] : name === "" || q === None || name !== q ? [] : findPath(tree, more, [ ...r, q ]) const data = [ { 'name': 'name1', 'tree': [ {'name': 'name2'}, {'name': 'name3'}, { 'name': 'name4', 'tree': [ {'name': 'name5'}, {'name': 'name6'} ] }, {'name': 'name7'} ] }, { 'name': 'name8', 'tree': [ {'name': 'name9'} ] } ] console.log(findPath(data, ["name1", "name4", "name5"])) // [ [ "name1", "name4", "name5" ] ] console.log(findPath(data, [ "name1", "name7" ])) // [ [ "name1", "name7" ] ] console.log(findPath(data, [ "name1", "name9" ])) // [] using Generators Here's an alternative implementation using generators - const None = Symbol () const findPath = function* (tree = [], names = [], r = []) { if (tree.length && names.length) // base: and for (const branch of tree) yield* findPath1(branch, names, r) else if (tree.length || names.length) // inductive: xor return else // inductive: nor yield r } const findPath1 = function* ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) { if (name === "" && q === None) // base: and yield r else if (name === "" || q === None || name !== q) // inductive: xor return else // inductive: nor yield* findPath(tree, more, [ ...r, q ]) } It has the exact same output as above, only to coerce the iterable generator into an array, we use Array.from - Array.from(findPath(data, ["name1", "name4", "name5"])) // => [ [ "name1", "name4", "name5" ] ] Array.from(findPath(data, [ "name1", "name7" ])) // => [ [ "name1", "name7" ] ] Array.from(findPath(data, [ "name1", "name9" ])) // => [] Expand the snippet below to verify the results in your own browser - const None = Symbol () const findPath = function* (tree = [], names = [], r = []) { if (tree.length && names.length) for (const branch of tree) yield* findPath1(branch, names, r) else if (tree.length || names.length) return else yield r } const findPath1 = function* ({ name = "", tree = [] } = {}, [ q = None, ...more ] = [], r = []) { if (name === "" && q === None) yield r else if (name === "" || q === None || name !== q) return else yield* findPath(tree, more, [ ...r, q ]) } const data = [ { 'name': 'name1', 'tree': [ {'name': 'name2'}, {'name': 'name3'}, { 'name': 'name4', 'tree': [ {'name': 'name5'}, {'name': 'name6'} ] }, {'name': 'name7'} ] }, { 'name': 'name8', 'tree': [ {'name': 'name9'} ] } ] console.log(Array.from(findPath(data, ["name1", "name4", "name5"]))) // [ [ "name1", "name4", "name5" ] ] console.log(Array.from(findPath(data, [ "name1", "name7" ]))) // [ [ "name1", "name7" ] ] console.log(Array.from(findPath(data, [ "name1", "name9" ]))) // [] how they're the same; how they're not Note the similarity between the two implementations and how the result is formed. Both use mutual recursion. The functional solution uses expressions whereas the generator solution uses statements. The generator implementation extends a distinct advantage where by we can chose to stop or continue iteration ("finding") whenever we want. For example, imagine an input where there are ten (10) unique paths for the given input. Perhaps we want to just return the first match, const findFirst = (tree = [], names = []) => { for (const path of findPath(tree, names)) return path } Or get the first three (3) matches - const findFirst3 = (tree = [], names = []) => { const r = [] for (const path of findPath(tree, names)) if (r.length < 3) r.push(path) return r } Or get the first N - const findFirstN = (tree = [], names = [], n = 0) => { const r = [] for (const path of findPath(tree, names)) if (r.length < n) r.push(path) return r } Generators are flexible like this. By contrast, the flatMap implementation is eager and always returns all results.
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' ]
Numeric sort on an array with javascript
I have this array : [ [ 1, 'a' ], [ 2, 'b' ], [ 1, 'd' ], [ 9, 'e' ], [ 1, 'f' ], [ 11, 'g' ], [ 9, 'h' ], [ 3, 'i' ] ] and I would like to have : [ [ 11, 'g' ], [ 9, 'e' ], [ 9, 'h' ], [ 3, 'i' ], [ 2, 'b' ], [ 1, 'a' ], [ 1, 'd' ], [ 1, 'f' ] ] How can I do that with javascript please ? I tried sort(), I also tried with sort(compare) with : function compare(x, y) { return x - y; }
You can use .sort() with Array Destructuring like this: function compare([a], [b]) { return b - a; } Demo: let a = [ [ 1, 'a' ], [ 2, 'b' ], [ 1, 'd' ], [ 9, 'e' ], [ 1, 'f' ], [ 11, 'g' ], [ 9, 'h' ], [ 3, 'i' ] ]; a.sort(compare); function compare([a], [b]) { return b - a; } console.log(a); .as-console-wrapper { max-height: 100% !important; top: 0; } In case first elements match, you can sort based on 2nd element as well: function compare([a, c], [b, d]) { return (b - a) || c.localeCompare(d) } Demo: let a = [ [ 2, 'b' ], [ 1, 'd' ], [ 9, 'e' ], [ 1, 'f' ], [ 11, 'g' ], [ 9, 'h' ], [ 3, 'i' ], [ 1, 'a' ] ]; a.sort(compare); function compare([a, c], [b, d]) { return (b - a) || c.localeCompare(d); } console.log(a); .as-console-wrapper { max-height: 100% !important; top: 0 }
You need to compare the first element in the nested array since you want to sort based on that number. function compare(x, y) { return y[0] - x[0]; } var data = [ [1, 'a'], [2, 'b'], [1, 'd'], [9, 'e'], [1, 'f'], [11, 'g'], [9, 'h'], [3, 'i'] ]; function compare(x, y) { return y[0] - x[0]; } data.sort(compare); console.log(data); In case you want to sort based on second element(secondary sorting in case the first element is same) then use String#localeCompare method for comparing. function compare(x, y) { return y[0] - x[0] || x[1].localeCompare(y[0]); } var data = [ [2, 'b'], [1, 'd'], [9, 'e'], [1, 'f'], [1, 'a'], [11, 'g'], [9, 'h'], [3, 'i'] ]; function compare(x, y) { return (y[0] - x[0]) || x[1].localeCompare(y[1]); } data.sort(compare); console.log(data);
Compare the elements based on their first element, which is the number. var a = [ [ 1, 'a' ], [ 2, 'b' ], [ 1, 'd' ], [ 9, 'e' ], [ 1, 'f' ], [ 11, 'g' ], [ 9, 'h' ], [ 3, 'i' ] ]; a = a.sort((a,b) => { return b[0] - a[0] }); console.log(a)
Use sort to compare first element and if first element is same then compare the second element. arr.sort( (a,b) => (b[0] - a[0]) || (b[1] - a[1]) ) Demo var arr = [ [ 1, 'a' ], [ 2, 'b' ], [ 1, 'd' ], [ 9, 'e' ], [ 1, 'f' ], [ 11, 'g' ], [ 9, 'h' ], [ 3, 'i' ] ]; arr.sort( (a,b) => (b[0] - a[0]) || (b[1] - a[1]) ); console.log(arr);
Breaking an array of users in a group of sub-arrays
I would like to break a array with users: [ {name: "Carlos"}, {name: "Marcos"}, {name: "Fernando"}, {name: "Jhon"}, {name: "Loius"}, {name: "Jacob"}, ] And get something like this: [ [ {name: "Jhon"}, {name: "Loius"}, {name: "Jacob"}, ], [ {name: "Carlos"}, {name: "Marcos"}, {name: "Fernando"}, ] ] The criterion for splitting them is that I want that each sub array to have a maximum of 3 users, but the number of sub arrays can be unlimited.
function splitIntoParts(input, maxElementsPerPart) { const inputClone = [...input]; // create a copy because splice modifies the original array reference. const result = []; const parts = Math.ceil(input.length/maxElementsPerPart); for(let i = 0; i < parts; i++) { result.push(inputClone.splice(0, maxElementsPerPart)); } return result; } console.log(splitIntoParts([ {name: "Carlos"}, {name: "Marcos"}, {name: "Fernando"}, {name: "Jhon"}, {name: "Loius"}, {name: "Jacob"}, {name: "Simon"}, ], 3));
chunk is elegantly expressed using functional style const chunk = (xs = [], n = 1) => xs.length <= n ? [ xs ] : [ xs.slice (0, n) ] .concat (chunk (xs.slice (n), n)) const data = [ 1, 2, 3, 4, 5, 6 ] console.log (chunk (data, 1)) // [ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ], [ 6 ] ] console.log (chunk (data, 2)) // [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ] console.log (chunk (data, 3)) // [ [ 1, 2, 3 ], [ 4, 5, 6 ] ] console.log (chunk (data, 4)) // [ [ 1, 2, 3, 4 ], [ 5, 6 ] ] console.log (chunk ()) // [ [ ] ] I think take and drop abstractions make the function read a littler nicer. Your opinion may vary. const take = (xs = [], n = 1) => xs.slice (0, n) const drop = (xs = [], n = 1) => xs.slice (n) const chunk = (xs = [], n = 1) => xs.length <= n ? [ xs ] : [ take (xs, n) ] .concat (chunk (drop (xs, n), n))
let data = [ {name: "Carlos"}, {name: "Marcos"}, {name: "Fernando"}, {name: "Jhon"}, {name: "Loius"}, {name: "Jacob"}, ] function toGroupsOf(n, array){ return Array.from( {length: Math.ceil(array.length/n)}, //how many groups (_,i) => array.slice(i*n, (i+1)*n) //get the items for this group ) } console.log(toGroupsOf(3, data)); .as-console-wrapper{top:0;max-height:100%!important} or function toGroupsOf(n, array){ var groups = Array(Math.ceil(array.length/n)); for(var i=0; i<groups.length; ++i) groups[i] = array.slice(i*n, (i+1)*n); return groups; }
javascript: Trying to flatten array only one level
I am trying to write a function to flatten an array. I have part of the function working and I need help in the other half. flatten: function(anyArray, singleLevel) { if (singleLevel == true) { flatArray = Array.prototype.concat.apply([], anyArray); return flatArray; } flatArray = Array.prototype.concat.apply([], anyArray); if (flatArray.length != anyArray.length) { flatArray = someObject.array.flatten(flatArray); } return flatArray; } if I type .flatten([[[1],[1,2,3,[4,5],4],[2,3]]], true); I want it to flatten only one level: [[1],[1,2,3,[4,5],4],[2,3]]
Modern JavaScript allows us to handle this very easily using a variety of techniques Using Array.prototype.flat - const arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] const flatArr = arr.flat(1) // 1 is default depth console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]] Using Array.prototype.flatMap - const arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] const flatArr = arr.flatMap(x => x) console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]] Using a spread argument to Array.prototype.concat const arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] const flatArr = [].concat(...arr) console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]] Older version of JavaScript (ECMAScript 5 and below) can use techniques like Function.prototype.apply - var arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] var flatArr = Array.prototype.concat.apply([], arr) console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]] Using Array.prototype.reduce - var arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] var flatArr = arr.reduce((r, a) => r.concat(a), []) console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]] Using a primitive for loop - var arr = [ [ 1 ], [ 2, 3, [ 4, 5, [ 6 ] ] ], [ 7, [ 8, 9 ] ] ] var flatArr = [] for (var i = 0; i < arr.length; i = i + 1) flatArr = flatArr.concat(arr[i]) console.log(JSON.stringify(arr)) console.log(JSON.stringify(flatArr)) // [[1],[2,3,[4,5,[6]]],[7,[8,9]]] // [1,2,3,[4,5,[6]],7,[8,9]]
The concat array method expects one or more arrays as arguments, whose elements will be appended: [1].concat([2, 3], [4]) // [1, 2, 3, 4] So if you are using apply, that will flatten another level: [].concat.apply([1], [[2], [3]]) // === [1].concat([2], [3]) So you can either use push instead of concat, or call (or just direct invocation) instead of apply to get only a single flattening level.
if you use ES6/ES2015 you can use spread operator. Something like this console.log(...[[[1],[1,2,3,[4,5],4],[2,3]]])