37

I'm sure there are many ways to achieve that but I'm looking for something "elegant".

a = [
  'a',
  'b',
  'c'
];

magicArrayJoin(a, {value: 255} ); // insert the same object between each item

result ==  [
  'a',
  {value: 255},
  'b',
  {value: 255}
  'c'
];

All proposals are welcome. :)

Badacadabra
  • 8,043
  • 7
  • 28
  • 49
cimak
  • 1,765
  • 3
  • 15
  • 18
  • What exactly should 'magicArrayJoin' do? Does it insert that second argument at a certain regular interval, or just between every item? – John Barton Aug 07 '15 at 14:05
  • @Pointy I don't think this is a dup. The OP isn't asking how to insert an element into an array at a position. They're asking how to insert an element into an array between every element. – Ghazgkull Aug 07 '15 at 14:09
  • @cimak Are you asking how to insert *one* object (repeatedly) between all elements in the array, as in your example? Or are you asking how to insert multiple objects between elements in the array, as in the question title? Also, is it a requirement that you modify the array in place or is creating a new array an option? – Ghazgkull Aug 07 '15 at 14:12
  • I want to insert the same object between every item. Modify existing or create a new array - it doesnt matter. – cimak Aug 07 '15 at 14:13

15 Answers15

39

One-liner using plain ES6:

const interleave = (arr, thing) => [].concat(...arr.map(n => [n, thing])).slice(0, -1)

Usage:

interleave(['foo', 'bar', 'baz'], 'avocado')

Prints:

> ["foo", "avocado", "bar", "avocado", "baz"]
nucleartide
  • 3,888
  • 4
  • 28
  • 29
  • 6
    Nice technique – now could just be `const interleave = (arr, x) => arr.flatMap(e => [e, x]).slice(0, -1)` – Christopher G Oct 28 '21 at 03:14
  • @ChristopherG that's nice and also Typescript friendly. `const interleave = (arr:any[], x: any) => arr.flatMap(e => [e, x]).slice(0, -1)` Typing the original answer was quite tough. – Davey Apr 18 '23 at 09:11
  • @Davey Nice! Here's a version with even better type safety for Typescript: `function interleave(arr:any[], x: any) => arr.flatMap(e => [e, x]).slice(0, -1)` – Ro Marcus Westin May 16 '23 at 13:38
  • Not a good answer because nobody will understand it without thinking about it for 5 minutes. – Page not found Aug 17 '23 at 04:02
27

You can do it with flatMap. It can be found from lodash for example

_.flatMap([1,2,3,4], (value, index, array) =>
     array.length -1 !== index // check for the last item
     ? [value, "s"]
     : value
);

ouputs

[1, "s", 2, "s", 3, "s", 4]

Update

Array#flatMap proposal is in the works so in future this should work:

[1, 2, 3, 4].flatMap(
    (value, index, array) =>
        array.length - 1 !== index // check for the last item
            ? [value, "s"]
            : value,
);
esamatti
  • 18,293
  • 11
  • 75
  • 82
16

In my opinion the most elegant way to do this is the following one:

ES6 syntax version

const insertIntoArray = (arr, value) => {

    return arr.reduce((result, element, index, array) => {

        result.push(element);

        if (index < array.length - 1) {
            result.push(value);
        }

        return result;
    }, []);
};

Usage:

insertIntoArray([1, 2, 3], 'x'); // => [1, 'x', 2, 'x', 3]
micnic
  • 10,915
  • 5
  • 44
  • 55
  • it's clear there's an irrational bias in favor of using reduce when this has more upvotes than Bergi's perfectly fine and more efficient answer from two years earlier – Andy Jun 24 '20 at 01:28
  • @Andy this is why the original question asked about an elegant way of doing this – micnic Jun 24 '20 at 15:04
  • Well as far as elegance over performance, the `concatMap` examples in Bergi's answer seemed elegant to me but using `reduce` this way doesn't – Andy Jun 25 '20 at 16:46
10

Use ES6 flatMap function.

const insertBetween = (ele, array) => {
  return array.flatMap((x) => [ele, x]).slice(1);
};

insertBetween('+', [1, 2, 3]);
Leon Huang
  • 587
  • 6
  • 14
  • This way adds a lot of flexibility because when you want to mix objects, they are not necessarily the same structure as other answers give for granted. – Alvin Aug 18 '22 at 23:55
7

An ordinary loop seems to be the best:

function intersperse(arr, el) {
    var res = [], i=0;
    if (i < arr.length)
        res.push(arr[i++]);
    while (i < arr.length)
        res.push(el, arr[i++]);
    return res;
}

If you're looking for something elegant, it would probably have to use some kind of concatMap, as in

