纯javascript中的延迟分配

时间:2016-01-02 15:01:16

标签: javascript deferred-execution

this question中,我遇到了以下简化问题:

我们从具有value属性的Objects数组开始。我们想要为每个值计算它的总和的百分比,并将其作为属性添加到结构中。要做到这一点,我们需要知道值的总和,但这个总和不是事先计算的。

//Original data structure
[
  { "value" : 123456 },
  { "value" : 12146  }
]

//Becomes
[
  { 
    "value" : 123456,
    "perc"  : 0.9104
  },
  {
    "value" : 12146 ,
    "perc"  : 0.0896
  }
]

一个简单且可能最具可读性的解决方案是两次完成数据结构。首先我们计算总和,然后计算百分比并将其添加到数据结构中。

var i;
var sum = 0;
for( i = 0; i < data.length; i++ ) {
  sum += data[i].value;
}
for( i = 0; i < data.length; i++ ) {
  data[i].perc = data[i].value / sum;
}

我们是否可以只通过数据结构一次,并以某种方式告诉只有在知道整个总和后才能评估百分比表达式?

我主要对解决纯javascript问题的答案感兴趣。那就是:没有任何库。

6 个答案:

答案 0 :(得分:30)

self-modifying code的解决方案。

它将计算的函数f移动到迭代的末尾,然后通过链接函数来分配单个项目的百分比。

var data = [
        { "value": 123456 },
        { "value": 12146 },
    ];

data.reduceRight(function (f, a, i) { // reduceRight, i=0 is at the end of reduce required
    var t = f;                        // temporary save previous value or function
    f = function (s) {                // get a new function with sum as parameter
        a.perc = a.value / s;         // do the needed calc with sum at the end of reduce
        t && t(s);                    // test & call the old func with sum as parameter
    };
    f.s = (t.s || 0) + a.value;       // add value to sum and save sum in a property of f
    i || f(f.s);                      // at the last iteration call f with sum as parameter
    return f;                         // return the function
}, 0);                                // start w/ a value w/ a prop (undef/null don't work)

document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');

答案 1 :(得分:12)

此解决方案使用单个循环计算总和,并使用perc在每个元素上放置计算的getter属性:

function add_percentage(arr) {
  var sum = 0;
  arr.forEach(e => {
    sum += e.value;
    Object.defineProperty(e, "perc", {
       get: function() { return this.value / sum; }
    });
  });
}

直接推迟就是

function add_percentage(arr) {
  var sum = 0;
  arr.forEach(e => {
    sum += e.value;
    setTimeout(() => e.perc = e.value / sum);
  });
}

但是,到底这样做有什么意义呢?

答案 2 :(得分:6)

用一个较少的循环来实现这一点的方法是写出由所有可能的项组成的整个sum语句,例如

var sum = (data[0] ? data[0].value : 0) +
          (data[1] ? data[1].value : 0) +
          (data[2] ? data[2].value : 0) +
          ...
          (data[50] ? data[50].value : 0);

for( i = 0; i < data.length; i++ ) {
   data[i].perc = data[i].value / sum;
}

并不是说这实际上是一个真正的解决方案

您可以使用Array的reduce函数,但这仍然是后台循环,并且每个数组元素都有一个函数调用:

var sum = data.reduce(function(output,item){
   return output+item.value;
},0);
for( i = 0; i < data.length; i++ ) {
  data[i].perc = data[i].value / sum;
}

你可以使用ES6 Promise,但是你还在添加一堆函数调用

var data = [
  { "value" : 123456 },
  { "value" : 12146  }
]
var sum = 0;
var rej = null;
var res = null;
var def = new Promise(function(resolve,reject){
    rej = reject;
    res = resolve;
});
function perc(total){
    this.perc = this.value/total;
}

for( i = 0; i < data.length; i++ ) {
  def.then(perc.bind(data[i]));
  sum+=data[i].value;      
}
res(sum);

Perf Tests

  

加法声明
  10834196个
  ±0.44%
  最快

     

降低
  3552539个
  ±1.95%
  慢了67%

     

无极
  26325个
  ±8.14%
  慢100%

     

For loops
  9640800个
  ±0.45%
  慢了11%

答案 3 :(得分:1)

