web-dev-qa-db-ja.com

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(', '));
22
Ry-

リストとして返される部分的な結果をフラット化しようとしないため、フラットマップはあなたのケースでは役に立ちませんが、おそらく単一の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

また、配列を1回だけ反復するため、スライスを作成してからそれらを合計するソリューションよりもパフォーマンスが少し高くなる可能性があります。

または、同じ配列を再利用する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] 
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 ]

初期アキュムレータを使用しないプログラムが必要な場合は、代わりに入力配列の最初の要素を使用できます。このバリエーションは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 ]
6
Thank you

すべてのステップで現在の値を前の結果に追加するだけでよいので、単純な削減を使用できます。

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());
4
Pablo Lozano

速いまたはより効率的なの方法があると尋ねた場合、他の答えで十分です。

ただし、現在のソリューションと同様のものは、マッピング関数として表現すると読みやすく、宣言的であると主張します。

具体的には、「各値をそれ自体に加えて、配列内の以前のすべての値にマップする」のようなものです。

質問で行ったように、フィルターを使用することもできますが、スライスの方が明確だと思います。

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))
4
Steve

変数で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))
4
Code Maniac

外部のアキュムレータ変数を保持している場合は、mapを直接使用できます。

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

let acc = 0;
const prefixSum = x.map(x => acc += x);

console.log(prefixSum);
2
Joe23

1つのオプションは、スライスされた部分配列を合計するために内部で.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);
1

それぞれにを使用し、配列をスライスして要素を1つずつ取得し、それらをすべて_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))_
0
weegee

これは、再帰関数を使用した簡単な答えです。

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