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

时间:2019-06-10 00:11:37

标签: javascript arrays functional-programming prefix-sum

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

给定一个数组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))

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

11 个答案:

答案 0 :(得分: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(', '));

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

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

答案 1 :(得分: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 ]

答案 2 :(得分: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

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

或带有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] 

答案 3 :(得分:4)

您可以简单地使用带有变量的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 :(得分:4)

您只需要在每个步骤中将当前值添加到上一个结果中,这样就可以使用简单的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());

答案 5 :(得分:4)

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

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

特别是类似于“将每个值映射到自身以及数组中所有以前的值”。

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

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

答案 6 :(得分: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);

答案 7 :(得分:1)

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

interface Drawable{  
    public void draw();  
}  

public class Exampleinterface {  
    public static void main(String[] args) {  
        int width=10;  


        Drawable d=new Drawable(){  
            public void draw(){System.out.println("Drawing "+width);}  
        };  

        d.draw();  
    }  
}

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

一种方法是对每个数组使用,然后对数组进行切片以逐个获取元素,然后按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))

答案 10 :(得分:0)

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

INFO  - Starting container for site
INFO  - docker run -d -p 2747:8000 --name azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7 -e WEBSITE_SITE_NAME=azapp-rio-analyse-k6xbl -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=azapp-rio-analyse-k6xbl-dev.azurewebsites.net -e WEBSITE_INSTANCE_ID=945195e1f60010c33c817fd7ae1c28de08dc486224321f51d599531c29bb5725 -e HTTP_LOGGING_ENABLED=1 appsvc/python:3.8_20200707.6
INFO  - Initiating warmup request to container azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7_msiProxy for site azapp-rio-analyse-k6xbl__6cdd
INFO  - Container azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7_msiProxy for site azapp-rio-analyse-k6xbl__6cdd initialized successfully and is ready to serve requests.
INFO  - Initiating warmup request to container azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7 for site azapp-rio-analyse-k6xbl__6cdd


  _____
  /  _  \ __________ _________   ____
 /  /_\  \___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/

A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
Python 3.8.3
Note: Any data outside '/home' is not persisted
Starting OpenBSD Secure Shell server: sshd.
App Command Line not configured, will attempt auto-detect
Launching oryx with: create-script -appPath /home/site/wwwroot -output /opt/startup/startup.sh -virtualEnvName antenv -defaultApp /opt/defaultsite
Found build manifest file at '/home/site/wwwroot/oryx-manifest.toml'. Deserializing it...
Build Operation ID: |mt6xPWP5VVs=.cc9e7dda_
Oryx Version: 0.2.20200706.2, Commit: 42be45d884938c3c818ba08e9e4760b1136fd9b3, ReleaseTagName: 20200706.2
Detected an app based on Django
Generating `gunicorn` command for 'main.wsgi'
Writing output script to '/opt/startup/startup.sh'
Using packages from virtual environment pythonenv3.8 located at /home/site/wwwroot/pythonenv3.8.
Updated PYTHONPATH to ':/home/site/wwwroot/pythonenv3.8/lib/python3.8/site-packages'
[2020-09-13 16:29:57 +0000] [37] [INFO] Starting gunicorn 20.0.4
[2020-09-13 16:29:57 +0000] [37] [INFO] Listening at: http://0.0.0.0:8000 (37)
[2020-09-13 16:29:57 +0000] [37] [INFO] Using worker: sync
[2020-09-13 16:29:57 +0000] [39] [INFO] Booting worker with pid: 39
INFO:opencensus.ext.postgresql.trace:Integrated module: postgresql
[2020-09-13 18:30:13 +0200] [39] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/arbiter.py", line 583, in spawn_worker
    worker.init_process()
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/workers/base.py", line 119, in init_process
    self.load_wsgi()
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/workers/base.py", line 144, in load_wsgi
    self.wsgi = self.app.wsgi()
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 49, in load
    return self.load_wsgiapp()
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/app/wsgiapp.py", line 39, in load_wsgiapp
    return util.import_app(self.app_uri)
  File "/opt/python/3.8.3/lib/python3.8/site-packages/gunicorn/util.py", line 358, in import_app
    mod = importlib.import_module(module)
  File "/opt/python/3.8.3/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/site/wwwroot/main/wsgi.py", line 16, in <module>
    application = get_wsgi_application()
  File "/home/site/wwwroot/django/core/wsgi.py", line 12, in get_wsgi_application
    django.setup(set_prefix=False)
  File "/home/site/wwwroot/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/home/site/wwwroot/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/home/site/wwwroot/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/opt/python/3.8.3/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/site/wwwroot/users/models.py", line 25, in <module>
    class ContactInfo(models.Model):
  File "/home/site/wwwroot/users/models.py", line 29, in Search
    data = models.JSONField(encoder=DjangoJSONEncoder, null=True, db_index=True)
AttributeError: module 'django.db.models' has no attribute 'JSONField'
[2020-09-13 18:30:13 +0200] [39] [INFO] Worker exiting (pid: 39)
[2020-09-13 16:30:13 +0000] [37] [INFO] Shutting down: Master
[2020-09-13 16:30:13 +0000] [37] [INFO] Reason: Worker failed to boot.

ERROR - Container azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7 for site azapp-rio-analyse-k6xbl__6cdd has exited, failing site start
ERROR - Container azapp-rio-analyse-k6xbl__6cdd_0_ee823bd7 didn't respond to HTTP pings on port: 8000, failing site start. See container logs for debugging.
INFO  - Stopping site azapp-rio-analyse-k6xbl__6cdd because it failed during startup.