function concatMap(arr, fn) { return [].concat.apply([], arr.map(fn)); }
function intersperse(arr, el) { return concatMap(arr, x => [el, x]).slice(1); }
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    It's kind of sad [how slow the "elegant" solutions are](https://jsbenchit.org/?src=052cd5e627d7b8aeb3a1b0f67449a62c). I expected that given what they are doing, but still... – gman Oct 24 '22 at 22:27
  • @gman Looks like Firefox is optimising `flatMap` much better than Chrome… I'm also surprised to see `[].concat.apply([], arr.map(fn))` being faster than `.flatMap()` – Bergi Oct 24 '22 at 22:38
3

Immutable solution

When reducing an array the reduce function should not mutate the array but return a new value (in this case a new array). That way the changes will be only applied to the returned array and not the original one and side effects will be avoided.

const insertBetween = (insertee, array) => array.reduce(
    (acc, item, i, { length }) => {
        if (i && i < length) {
            return [...acc, insertee, item];
        }
        return [...acc, item];
    },
    []
);
Iddan Aaronsohn
  • 1,030
  • 9
  • 11
  • 3
    I think that's instantiating a ton of intermediate arrays. – mpen Jul 27 '17 at 20:38
  • In engines that support tail call optimization you could do it the LISPy way: `(insertee, array) => array.length <= 1 ? array : [array[0], insertee, ...insertBetween(insertee, array.slice(1))]` – Andy Jun 25 '20 at 16:55
  • 1
    Cute but this is pretty horribly [inefficient](https://jsbenchit.org/?src=052cd5e627d7b8aeb3a1b0f67449a62c) solution – gman Oct 24 '22 at 22:36
2

Ramda has intersperse method that:

Creates a new list with the separator interposed between elements.

Code:

R.intersperse({name: 'separator'}, ['one', 'two', 'three']);

Result:

[
    'one',
    {name: 'separator'},
    'two',
    {name: 'separator'},
    'three'
]
cimak
  • 1,765
  • 3
  • 15
  • 18
1

You can achieve this using reduce (it is also immutable).

const insertBetween = (insertion, array) =>
    array.reduce(
        (newArray, member, i, array) =>
            i < array.length - 1 
                ? newArray.concat(member, insertion) 
                : newArray.concat(member),
        []
    );

const result = insertBetween('and', [1, 2, 3]);

console.log(result);

// outputs;
// [
//   1, 
//   'and', 
//   2, 
//   'and', 
//   3
// ]

Or in older JS syntax;

function insertBetween(insertion, array) {
    const indexOfLastItem = array.length - 1;
    return array.reduce(withInsertion, []);

    function withInsertion(newArray, item, index, array) {
        return index < indexOfLastItem 
            ? newArray.concat(item, insertion) 
            : newArray.concat(item);
    }
}

const result = insertBetween('and', [1, 2, 3]);

console.log(result);

// outputs;
// [
//   1, 
//   'and', 
//   2, 
//   'and', 
//   3
// ]
Jamie Mason
  • 4,159
  • 2
  • 32
  • 42
1

I really in favor of @Vidul 's comments, which is very logical and concise! I myself also came up with splice(), but missed %. However, most of the braces seem unnecessary as an oneliner. It can be further simplified as

for (var i = 0; i < a.length; i++) if (i % 2) a.splice(i, 0, {value: 255});
Drew Neon
  • 11
  • 1
1
const arr = ['apple', 'banana', 'orange'];

arr.flatMap(item => [item, ', ']).slice(0, -1);
// ["apple", ", ", "banana", ", ", "orange"]
sarimarton
  • 151
  • 1
  • 7
0
function insertObject(arr, obj) {
  var result = [];

  function insert(element, index) {
    result.push(element);
    if (index + 1 < arr.length) {
      result.push(obj);
    }
  }
  arr.forEach(insert);
  return result;
}
var a = [1, 2, 3, 4];
insertObject(a, {
  test: 'test'
});
0

Using splice as Kamen suggests, you could do something like:

const numSeparators = arr.length - 1;
for (let i = 1; i <= numSeparators; i++) {
    const index = (i * 2) - 1;
    arr.splice(index, 0, { value: 255 });
}
0

for a simple purely functional way I suggest doing it this way:

const magicArrayJoin = (array, el) =>
  array.length ?
    array.slice(1).reduce((acc, cur) => acc.concat([el, cur]), [array[0]]) :
    []

p.n. this way is not the most performant one in javascript

Amin_mmz
  • 394
  • 4
  • 14
0

This worked for me:

 a.map(val => [val, {value: 255}]).flat()
Matthias
  • 13,607
  • 9
  • 44
  • 60
Grimor
  • 32
  • 1
-1

ES6:

const arrayWithSeparator = array.reduce((a, i) => a.length ? a.push(separator) && a.push(i) && a : a.push(u) && a, [])
Felipe Baytelman
  • 544
  • 3
  • 12