纯javascript中的延迟分配

Sum*_*ai8 14 javascript deferred-execution

这个问题中,我遇到了以下简化问题:

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

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

//Becomes
[
  { 
    "value" : 123456,
    "perc"  : 0.9104
  },
  {
    "value" : 12146 ,
    "perc"  : 0.0896
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Nin*_*olz 30

具有自修改代码的解决方案.

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

  • 我很遗憾破坏派对,但它是一个伪装的双循环,难以阅读,内存占用更大,并且它使得有争议的属性使用.尽管如此,这是一个有趣的答案,但我认为这里有太多的认可: - / (14认同)
  • 这是很酷的一个*混淆竞赛*,而是通过代码去后,我感到有点失望.很少有关于这个代码,你可以称之为"自我修改代码",除非你认为关闭是"自修改代码"?如果有一个`eval`里面的某个地方,也许吧.但这只是一个计算循环中总和的一种相当复杂的方法(顺便说一下,'forEach`会比`reduceRight`更合适,更少复杂)并将单个值推入隐式堆栈来运行递归,这也是多余的因为你已经拥有了阵列. (9认同)
  • @procrastinator,它只是为了展示什么是可能的,而不是强调足迹,内存或其他性能问题. (2认同)

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

一个直截了当的推迟就是

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

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


Pat*_*ans 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

你可以使用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);
Run Code Online (Sandbox Code Playgroud)

Perf测试

加成声明
10,834,196
±0.44%
最快

减少
3,552,539
±1.95%减少
67%

承诺
26,325
±8.14
%慢100%

对于循环
9,640,800
±0.45
%慢11%

  • 我不认为展开循环计数作为解决方案. (4认同)
  • 那个承诺的事情有点整洁,只是不必要的异步.`.perc`只能异步使用,并且应该是一个promise本身.顺便说一下,请不要调用promise变量`def`,并避免使用`rej`和`res`变量 - 只需将负责解析promise*的代码放在`Promise`构造函数回调中. (3认同)