再看一下这个问题,使用堆栈最容易再现所需的效果。这里最简单的方法是创建递归函数而不是循环。递归函数将充当循环,并且解包可用于设置百分比属性。

/**
 * Helper function for addPercentage
 * @param arr Array of data objects
 * @param index
 * @param sum
 * @return {number} sum
 */
function deferredAddPercentage(arr, index, sum) {
  //Out of bounds
  if (index >= arr.length) {
    return sum;
  }

  //Pushing the stack
  sum = deferredAddPercentage(arr, index + 1, sum + arr[index].value);

  //Popping the stack
  arr[index].percentage = arr[index].value / sum;

  return sum;
}

/**
 * Adds the percentage property to each contained object
 * @param arr Array of data Objects
 */
function addPercentage(arr) {
  deferredAddPercentage(arr, 0, 0);
}


// ******

var data = [{
  "value": 10
}, {
  "value": 20
}, {
  "value": 20
}, {
  "value": 50
}];

addPercentage(data);

console.log( data );

它比2个简单的for循环表现差29%。扩展Patrick的JSPerf

答案 4 :(得分:1)

OP已经给出example of a recursive solution。虽然我认为非尾递归函数是完成此任务的理想方法,但我认为它们的实现有两个缺点:

  1. 它改变了其父范围的状态
  2. 非常具体,因此难以重复使用
  3. 我试图在不改变全局状态的情况下实现更通用的解决方案。请注意,我通常会通过组合几个较小的可重用函数来解决这个问题。然而,OP的条件是只有一个循环。 这是一个有趣的挑战,我的实施并不打算用于实际代码!

    我调用函数defmap,即延迟映射:

    &#13;
    &#13;
    const xs = [
      { "value" : 10 },
      { "value" : 20 },
      { "value" : 20 },
      { "value" : 50 }
    ];
    
    const defmap = red => map => acc => xs => {
      let next = (len, acc, [head, ...tail]) => {
        map = tail.length
         ? next(len, red(acc, head), tail)
         : map([], red(acc, head), len);
        
        return map(Object.assign({}, head));
      };
    
      return next(xs.length, acc, xs);
    };
    
    const map = f => (xs, acc, len) => o => xs.length + 1 < len
     ? map(f) (append(f(o, acc), xs), acc, len)
     : append(f(o, acc), xs);
    
    const append = (xs, ys) => [xs].concat(ys);
    
    const reducer = (acc, o) => acc + o.value;
    const mapper = (o, acc) => Object.assign(o, {perc: o.value / acc});
    
    console.log(defmap(reducer) (map(mapper)) (0) (xs));
    &#13;
    &#13;
    &#13;

答案 5 :(得分:0)

根据我的评论,如果没有有效地循环两次,我就看不到这样做的方法。

  1. 实际计算值
  2. 根据总数
  3. 评估每个值

    回答&#34;延期&#34;你问题的一部分,一个可能的解决方案,虽然速度较慢(只是因函数调用而猜?)而且可能不是你想要使用的(JSFiddle):

    else if ((seatChoice.substring(0,13)).equalsIgnoreCase("Backstage Pass") && seatChoice.length() == 14)
    

    输出:

    var data = [
      { value: 10 },
      { value: 20 },
      { value: 20 },
      { value: 50 }
    ];
    
    var total = 0;
    
    for (var i = 0; i < data.length; i++) {
      var current = data[i];
      total += current["value"];
      current.getPercent = function() { return this["value"] / total; };
    }
    
    for (var i = 0; i < data.length; i++) {
      var current = data[i];
      console.log(current.getPercent());
    }
    

    这样做的另一个好处就是在你需要它之​​前不会实际计算它,但是当它计算它时,会有更高的cpu成本(由于调用函数等)。

    可以通过将0.1 0.2 0.2 0.5 行更改为:

    来略微优化
    getPercent

    这将确保计算仅在第一次运行。 Updated Fiddle

    修改 我运行了一些测试(在我通过测试过多的迭代来崩溃铬之前忘了保存,但它们很容易复制)。 我得到了

    1. Sumurai初始方法(1000000个对象,值0 - > 9999999)= 2200ms
    2. 我的初始方法(相同)= 3800ms
    3. 我的&#34;优化&#34;方法(相同)= 4200ms