6

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"]
  }
]
4
  • It would be helpful if you edited your question to include: 1. Is the input array always sorted as in your example? 2. Should the order of the output match the order of the input? Several of the answers assume sorted input. Commented Nov 11, 2018 at 1:35
  • If you don't consider complexity and performance , than , basically you can use destructuring [...arr,...arr].sort(). And by the way functional programming does not refer to Arrow-Functions (as you used in your example) but rather the behavior and meaning of the function which should act like Mathematical functions by receiving parameters and returning result. So in your case you would like to write something like this (to align with functional programming): const duplicateArrayItems = (arr) => [...arr,...arr].sort(); let array = ['a','b','c'], result = duplicateArrayItems(array); Commented Nov 11, 2018 at 7:09
  • @MattMorgan thanks for pointing that out, I was trying to provide an example as simple as possible but that caused important details to be left out. 1) input is not sorted because the real actual input is an array of complex objects that are not sortable. 2) the order of the output should be like a,a,b,b,c,c duplicated elements are next to each other Commented Nov 11, 2018 at 11:21
  • @Reek thanks for the clarification. I've edited my answer to reflect your new input data. Commented Nov 11, 2018 at 12:12

5 Answers 5

6

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.

Sign up to request clarification or add additional context in comments.

5 Comments

concat also takes multiple arguments ... => acc.concat(el, el)
@kemicofa using concat().sort() as a few have suggested only works when the original array was sorted. If you have a better suggestion please post an answer.
acc.concat(el).concat(el) may be less code but it's much worse time complexity. I would use push(el, el) with a return acc statement because push is O(1) as opposed to concat which is O(n) since it's building a new array.
@slider thanks for the suggestion! That sounds like a great optimization.
4

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; }

6 Comments

Is Array.prototype.concat.call... really necessary? Couldn't this just be done by array.concat(array).sort()?
Makes sense. There's still more than one way to do it. For instance by making a copy of the original array with slice
How's array.concat(array) going to mutate the original? The docs say "This method does not change the existing arrays, but instead returns a new array."
Also this assumes the original array is sorted. What if the original is ['b', 'a', 'c']?
Well, with ['b', 'a', 'c'] this will result in ["a","a","b","b","c","c"] but the desired output may be ['b', 'b', 'a', 'a', 'c', 'c'] (I'm not sure though).
|
4

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','♧']
// ]

2 Comments

That is really nice, although of limited practical use until there’s Microsoft and Node support.
@MarkMeyer flatMap is just a specialised reduce - I've updated the answer to show how to use flatMap in an environment that doesn't support Array.prototype.flatMap
2

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.

Comments

0

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' ]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.