web-dev-qa-db-fra.com

Existe-t-il une meilleure façon de faire des sommes partielles d'éléments de tableau en JavaScript?

Je me demande s'il existe un meilleur moyen de générer une solution plus performante pour les sommes partielles d'un tableau.

Étant donné un tableau, dites x = [ 0, 1, 2, 3, 4, 5 ], J'ai généré des sous-tableaux des éléments, puis calculé la somme de chaque tableau qui donne:

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

Le code complet est donc:

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

Je me demande si une carte plate ou une autre méthode de tableau aura une solution qui ne nécessite pas d'étendre chaque sous-réseau.

Beaucoup, en gardant un total cumulé:

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

Temps linéaire, en ligne. (Vous pouvez également produire un tableau directement; développez ci-dessous.)

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-

La carte plate ne sera pas utile dans votre cas, car vous n'essayez pas d'aplatir vos résultats partiels sous forme de listes, mais nous pouvons probablement essayer de résoudre votre problème en une seule réduire:

[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

Il itère également le tableau une seule fois, ce qui peut être un peu plus performant qu'une solution créant des tranches puis les additionnant.

Ou une version avec array.Push, qui réutilise le même tableau:

[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
Krzysztof Atłasik

Ci-dessous, scan prend une fonction de mappage f et un accumulateur initial 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 ]

Si vous avez besoin d'un programme qui ne prend pas d'accumulateur initial, le premier élément du tableau d'entrée peut être utilisé à la place. Cette variation est appelée 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 ]
// []

Des optimisations de performances peuvent être apportées et les problèmes de débordement de pile peuvent être corrigés, si nécessaire, le tout sans sacrifier le style fonctionnel -

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

Maintenant, exécutons-le avec une grande entrée -

// 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 ]

Cela dépend des procédures fonctionnelles génériques suivantes -

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
  )

Développez l'extrait ci-dessous pour vérifier les résultats dans votre propre navigateur -

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

Il vous suffit d'ajouter à chaque étape la valeur actuelle au résultat précédent, afin de pouvoir utiliser une réduction simple.

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

Si vous demandez s'il existe un moyen plus rapide ou plus efficace, les autres réponses sont suffisantes.

Cependant, je dirais que quelque chose de similaire à votre solution actuelle est plus facile à lire et plus déclaratif si nous le formulons comme une fonction de mappage.

Plus précisément, quelque chose comme "Mapper chaque valeur à elle-même plus toutes les valeurs précédentes dans le tableau".

Vous pouvez utiliser un filtre, comme vous l'avez fait dans votre question, mais je pense qu'une tranche est plus claire.

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

Vous pouvez simplement utiliser une boucle for avec une variable pour garder une trace de la dernière somme

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

Vous pouvez également utiliser la carte:

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

Il est possible d'utiliser directement la carte, si vous conservez une variable d'accumulateur externe:

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

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

console.log(prefixSum);
2
Joe23

Une option consiste à utiliser un seul .map qui utilise .reduce inside pour résumer le tableau partiel découpé:

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

Un moyen peut être d'utiliser chacun d'eux, puis de découper les tableaux pour obtenir les éléments un par un, puis de les additionner tous par array.reduce Vous pouvez le faire comme

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)

Nous obtenons [0] Puis [0,1] Puis [0,1,2] Puis [0,1,2,3] Et via [0,1,2].reduce((a, b) => a + b)) nous obtenons 3. Il suffit de pousser cela vers un nouveau tableau . Quelle est votre réponse.

Nous pouvons aller encore plus court en faisant cela. Pour moi, cela semble une solution très optimisée.

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

Ou avec .map, Vous pouvez

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

Voici une réponse simple en utilisant une fonction récursive.

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