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

Pat*_*eme 25 javascript arrays functional-programming prefix-sum

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

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

[ 0, 1, 3, 6, 10, 15 ]
Run Code Online (Sandbox Code Playgroud)

因此,完整的代码是:

x.map((y,i)=>x.filter((t,j)=>j<=i))
 .map(ii=>ii.reduce((x,y)=>x+y,0))
Run Code Online (Sandbox Code Playgroud)

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

Ry-*_*Ry- 22

通过保持运行总计,可以达到很多效果:

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(', '));
Run Code Online (Sandbox Code Playgroud)

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

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(', '));
Run Code Online (Sandbox Code Playgroud)

  • 呃...`yield`是一个很弱的词。将每个项目添加到数组中,然后像男人一样“返回” **。 (6认同)
  • @LogicalBranch为什么?2019年的迭代器FTW,不是吗? (3认同)
  • 这是一个很好的命令性答案,但是,它不适合使用函数式编程标记的问题。这仅仅是针对特定问题的特定助手功能的1:1分配。零归纳,一无所获。 (2认同)

Tha*_*you 6

下面,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 ]
Run Code Online (Sandbox Code Playgroud)

如果您需要一个不带初始累加器的程序,则可以使用输入数组的第一个元素。这种变化称为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 ]
// []
Run Code Online (Sandbox Code Playgroud)


可以在不牺牲功能风格的情况下对性能进行优化,并可以解决堆栈溢出的麻烦-

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
              )
    )
Run Code Online (Sandbox Code Playgroud)

现在让我们投入大量资金来运行它-

// 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 ]
Run Code Online (Sandbox Code Playgroud)

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

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
  )
Run Code Online (Sandbox Code Playgroud)

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

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
              )
    )
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是O(n *(n + 1)/ 2)= O(n²)。“一半”是一个恒定因素。 (6认同)
  • `scan`很好。像在Haskell中那样在JavaScript中实现`scan`并不是一件好事,因为它给您二次方的时间复杂度,并且在不是特别大的数组上产生堆栈溢出。 (3认同)
  • @ Ry-,谢谢,但我看不到二次时间复杂度。中间数组肯定是浪费的,但是简单的程序精确地显示了需要做的事情。使用此类程序进行优化很容易,因此我通常将它们留给最了解其需求的最终用户使用。答案的更新显示了堆栈安全的“扫描”。感谢您的评论。 (2认同)

Cod*_*iac 6

您可以简单地使用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))
Run Code Online (Sandbox Code Playgroud)

您还可以使用地图:

let x = [0, 1, 2, 3, 4, 5]

let sum = (arr) => {
  let sum = 0
  return arr.map(current => sum += current )
}

console.log(sum(x))
Run Code Online (Sandbox Code Playgroud)


Krz*_*sik 6

平面映射在您的情况下将不会有用,因为您没有尝试将部分结果以列表的形式展平,但是我们可能可以尝试通过一个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
Run Code Online (Sandbox Code Playgroud)

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

或的版本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] 
Run Code Online (Sandbox Code Playgroud)

  • 没有什么是纯粹的功能。您拥有阵列。很好 (2认同)

Pab*_*ano 5

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

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());
Run Code Online (Sandbox Code Playgroud)


Cer*_*nce 1

一种选择是使用 single .map,它使用.reduceinside 对切片的部分数组求和:

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);
Run Code Online (Sandbox Code Playgroud)