javascript - 查找JS数组的总和,并排除所有NULL值

标签 javascript arrays

假设我有一个js对象数组,其中包含我的每月费用数据,如下所示

var data=[
    {
    "date": "2016-09-01",
    "bills": "34"            
    },{
    "date": "2016-09-02",
    "bills": "34" 
    },{
    "date": "2016-09-03",
    "bills": null
    },{
    "date": "2016-09-04",
    "bills": "34"
    },{
    "date": "2016-09-05",
    "bills": null
    },{
    "date": "2016-09-06",
    "bills": "34"            
    },{
    "date": "2016-09-07",
    "bills": "34" 
    },{
    "date": "2016-09-08",
    "bills": null
    },{
    "date": "2016-09-09",
    "bills": "34"
    },{
    "date": "2016-09-10",
    "bills": null
    }
   ];

  var totalAmount = function(response){        
    for(var i=0; i<response.length; i++ ){
      if (response.bills!==null){
      var totalSum = "";
      totalSum += response.bills;
      }
    }
    return totalSum;
  };
  totalAmount(data);


虚线表示其他日期的数据,并且您可以看到某些日期的帐单金额表示为整数,而某些日子的值为零。我可以使用零,但假设我的数组中有空值,并且我想查找一个月的总支出,该如何编写函数?选中此jsfiddle

最佳答案

您已经编写了遍历一件事并检查另一件事并添加另一件事的代码,这为您发现各种错误提供了很多机会!更加现代的编程风格不仅可以使您更紧凑地编写程序,还可以减少发生错误的机会,并帮助您(和其他人)通过阅读程序使自己确信程序是正确的。

我们将自上而下地进行处理。您要做的最基本的事情是创建总和。因此,我们将从最简单的程序开始:

sum(bills)


当然,现在我们必须实现sum,并弄清楚从哪里获取bills

求和

让我们立即摆脱sum。我们只循环一些数组,然后将数字加起来:

function sum(array) {
  var total = 0;
  for (var i = 0; i < array.length; i++) total += array[i];
  return total;
}


现在,我们有了一个不错的,干净的,通用的,显然正确的函数来累加一个数字列表,即使在新项目中,我们也可以随时随地放心使用它。它对账单一无所知。用程序设计术语来说,它是“解耦的”。

我们还可以使用JavaScript中的新功能for...of循环,使其更加简洁。 for...of直接为您提供数组的元素,而您不必担心索引。

function sum(array) {
  var total = 0;
  for (var num of array) total += num;
  return total;
}


请注意,我们不会通过检查null或将字符串转换为数字来弄乱此sum函数。我们假定,无论谁打电话给它,都已经负责提供干净的数字列表。这个概念称为“关注点分离”。我们将“获得和”的关注与“过滤/转换数字列表”的关注分开。

还有其他更花哨的方式来编写此代码,例如使用有用的reduce函数,但我们暂时将其跳过。

获取账单

接下来,当然,我们必须获取bills值以传递给sum。这只是一个数字数组,例如[34, 29, 72]。我们将使用功能data从输入getBills中提取票据,因此我们可以将顶级程序编写为

sum(getBills(data))


现在,我们要做的就是编写getBills!最明显的方法是编写另一个for循环:

function getBills(data) {
  var bills = [];
  for (var i = 0; i < data.length; i++) bills.push(data[i].bills);
  return bills;
}


还是再次提醒自己如何使用for...of

function getBills(data) {
  var bills = [];
  for (var obj of data) bills.push(obj.bills);
  return bills;
}


没关系,但是JavaScript实际上提供了一种方便的通用方式来实现这一点-通过对旧数组的每个元素应用一些规则,从旧数组创建新数组。这称为map,我们通过在旧数组上调用它来使用它:

newarray = oldarray.map(rule)


其中rule是一个函数,该函数接受旧数组中的每个元素,并返回一些值以放入新数组中。在这种情况下,旧数组中的元素是{"date": "2016-09-02", "bills": "34" }形式的小对象,我们要应用的规则是从该对象中提取bills属性的值。那真的很简单:

function extractBills(object) { return object.bills; }


所以现在我们可以简单地写为getBills

function getBills(data) {
  return data.map(extractBills);
}


这里发生了什么?我们要将一个函数作为参数传递给另一个函数?你能做到吗?是的,在JavaScript函数中是“一流的值”,就像传递数字1一样容易传递。您可以像在此处一样将一个函数传递给另一个函数,然后该函数可以“调用”我们传递给它的函数。在这种情况下,我们将一个函数传递给map,而map则在每个元素上调用该函数。此类函数通常称为“回调”,因为map是针对每个元素“回调”我们的函数。

整理账单清单:消除空值

我们快到了!唯一剩下的问题是摆脱一些小对象中bills属性的空值。无需将检查内容混入我们的其他代码中,而是将其单独处理。让我们编写一个名为nonNull的小函数,它使用一个旧数组并返回一个删除了所有null值的新数组。同样,最明显的写法是循环:

function nonNull(array) {
  var result = [];
  for (var i = 0; i < array.length; i++) {
    var value = array[i];
    if (value !== null) result.push(value);
  }
  return result;
}


或再次使用for...of

function nonNull(array) {
  var result = [];
  for (var value of array) if (value !== null) result.push(value);
  return result;
}


