admin管理员组

文章数量:1295325

Is there a function that lets me concat several arrays, with delimiters between them (the delimiters are also arrays), similarly to how join works but not restricted to strings?

The function can be standard JS or part of a major library such as lodash (which is why it's referenced in the tags).

Here is an example of usage:

let numbers = [[1], [2], [3]];
let result = _.joinArrays(numbers, [0]);
console.log(result); 
//printed: [1, 0, 2, 0, 3]

This is analogous to:

let strings = ["a", "b", "c"];
let result = strings.join(",");
console.log(result);
//printed: "a,b,c";

However, join can't be used because it turns values into strings, which I don't want to happen.

But it works for any type.

Is there a function that lets me concat several arrays, with delimiters between them (the delimiters are also arrays), similarly to how join works but not restricted to strings?

The function can be standard JS or part of a major library such as lodash (which is why it's referenced in the tags).

Here is an example of usage:

let numbers = [[1], [2], [3]];
let result = _.joinArrays(numbers, [0]);
console.log(result); 
//printed: [1, 0, 2, 0, 3]

This is analogous to:

let strings = ["a", "b", "c"];
let result = strings.join(",");
console.log(result);
//printed: "a,b,c";

However, join can't be used because it turns values into strings, which I don't want to happen.

But it works for any type.

Share edited Sep 26, 2016 at 8:02 GregRos asked Sep 26, 2016 at 7:45 GregRosGregRos 9,1433 gold badges42 silver badges67 bronze badges 8
  • 1 please add some more examples. – Nina Scholz Commented Sep 26, 2016 at 7:46
  • 1 The functional name for what you want is to intersperse the array with another element. It is currently a feature-request for Lodash, so go upvote it if you want to see it added to the library! – castletheperson Commented Sep 26, 2016 at 7:55
  • @4castle Oh cool. Maybe I'll submit a pull request. Thank you!! – GregRos Commented Sep 26, 2016 at 8:03
  • Probable duplicate of this question. – Redu Commented Sep 26, 2016 at 8:21
  • Yeah, fairly similar. Not sure of the full extent of the generator-based answer. I mean holy crap... punching that into babel.js is mindbogglingly over plicated in it's output. – TylerY86 Commented Sep 26, 2016 at 8:43
 |  Show 3 more ments

7 Answers 7

Reset to default 9

You could simply use array.reduce to concat the arrays, and push what ever you want to use as your delimiter.

let numbers = [[1], [2], [3]];

let n = numbers.reduce((a, b) => a.concat(0, b))

console.log(n)

Matrix Interspersion

Here's the full monty. Go nuts.

var numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
],
  delimiters = [
  ',', '-', 'x'
];

// matrix interspersion, delimiters into numbers's children
// the rank/order/whatevs of the matrix can be arbitrary and variable
numbers.forEach((x, i) => {
  for (var j = 1, l = x.length; j <= l; j+=2 )
    x.splice(j, 0, delimiters[i]);
})

alert( "Matrix interspersed: " + JSON.stringify(numbers) );

// normal interspersion, a static delimiter into numbers
for (var j = 1, l = numbers.length; j <= l; j+=2 )
    numbers.splice(j, 0, ' AND ');

alert( "Outer array interspersed: " + JSON.stringify(numbers) );

// flattening a 2 rank array into a single array
var flattened = Array.prototype.concat.apply([], numbers);

alert( "Flattened: " + JSON.stringify(flattened) );

var result = [].concat.apply([], numbers);

console.log(result)

Here is an implementation of a function that does this, with some extra logic, in case someone else wants to do it. I was really asking about whether this exists already.

Requires lodash.

export function intersperse(arrs, delimeter) {
    let joined = [];
    for (var i = 0; i < arrs.length; i++) {
        let arr = arrs[i];
        if (!arr) continue; //handle sparse arrays
        joined.push(...arr);
        if (i === arrs.length - 1) break;
        if (_.isFunction(delimeter)) {
            joined.push(...delimeter());
        } else if (_.isArray(delimeter)) {
            joined.push(...delimeter);
        } else {
            throw new Error("unknown type");
        }
    }
    return joined;
}

You could use Array#reduce and return an array with the items and glue if necessary.

const join = (array, glue) => array.reduce((a, b) => a.concat(glue, b));

var numbers = [[1], [2], [3]];

console.log(join(numbers, [0]));
console.log(join(numbers, [42, 43]));
console.log(join([[1]], [0]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Many good answers here including 4Castle's ments. As for a change I would like to develop a generic Array.prototype.intersperse() for this job.

In functional JS I could e up with 3 alternatives for this job;

Array.prototype.intersperse_1 = function(s){
  return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s));
};
Array.prototype.intersperse_2 = function(s){
  return this.reduce((p,c,i) => (i ? p.push(s,c) : p.push(c),p),[]);
};
Array.prototype.intersperse_3 = function(s){
  return this.reduce((p,c,i) => i ? p.concat([s],[c]) : p.concat([c]),[]);
};

You should stay away from the 3rd one since .concat() is one of the most expensive operations in functional JS. It will not be able to plete the job for even 100K items.

On the other hand while always being slightly faster in small size arrays, the 1st one turns out to be 2x or even more faster than the 2nd in very large arrays both in FF and Chrome. i.e. 1st intersperses a 10M item array in less than 1000 msec while for the same job the 2nd takes like 2000-2500 msec. Handling this size of course wouldn't be possible with 3rd at all.

So in this particular case it will probably be more expensive pared to the tailored solutions since we have to map the result into primitive values but i guess it's still worth noting the following code. I am sure .concat() adopting tailored solutions will fall behind this when the array length is beyond a certain figure.

Array.prototype.intersperse = function(s){
  return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s));
}

