问题
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
回答1:
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)
回答2:
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]
回答3:
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], "&")));
回答4:
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>");
回答5:
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'];
回答6:
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;
},[]);
回答7:
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
回答8:
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)))
}
});
}
回答9:
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();
回答10:
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"]
回答11:
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, '&'));
回答12:
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, '&')]);
回答13:
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'];
来源:https://stackoverflow.com/questions/37128624/terse-way-to-intersperse-element-between-all-elements-in-javascript-array