繁体   English   中英

有没有更好的方法在 JavaScript 中对数组项进行部分求和?

[英]Is there a better way to do partial sums of array items in JavaScript?

我想知道是否有更好的方法来为数组的部分总和生成性能更好的解决方案。

给定一个数组x = [ 0, 1, 2, 3, 4, 5 ] ,我生成项目的子数组,然后计算每个数组的总和,它给出:

[ 0, 1, 3, 6, 10, 15 ]

所以完整的代码是:

x.map((y,i)=>x.filter((t,j)=>j<=i))
 .map(ii=>ii.reduce((x,y)=>x+y,0))

我想知道平面地图或其他一些数组方法是否有不需要扩展每个子数组的解决方案。

很多,通过保持运行总数:

 function* partialSums(iterable) { let s = 0; for (const x of iterable) { s += x; yield s; } } const x = [0, 1, 2, 3, 4, 5]; console.log(Array.from(partialSums(x)).join(', '));

线性时间,在线。 (您也可以直接生成数组;在下面展开。)

 const partialSums = arr => { let s = 0; return arr.map(x => s += x); }; const x = [0, 1, 2, 3, 4, 5]; console.log(partialSums(x).join(', '));

下面, scan采用映射函数f和初始累加器r -

 const scan = (f, r, [ x, ...xs ]) => x === undefined ? [ r ] : [ r, ...scan (f, f (r, x), xs) ] const add = (x, y) => x + y const print = (...vs) => vs .forEach (v => console .log (v)) const data = [ 0, 1, 2, 3, 4, 5 ] print ( scan (add, 0, data) , scan (Math.max, 3, data) , scan (add, 0, []) ) // [ 0, 0, 1, 3, 6, 10, 15 ] // [ 3, 3, 3, 3, 3, 4, 5 ] // [ 0 ]

如果您需要一个不带初始累加器的程序,则可以使用输入数组的第一个元素。 这种变化称为scan1 -

 const scan = (f, r, [ x, ...xs ]) => x === undefined ? [ r ] : [ r, ...scan (f, f (r, x), xs) ] const scan1 = (f, [ x, ...xs ]) => x === undefined ? [] : scan (f, x, xs) const add = (x, y) => x + y const print = (...vs) => vs .forEach (v => console .log (v)) const data = [ 0, 1, 2, 3, 4, 5 ] print ( scan1 (add, data) , scan1 (Math.max, data) , scan1 (Math.min, data) , scan1 (add, []) ) // [ 0, 1, 3, 6, 10, 15 ] // [ 0, 1, 2, 3, 4, 5 ] // [ 0, 0, 0, 0, 0, 0 ] // []


如有必要,可以进行性能优化并解决堆栈溢出问题,而无需牺牲功能风格 -

const scan = (f, init, xs) =>
  loop
    ( ( r = []
      , a = init
      , i = 0
      ) =>
        i >= xs.length
          ? push (a, r)
          : recur
              ( push (a, r)
              , f (a, xs[i])
              , i + 1
              )
    )

现在让我们用一个大输入来运行它 -

// BIG data!
const data =
  Array .from (Array (10000), (_, x) => x)

// fast and stack-safe
console .time ("scan")
const result = scan (add, 0, data)
console .timeEnd ("scan")
// scan: 8.07 ms

console .log (result)
// [ 0, 0, 1, 3, 6, 10, 15, ..., 49985001 ]

这取决于以下通用功能程序 -

const recur = (...values) =>
  ({ recur, values })

const loop = f =>
{ let r = f ()
  while (r && r.recur === recur)
    r = f (...r.values)
  return r
}

const push = (x, xs) =>
  ( xs .push (x)
  , xs
  )

展开下面的代码段以在您自己的浏览器中验证结果 -

 const recur = (...values) => ({ recur, values }) const loop = f => { let r = f () while (r && r.recur === recur) r = f (...r.values) return r } const push = (x, xs) => ( xs .push (x) , xs ) const scan = (f, init, xs) => loop ( ( r = [] , a = init , i = 0 ) => i >= xs.length ? push (a, r) : recur ( push (a, r) , f (a, xs[i]) , i + 1 ) ) const add = (x, y) => x + y const data = Array .from (Array (10000), (_, x) => x) console .time ("scan") const result = scan (add, 0, data) console .timeEnd ("scan") console .log (result) // [ 0, 0, 1, 3, 6, 10, 15, ..., 49995000 ]

