JavaScript数组中在所有元素之间插入元素的简洁方法?

30

假设我有一个数组 var arr = [1, 2, 3],我想用一个元素(例如var sep = "&")分隔每个元素,使输出结果为[1, "&", 2, "&", 3]

另一种思考方式是,我想要执行 Array.prototype.join (arr.join(sep)),但结果不应该是字符串(因为我尝试使用的元素和分隔符是对象,而不是字符串)。

在ES6/7或Lodash中是否有一种优美的方式来实现这一点,而无需使用像下面这样笨拙的方法:

_.flatten(arr.map((el, i) => [el, i < arr.length-1 ? sep : null])) // too complex
或者
_.flatten(arr.map(el => [el, sep]).slice(0,-1) // extra sep added, memory wasted

甚至可以

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

编辑:Haskell有一个名为intersperse的函数。

18个回答

28

使用生成器:

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

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

感谢 @Bergi 指出了一个有用的概括,即输入可以是任何可迭代对象。

如果您不喜欢使用生成器,则

[].concat(...a.map(e => ['&', e])).slice(1)

13
flatMap使这个过程更加简单:a.flatMap(e => ['&', e]).slice(1)。 其中,flatMap会把每一个元素映射为一个数组,然后将这些数组拼接成一个新的数组。在这里,我们将每个元素e映射为一个包含'&'e的数组,然后取出从索引1开始的子数组,以去掉第一个无用的'&'元素。 - baseten

12

在缩短函数的同时,使用展开和显式返回会使其更加简洁:

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
function intersperse(arr: T[], sep: T): T[] { return arr.reduce((a: T[], v: T) => [...a, v, sep], []).slice(0, -1); } - kalzen

8
在ES6中,您可以编写一个生成器函数,该函数可以生成一个迭代器,该迭代器产生带有插入元素的输入结果:
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], "&")));

3
请将文本从英语翻译成中文。仅返回翻译后的文本: - user663031

5
一种简单的方法是将reduce函数用一个大小比原始数组的两倍少一个的初始数组进行填充,其中包含要用于插入的字符。然后将原数组中索引为i的元素映射到最初提供的目标数组中的2 * i位置上,就可以完美地完成此任务。
在这种方法中,我没有看到很多冗余操作。另外,由于我们不会在设置它们之后修改任何数组大小,所以我不希望运行后台任务进行内存重新分配、优化等操作。 另一个好处是使用标准数组方法,因为它们检查各种不匹配等问题。
该函数返回一个新数组,其中调用数组的项与提供的参数交替出现。

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>");


2

现在Array有一个flatMap函数,这是我发现的最干净的写法:

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

或者,作为数组的一个方法:

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

它有效:

[1,2,3].intersperse("&")
// [1, '&', 2, '&', 3]

2

使用reduce但不使用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;
},[]);

1
你可以使用Array.from创建一个最终大小的数组,然后使用回调参数来实际填充它:

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);


1

一行代码和快速执行

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

console.log(intersperse([1, 2, 3], '&'));


1
如果您的依赖项中有Ramda,或者愿意添加它,那么可以使用其中的intersperse方法。
从文档中可以得知:
创建一个新列表,在元素之间插入分隔符。如果第二个参数的intersperse方法存在,则转发到该方法。
R.intersperse('n', ['ba', 'a', 'a']); //=> ['ba', 'n', 'a', 'n', 'a']

或者您可以查看其中一种在代码库中执行此操作的源代码。https://github.com/ramda/ramda/blob/v0.24.1/src/intersperse.js


0

这里也有一个仅使用reduce的版本:

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]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接