很好,但是事实证明,还有另一个方便的函数已经在执行此过程,称为“过滤”,可以从仅包含某些元素的旧数组创建新数组。此功能称为filter,显然足够了。为了告诉它要包含在新数组中的元素,我们再次给它提供一个规则,但是这次规则说明是要包含一个元素还是要排除它。该规则也被写为一个函数,该函数传递一个元素,并返回truefalse,表示包含或不包含。

我们的规则很简单:值是否为null?所以我们可以很容易地写成这样

function notNull(val) { return val !== null; }


现在我们可以说出一些数组中的空值

newarray = oldarray.filter(notNull);


通过在控制台中键入[1, null, 2].filter(notNull),我们可以轻松地以独立方式对其进行测试。

我们也可以这样写

newarray = oldarray.filter(Boolean)


它将内置的Boolean函数应用于每个元素,仅当元素为“ falsy”(即undefinednull,0或空字符串)时,它才会返回false(从而排除该元素)。在这种情况下,也可以。通过在控制台中键入Boolean(null)进行尝试。

修正清单中的更多问题:转换为数字

但是,还有一个问题:bills属性的值是字符串。但是sum旨在处理数字。同样,我们不想通过特殊的检查和转换来完善我们漂亮的sum函数。因此,我们想做一个单独的映射,将数组中的所有值转换为数字。同样,我们可以使用map

arrayOfNumbers = arrayOfStrings.map(Number)


这会在Number的每个元素上调用Number("34")内置函数(在控制台中通过键入arrayOfStrings尝试),并为我们提供一个数组,其中包含每个转换为数字的元素。

现在,我们拥有完成小程序所需的所有内容,这仅仅是

sum(getBills(data).filter(notNull).map(Number))


您可以从“内部”阅读此内容。换句话说,我们首先从数据中获取账单;这将是一个值,例如["34", ..., null]。然后,我们对该数组进行过滤以删除null。然后,我们映射此数组以将其值从字符串转换为数字。最后,我们在整个过程中调用sum。 Voilà,我们完成了。

现在,我们几乎可以将其作为常规的英语句子来阅读,它说


  getBills中的data,进行过滤以仅保留notNull,将它们映射到Number,然后获取整个内容的sum


我发现采用一种“文学编程”风格很有用,使用这种风格的注释使编写起来容易:

sum(                   // Find the sum of
  getBills(data)       // the bills from the data
   .filter(notNull)    // with nulls removed
   .map(Number)        // and strings converted to numbers
)


完成程序

现在我们的完整程序



var data=[
  {"date": "2016-09-01", "bills": "34"},
  {"date": "2016-09-02", "bills": "34"},
  {"date": "2016-09-08", "bills": null}
];

function sum(array) {
  var total = 0;
  for (var num of array) total += num;
  return total;
}

function extractBills(object) { return object.bills; }
function getBills(data)       { return data.map(extractBills); }
function notNull(val)         { return val !== null; }

function addUpBills(data)     { return sum(getBills(data).filter(notNull).map(Number)); }

console.log(addUpBills(data));





仅通过查看该程序,我们几乎可以知道它将会起作用,并按照我们想要的去做。该程序几乎不可能拥有原始代码中的那种错误,例如在循环内部而不是外部初始化一些变量。

但这一切必要吗?

在这种简单情况下,也许不是。您可以轻松地编写以下内容:

var total = 0;
for (var obj of data) total += +obj.bills;


利用一元+将字符串转换为数字,这也将null视为0。但是,随着您在编程中努力解决更复杂的问题,功能概念仍将为您提供良好的支持。

摘要

这是所谓的“函数式编程”的一个简单示例,因为我们正在组织逻辑,例如提取逻辑,将其转换,转换或过滤其他内容,转换为诸如extractBills之类的函数,然后使用函数,例如mapfilter,以逐步将这些函数应用于我们的数据以得到我们的输出。

通过函数式编程,我们经常发现自己是“自上而下”的工作。换句话说,我们没有编写一堆完整的代码,而是编写了一个简单,顶层的解决方案。通常,我们可以通过提供一些虚拟数据立即进行测试。然后,我们继续“填补空白”,编写顶层逻辑所需的下层代码。我们还可以轻松地一对一地测试较低级别的代码;例如,我们可以通过在控制台中直接输入sum来测试sum([1, 2, 3]),而不必担心账单或其他问题。

另一个关键点是将不同的事物分开。在这种情况下,我们进行了循环,并检查了null,并将字符串转换为数字,并进行了加法运算。我们不会将所有这些事情混为一谈,而是将它们编写为独立的微型函数,然后通过mapfilter等函数进行“组合”,从而分别处理它们,以解决我们的问题。单独处理事物的好处还在于,我们创建的小片段可以轻松重用,测试或更改,而不会破坏其他事物。

关于javascript - 查找JS数组的总和,并排除所有NULL值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39761469/

相关文章:

javascript - 如何将字符串附加到 session 存储 key ?

c - 使用字符数组进行二进制搜索?

c++ - 函数如何使用引用返回所需的数字?

javascript - 找不到未定义错误的属性长度

javascript - jQuery 克隆元素仅在循环内 append 一次克隆项

javascript - 如何从 jSon 对象构建数组

python - numpy:如何加入数组? (获得几个范围的联合)

java - 对象数组列表到字符串数组

javascript - iFrame 自动刷新不适用于 Chrome,但适用于 IE

Javascript:获取页面元素的内容并将其用作变量?