var arr = [[1],[2],[3]],
 result = arr.intersperse([0])
             .map(e => e[0]);
console.log(JSON.stringify(result));

As already mentioned here a few times, the simplest way to do this is

function intercalate(glue, arr){
    return arr.reduce((acc, v) => acc.concat(glue, v));
}

but this is not the best way, since it creates with every iteration a new (intermediate) array, that is then thrown away. This doesn't matter for this short array of values, but if you ever intend to use this on a longer Array, you might notice the impact.

Better would be to create one Array and push the values into that, as they e in.

function intercalate(glue, arr){
    const push = (acc, v) => (Array.isArray(v)? acc.push(...v): acc.push(v), acc);
    return arr.reduce((acc, v, i) => push(i===0? acc: push(acc, glue), v), []);
}

But since this Array is gradually increasing it may still need to allocate a bigger chunk of memory and copy the data. These tasks ar very performant, but still unnecessary (imo); we can do better.

We first create a list containing all Arrays and the delimiter in between, and flatten this by using concat.apply([], list). Therefore we produce one intermediate Array, who's size we can pute ahead of time, and the rest is the problem of Array.concat, and it's underlying implementation.

function intersperse(delimiter, arr){
    if(!arr.length) return [];
    let j = 0, push = (acc, v) => (acc[j++] = v, acc);
    return arr.reduce((acc, v) => push(j===0? acc: push(delimiter, glue), v), Array(2*arr.length-1));
}
//or
function intersperse(delimiter, arr){
    if(!arr.length) return [];
    var out = Array(2*arr.length-1);
        out[0] = arr[0];
    for(var i=1, j=1; j<out.length;){
        out[j++] = delimiter;
        out[j++] = arr[i++];
    }
    return out;
}

//and

function intercalate(glue, arr){
    var emptyArray = [];
    return arr.length? 
        emptyArray.concat.apply(emptyArray, intersperse(glue, arr)): 
        emptyArray;
}

Wich version will be the best/fastest, in the end, is not that easy to tell, since it may depend on the passed values, and wether or not the JIT piler optimizes the s*** out of it. I made my points, it's up to you to choose wich version/implementation you use.

To the first version: you may prefer to not put this into a lib at all, but write it mostly inline (it's short and simple enough for that), therefore the JIT-piler may not try to find some mon types between the different calls (#monomorphic function/code) and therefore optimize each occurance seperately. On the other hand, this could be premature optimization. It's up to you.

本文标签: javascriptJoin sequence of arrays with array delimeters (quotinterspersequot)Stack Overflow