Swift 中的高阶函数——forEach、filter、sorted

2,048 阅读4分钟

欢迎阅读 Swift 中高阶函数系列的第三部分!在第一部分中,我大致介绍了什么是高阶函数,以及如何创建自己的函数。在第二篇文章中,我专注于三个相关的高阶函数;mapcompactMapflatMap. 这篇文章继续介绍 Swift 中最重要和最常用的高阶函数;在这里,您将了解forEach,filtersorted功能。

forEach 高阶函数

forEach可以使用高阶函数代替for-in循环来遍历集合的元素。与大多数其他高阶函数不同,forEach 不返回新集合;相反,它只是通过原始集合并允许在重复操作和任务中使用其项目。

让我们看一个非常简单的例子。考虑以下带有正数和负数的数组:

var  numbers  =  [5,  -2,  11,  18,  -28,  17,  21,  -19]

假设我们要遍历数组并打印每个数字的绝对值。我们可以在这样的for-in循环中做到这一点:

for  number  in  numbers  {

    print("Absolute value of \(number) is \(abs(number))")

}

这种传统方式可以被forEach高阶函数取代。我们所要做的就是通过numbers数组访问它:

numbers.forEach  {  number in

    print("Absolute value of \(number) is \(abs(number))")

}

上述闭包中的参数表示执行循环时源集合中的每一项。大多数时候我们可以省略它并使用速记参数代替:

numbers.forEach  {  print("Absolute value of \($0) is \(abs($0))")  }

以上所有片段将打印完全相同的结果。

当然,也可以在forEach. 例如,以下代码打印原始数组中的所有正数:

numbers.forEach  {

    if  $0  >  0  {

        print($0)

    }

}

有些事情我们不能使用forEach. 也就是说,在迭代原始项目的同时对它们进行操作。如果您想使用高阶函数来更新原始集合,那么您应该考虑使用map,而不是forEach; 后者旨在为我们提供循环内的每个项目,保持源集合不可变

要理解这一点,请参阅下一个片段:

for  i  in  0..<numbers.count  {

    numbers[i]  *=  2

}

它所做的只是将numbers数组中每个数字的值加倍。即使上述内容可以完美地完成工作,以下内容也不起作用:

numbers.forEach  {  $0  *=  2  }

尝试这样做会使 Xcode 显示以下错误:

Left side of mutating operator isn't mutable: '$0' is immutable

请记住这一点,不要尝试使用forEach.

字典和集合也可以与 一起使用forEach,因为它们也是集合类型。举个例子,让我们考虑以下保留一些欧洲国家及其首都的字典:

let  capitals  =  ["France":  "Paris",  "Italy":  "Rome",  "Greece":  "Athens",  "Spain":  "Madrid"]

我们可以forEach像这样分别打印每个键值对:

capitals.forEach  {  print($0)  }

每个循环打印一个包含键和实际值的元组:

(key:  "Italy",  value:  "Rome")

(key:  "France",  value:  "Paris")

(key:  "Greece",  value:  "Athens")

(key:  "Spain",  value:  "Madrid")

也可以只访问键或值:

capitals.forEach  {  print($0.value)  }

所以,forEach简而言之,这是高阶函数。尽可能使用它代替传统的循环,因为它有助于编写更短、更清晰的代码。

滤波器高阶函数

filter高阶函数的名称几乎解释了它的用途。当应用于一个集合时,它会在根据条件过滤原始元素后返回另一个集合

让我们再次考虑numbers上一部分中的示例数组,假设我们只想使用该filter方法获取负数。在最扩展的形式中,这可以按如下方式完成:

let  negatives  =  numbers.filter  {  number  ->  Bool  in

    return  number  <  0

}

闭包的返回类型是一个 Bool 值;结果数组中的项目是那些满足主体内部条件的项目;在这种情况下,数字小于零。number闭包的参数代表原始集合中的每一项。

与其他高阶函数一样,return如果它是闭包中的单行语句,我们可以省略显式设置返回类型以及关键字。这意味着我们可以将上面的代码写得更简单:

let  negatives  =  numbers.filter  {  number in

    number  <  0

}

如果我们使用速记参数并将所有内容仅写在一行中,它会变得更加简单:

let  negatives  =  numbers.filter  {  $0  <  0  }

以上都是相同的,但是最后一个片段是过滤原始数字的最清晰和最短的方法。

同样的逻辑,我们也可以得到所有大于 10 的数字:

let  positivesOver10  =  numbers.filter  {  $0  >  10  }

filter与任何其他高级函数一样,不限于其项目为基本数据类型的集合。当项目是自定义类型时也可以使用它。考虑以下描述俱乐部成员的结构:

struct  Member  {

    enum  Gender  {  case  male,  female  }

    var  name:  String

    var  age:  Int

    var  gender:  Gender

}

假设我们有以下成员:

