uylmz
uylmz

Reputation: 1552

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"]
  }
]

Upvotes: 6

Views: 554

Answers (5)

Mulan
Mulan

Reputation: 135227

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

Upvotes: 4

Jackkobec
Jackkobec

Reputation: 6705

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

Upvotes: 0

Jack Bashford
Jack Bashford

Reputation: 44107

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.

Upvotes: 2

Matt Morgan
Matt Morgan

Reputation: 5303

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.

Upvotes: 6

Ele
Ele

Reputation: 33726

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

Upvotes: 4

Related Questions