30

Say I have an array var arr = [1, 2, 3], and I want to separate each element by an element eg. var sep = "&", so the output is [1, "&", 2, "&", 3].

Another way to think about it is I want to do Array.prototype.join (arr.join(sep)) without the result being a string (because the elements and separator I am trying to use are Objects, not strings).

Is there a functional/nice/elegant way to do this in either es6/7 or lodash without something that feels clunky like:

_.flatten(arr.map((el, i) => [el, i < arr.length-1 ? sep : null])) // too complex

or

_.flatten(arr.map(el => [el, sep]).slice(0,-1) // extra sep added, memory wasted

or even

arr.reduce((prev,curr) => { prev.push(curr, sep); return prev; }, []).slice(0,-1)
// probably the best out of the three, but I have to do a map already
// and I still have the same problem as the previous two - either
// inline ternary or slice

Edit: Haskell has this function, called intersperse

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Aaron_H
  • 1,623
  • 1
  • 12
  • 26

18 Answers18

28

Using a generator:

function *intersperse(a, delim) {
  let first = true;
  for (const x of a) {
    if (!first) yield delim;
    first = false;
    yield x;
  }
}

console.log([...intersperse(array, '&')]);

Thanks to @Bergi for pointing out the useful generalization that the input could be any iterable.

If you don't like using generators, then

[].concat(...a.map(e => ['&', e])).slice(1)
  • 13
    [`flatMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) makes this even easier: `a.flatMap(e => ['&', e]).slice(1)` – baseten Feb 14 '20 at 11:35
12

A spread and explicit return in reducing function will make it more terse:

const intersperse = (arr, sep) => arr.reduce((a,v)=>[...a,v,sep],[]).slice(0,-1)
// intersperse([1,2,3], 'z')
// [1, "z", 2, "z", 3]
  • 1
    Typescript version: ```function intersperse(arr: T[], sep: T): T[] { return arr.reduce((a: T[], v: T) => [...a, v, sep], []).slice(0, -1); } ``` – kalzen Feb 02 '22 at 04:31
8

In ES6, you'd write a generator function that can produce an iterator which yields the input with the interspersed elements:

function* intersperse(iterable, separator) {
    const iterator = iterable[Symbol.iterator]();
    const first = iterator.next();
    if (first.done) return;
    else yield first.value;
    for (const value of iterator) {
        yield separator;
        yield value;
    }
}

console.log(Array.from(intersperse([1, 2, 3], "&")));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
5

One straightforward approach could be like feeding the reduce function with an initial array in size one less than the double of our original array, filled with the character to be used for interspersing. Then mapping the elements of the original array at index i to 2*i in the initially fed target array would do the job perfectly..

In this approach i don't see (m)any redundant operations. Also since we are not modifying any of the array sizes after they are set, i wouldn't expect any background tasks to run for memory reallocation, optimization etc. One other good part is using the standard array methods since they check all kinds of mismatch and whatnot.

This function returns a new array, in which the called upon array's items are interspersed with the provided argument.

var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Array.prototype.intersperse = function(s){
  return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s));
}
document.write("<pre>" + JSON.stringify(arr.intersperse("&")) + "</pre>");
Redu
  • 25,060
  • 6
  • 56
  • 76
2

Using reduce but without slice

var arr = ['a','b','c','d'];
var lastIndex = arr.length-1;
arr.reduce((res,x,index)=>{
   res.push(x);
   if(lastIndex !== index)
    res.push('&');
  return res;
},[]);
Jagdish Idhate
  • 7,513
  • 9
  • 35
  • 51
2

Now that Array has a flatMap function, this is the cleanest I've found:

arr.flatMap((el, i) => i == 0 ? [el] : [sep, el])

Or, as a method on Arrays:

Array.prototype.intersperse = function(sep) {
  return this.flatMap((el, i) => i == 0 ? [el] : [sep, el]);
}

It works:

[1,2,3].intersperse("&")
// [1, '&', 2, '&', 3]
Brian Hempel
  • 8,844
  • 2
  • 24
  • 19
1

If you have Ramda in your dependencies or if willing to add it, there is intersperse method there.

From the docs:

Creates a new list with the separator interposed between elements.

Dispatches to the intersperse method of the second argument, if present.

R.intersperse('n', ['ba', 'a', 'a']); //=> ['ba', 'n', 'a', 'n', 'a']

Or you can check out the source for one of the ways to do it in your codebase. https://github.com/ramda/ramda/blob/v0.24.1/src/intersperse.js

Community
  • 1
  • 1
zubko
  • 1,718
  • 25
  • 28
1

You could use Array.from to create an array with the final size, and then use the callback argument to actually populate it:

const intersperse = (arr, sep) => Array.from(
    { length: Math.max(0, arr.length * 2 - 1) }, 
    (_, i) => i % 2 ? sep : arr[i >> 1]
);
// Demo:
let res = intersperse([1, 2, 3], "&");
console.log(res);
trincot
  • 317,000
  • 35
  • 244
  • 286
1

ONE-LINER and FAST

const intersperse = (ar,s)=>[...Array(2*ar.length-1)].map((_,i)=>i%2?s:ar[i/2]);

console.log(intersperse([1, 2, 3], '&'));
nkitku
  • 4,779
  • 1
  • 31
  • 27
0

javascript has a method join() and split()

var arr = ['a','b','c','d'];
arr = arr.join('&');
document.writeln(arr);

Output should be: a&b&c&d

now split again:

arr = arr.split("");

arr is now:

arr = ['a','&','b','&','c','&','d'];
Bryan
  • 100
  • 6
  • 3
    As I mentioned above, the native join method doesn't work as I am not using strings. I'll update the question with a different example. – Aaron_H May 10 '16 at 03:31
  • 1
    Let's just hope there are no array elements that have a "&" in their strings. – trincot Aug 13 '20 at 13:41
0
if (!Array.prototype.intersperse) {
  Object.defineProperty(Array.prototype, 'intersperse', {
    value: function(something) {
      if (this === null) {
        throw new TypeError( 'Array.prototype.intersperse ' + 
          'called on null or undefined' );
      }
      var isFunc = (typeof something == 'function')

      return this.concat.apply([], 
        this.map(function(e,i) { 
          return i ? [isFunc ? something(this[i-1]) : something, e] : [e] }.bind(this)))
    }
  });
}
Carter Cole
  • 918
  • 9
  • 16
0

you can also use the following:

var arr =['a', 'b', 'c', 'd'];
arr.forEach(function(element, index, array){
    array.splice(2*index+1, 0, '&');
});
arr.pop();
0

My take:

const _ = require('lodash');

_.mixin({
    intersperse(array, sep) {
        return _(array)
            .flatMap(x => [x, sep])
            .take(2 * array.length - 1)
            .value();
    },
});

// _.intersperse(["a", "b", "c"], "-")
// > ["a", "-", "b", "-", "c"]
tokland
  • 66,169
  • 13
  • 144
  • 170
0

const arr = [1, 2, 3];

function intersperse(items, separator) {
  const result = items.reduce(
    (res, el) => [...res, el, separator], []);
  result.pop();
  return result;
}

console.log(intersperse(arr, '&'));
Yangshun Tay
  • 49,270
  • 33
  • 114
  • 141
0

A few years later, here's a recursive generator solution. Enjoy!

const intersperse = function *([first, ...rest], delim){
    yield first;
    if(!rest.length){
      return;
    }
    yield delim;
    yield * intersperse(rest, delim);
};
console.log([...intersperse(array, '&')]);
John Henry
  • 2,419
  • 2
  • 20
  • 22
  • 1
    I want to upvote this on principle, but without tail call optimization in the runtime, it's probably not the best option. – solarshado Dec 12 '19 at 19:55
0
export const intersperse = (array, insertSeparator) => {
  if (!isArray(array)) {
    throw new Error(`Wrong argument in intersperse function, expected array, got ${typeof array}`);
  }

  if (!isFunction(insertSeparator)) {
    throw new Error(`Wrong argument in intersperse function, expected function, got ${typeof insertSeparator}`);
  }

  return flatMap(
    array,
    (item, index) => index > 0 ? [insertSeparator(item, index), item] : [item] // eslint-disable-line no-confusing-arrow
  );
};
Jakub Kubista
  • 101
  • 10
0

Here is also a version with reduce only:

const intersperse = (xs, s) => xs.reduce((acc, x) => acc ? [...acc, s, x] : [x], null)

const a = [1, 2, 3, 4, 5]
console.log(intersperse(a, 0))
// [1, 0, 2, 0, 3, 0, 4, 0, 5]
contramao
  • 51
  • 1
  • 2
-4

Updated for objects not using join method:

for (var i=0;i<arr.length;i++;) {
    newarr.push(arr[i]);
    if(i>0) { 
      newarr.push('&'); 
     }    
}

newarr should be:

newarr = ['a','&','b','&','c','&','d']; 
Bryan
  • 100
  • 6
  • THis reverses the order of the array elements. –  May 10 '16 at 06:52
  • 1
    First, this generates a syntax error due to the extra semicolon at the end of the `for` statement. Second, it results in no ampersand after the first element and an extra ampersand at the end. –  May 11 '16 at 02:26