平面地图在您的情况下没有用,因为您不会试图将作为列表的部分结果展平,但我们可能会尝试在单个reduce 中解决您的问题:

[0, 1, 2, 3, 4, 5]
.reduce(
   ([arr, sum], el) => { // We pass along array and running sum
       const next = sum + el
       return [[...arr, next], next]
   },
   [[], 0] // We need to seed our reduce with empty array and accumulator for calculating running sum
)[0] // Array containing array and the last sum is returned, so we need to take only the first element

它还只迭代数组一次,因此与创建切片然后对它们求和的解决方案相比,它的性能可能更高一些。

或者带有array.push的版本,它重用相同的数组:

[0, 1, 2, 3, 4, 5]
.reduce(
   ([arr, sum], el) => { // We pass along array and running sum
       const next = sum + el
       arr.push(next)
       return [arr, next]
   },
   [[], 0] // We need to seed our reduce with empty array and accumulator for calculating running sum
)[0] 

您可以简单地使用带有变量的for循环来跟踪最后的总和

 let x = [ 0, 1, 2, 3, 4, 5 ] let sum = (arr) => { let sum = 0 let final = [] for(let i=0; i<arr.length; i++){ sum+= arr[i] final.push(sum) } return final } console.log(sum(x))

您还可以使用地图:

 let x = [0, 1, 2, 3, 4, 5] let sum = (arr) => { let sum = 0 return arr.map(current => sum += current ) } console.log(sum(x))

您只需要在每一步中将当前值添加到前一个结果中,这样您就可以使用简单的 reduce。

 const array = [0, 1, 2, 3, 4, 5, 6]; const sums = array.reduce((acc,current,index) => { const prev = acc.length ? acc[index-1] : 0; acc.push(prev + current); return acc; },[]); console.log(sums.toString());

如果您问是否有更快更有效的方法,那么其他答案就足够了。

但是,我认为,如果我们将其表述为映射函数,则类似于您当前解决方案的内容更易于阅读且更具声明性。

具体来说,类似于“将每个值映射到自身加上数组中的所有先前值”。

您可以使用过滤器,就像您在问题中所做的那样,但我认为切片更清晰。

const x = [ 0, 1, 2, 3, 4, 5 ];

// A common generic helper function
const sum = (acc, val) => acc + val

const sums = x.map((val, i, self) => val + self.slice(0, i).reduce(sum, 0))

如果您保留外部累加器变量,则可以直接使用 map:

 const x = [ 0, 1, 2, 3, 4, 5 ]; let acc = 0; const prefixSum = x.map(x => acc += x); console.log(prefixSum);

一种选择是使用单个.map ,它在内部使用.reduce来总结切片的部分数组:

 const x = [0, 1, 2, 3, 4, 5]; const sum = (x, y) => x + y; const partialSums = x.map((_, i, arr) => arr.slice(0, i + 1).reduce(sum)); console.log(partialSums);

这是使用递归函数的简单答案。

var array = [ 0, 1, 2, 3, 4, 5 ];

function sumArray(arrayToSum, index){
    if(index < arrayToSum.length-1){
        arrayToSum[index+1] = arrayToSum[index] + arrayToSum[index+1];
        return sumArray(arrayToSum, index+1);
  }else
    return arrayToSum;

}
sumArray(array, 0);

console.log(array);

一种方法可以是使用 for each 然后将数组切片以一个一个地获取元素,然后通过array.reduce它们全部求和你可以这样做

 let x = [0, 1, 2, 3, 4, 5] let sum = [] x.forEach((_, index) => { index++; sum.push(x.slice(0, index).reduce((a, b) => a + b)) }) console.log(sum)

我们得到[0]然后[0,1]然后[0,1,2]然后[0,1,2,3]和通过[0,1,2].reduce((a, b) => a + b))我们得到 3。只需将其推送到一个新数组。 这是你的答案。

通过这样做,我们可以走得更短。 对我来说,这似乎是一个非常优化的解决方案。

 let ar = [0, 1, 2, 3, 4, 5] let s = 0 let arr = [] ar.forEach((n, i) => arr.push(s += n)) console.log(arr)

或者使用.map ,你可以

 let ar = [0, 1, 2, 3, 4, 5], s = 0 console.log(ar.map((n, i) => s += n))

最简单的答案是单行:如果 x 是 [0,1,2,3,4,5]

x.map(i=>s+=i, s=0)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM