30

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
  • 5,771
  • 7
  • 27
  • 45
cimak
  • 1,451
  • 3
  • 13
  • 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

14 Answers14

24

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,588
  • 4
  • 26
  • 27
23

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,
);
Epeli
  • 17,342
  • 10
  • 65
  • 77
14

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,168
  • 5
  • 41
  • 53
  • 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
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
  • 572,313
  • 128
  • 898
  • 1,281
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
  • 770
  • 6
  • 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
3

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,451
  • 3
  • 13
  • 18
3

Use ES6 flatMap function.

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

insertBetween('+', [1, 2, 3]);
Leon Huang
  • 449
  • 5
  • 12
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
  • 3,924
  • 1
  • 29
  • 41
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
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
  • 306
  • 1
  • 13
-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
-2

Array.splice() should do the job like so:

a.splice(1, 0, {value : 255})

The first argument is the position at which you want to delete or insert elements, the second is the delete count, the third (optional) is the new element[s] you want to insert.

Kamen Minkov
  • 2,766
  • 1
  • 13
  • 19