let  members  =  [Member(name:  "John",  age:  35,  gender:  .male),

 Member(name:  "Bob",  age:  32,  gender:  .male),

 Member(name:  "Helen",  age:  33,  gender:  .female),

 Member(name:  "Kate",  age:  28,  gender:  .female),

 Member(name:  "Sean",  age:  27,  gender:  .male),

 Member(name:  "Peter",  age:  39,  gender:  .male),

 Member(name:  "Susan",  age:  41,  gender:  .female),

 Member(name:  "Jim",  age:  43,  gender:  .male)]

让我们看一些场景和示例。首先,假设我们要过滤上述内容并仅获取男性成员。我们使用filter如下所示的扩展形式来做到这一点:

let  males  =  members.filter  {  member  ->  Bool  in

    return  member.gender  ==  .male

}

或者,我们可以用它的较短版本做得更好:

let  males  =  members.filter  {  $0.gender  ==  .male  }

只需一行代码即可返回一个新数组,该数组仅包含满足闭包内条件的那些元素。看到我们可以通过速记参数访问自定义类型的属性,就像我们有一个普通对象一样;$0是本例中的对象。

继续另一个示例,以下返回所有 30 多岁的女性成员:

let  femalesIn30s  =  members.filter  {  $0.gender  ==  .female  &&  $0.age  >=  30  &&  $0.age  <  40  }

接下来是30岁以下的所有男性成员:

let  malesUnder30  =  members.filter  {  $0.gender  ==  .male  &&  $0.age  <  30  }

通常,我们可以在上下文中有效的闭包中编写任何可能的条件。例如,以下过滤所有成员并仅返回名称以“S”开头的成员:

let  membersWithS  =  members.filter  {  $0.name.starts(with:  "S")  }

为了快速打印并查看每个结果数组的内容,我们可以使用forEach前面介绍的高阶函数。这就是上面最后一个数组接下来发生的事情:

membersWithS.forEach  {  print($0.name)  }

Sean Susan

filter高阶函数没有什么特别困难的。您唯一应该特别注意的是您在其中应用的条件,以便它返回正确和预期的结果。

排序后的高阶函数

sorted高阶函数可以以按升序或降序排列的集合中。它返回的是一个新的集合,其中的项目已排序。

要查看示例,请再次考虑numbers本文第一部分中介绍的数组。假设我们要按升序对它进行排序并从最低值到最高值,sorted可以像这样使用:

let  sorted  =  numbers.sorted  {  (num1,  num2)  ->  Bool  in

    return  num1  <  num2

}

闭包中的两个参数是排序过程中任何给定时刻正在比较的两个项目。比较被确定为闭包主体中的条件。

与所有其他高阶函数类似,参数、返回类型以及return关键字可以省略并使用速记参数代替:

let  sorted  =  numbers.sorted  {  $0  <  $1  }

这比前一个片段短得多,并且看到第二个元素是用$1速记参数表示的。

sorted也为我们提供了一个更快的选项来执行比较;仅指示顺序,根本不使用任何参数:

let  sorted  =  numbers.sorted(by:  <)

此外sorted,还有sort高阶函数。尽管它的目的也是将集合的项目按升序或降序排列,但两者之间存在很大差异;sorted返回一个包含已排序项的新数组,同时sort对原始集合中的项进行排序

以下代码按numbers降序对数组中的所有数字进行排序,但不返回新数组;更改发生在同一个数组上:

numbers.sort(by:  >)

以上结果为:

[21,  18,  17,  11,  5,  -2,  -19,  -28]

作为建议,sort如果您希望在排序后保持原始集合完好无损,请不要使用。另一方面,sorted如果您有一个非常大的集合要排序,请不要使用;这将需要大量额外的内存,因此最好使用它sort来进行适当的排序。

您应该注意的最后一条有用信息是sorted应用于字典时返回的内容。让我们以这篇文章第一部分中介绍的几个国家和首都的字典为例:

let  capitals  =  ["France":  "Paris",  "Italy":  "Rome",  "Greece":  "Athens",  "Spain":  "Madrid"]

要根据从 A 到 Z 的城市名称对其进行排序,我们可以执行以下操作:

let  sortedCapitals  =  capitals.sorted  {  $0.value  <  $1.value  }

有趣的部分在这里;什么sorted返回并不是一个新的字典,但包含所有键-值对元组的数组排序。上面的代码结果如下:

[(key:  "Greece",  value:  "Athens"),  (key:  "Spain",  value:  "Madrid"),  (key:  "France",  value:  "Paris"),  (key:  "Italy",  value:  "Rome")]

概括

今天我介绍了 Swift 中三个非常常见的高阶函数,它们是编程中的好工具;forEachfiltersorted。当它们代替使用循环实现的传统解决方案时,代码可以变得更短、更清晰、可读和可维护。