Swift 用函数编程提取数组中的对象

Swift 用函数编程提取数组中的对象,swift,functional-programming,Swift,Functional Programming,从对象列表中提取数据时遇到一些困难,我正在学习函数式编程概念。 struct Country { let name: String } let country1 = Country(name: "Algeria") let country2 = Country(name: "Angola") let country3 = Country(name: "Belgium") let countries = [country1, country2, country3] 我想做的是一本字典,

从对象列表中提取数据时遇到一些困难,我正在学习函数式编程概念。

struct Country {
   let name: String
}

let country1 = Country(name: "Algeria")
let country2 = Country(name: "Angola")
let country3 = Country(name: "Belgium")

let countries = [country1, country2, country3]
我想做的是一本字典,它的键是第一个字符,值是所有以这个字符开头的国家的列表。在我的示例中,我将得到:

let dic = ["A": [country1, country2], "B":[country3]]
我知道如何使用“丑陋”for循环。像这样:

  for country in countries {
     let first = country.name.first
     if !sections.keys.contains(first) {
        let matchedCountries = countries.filter {$0.name.hasPrefix(first)}
        sections[first] = matchedCountries
     }
  }

我的问题是:有没有一种更实用的方法来实现这一点?

我不认为应该不惜一切代价避免
for
循环(我个人不会将
for…in
循环称为“丑陋”)。在某些情况下,它们可能是最合适的构造。也就是说,您可以在
countries
数组上使用
.forEach
(这实际上是一个
for…in
循环),只需将countries附加到现有键,并为不存在的键创建新的键值对(
字符串:[Country]
键值对)。例如:


请注意,此方法的一个可能优点是,对于循环(例如,
.contains
.filter
),不使用嵌套在外部
中的类循环结构;您只需处理
国家/地区
数组中的每个国家/地区一次。

您可以使用将
国家/地区
缩减为数组字典,从空白字典开始,使用每个国家/地区字母作为键,并在每次缩减后添加国家/地区

现在不是在Mac电脑上,但下面是一个快速的伪代码近似图,可以看出这是什么样子:

let dicFinal = countries.reduce([:]) {
    dic, country in
    let letter = country.name[0]
    if let arr = dic[letter] {
        dic[letter] = arr + country
    } else {
        dic[letter] = [country]
    }
    return dic
}
以下是我的解决方案:

public struct Country : CustomStringConvertible {
    let name: String
    public var description : String {
        get {
            return name
        }
    }
}

var countries = [
    Country(name: "Algeria"),
    Country(name: "Belgium"),
    Country(name: "Angola"),
    Country(name: "Canada"),
    Country(name: "Brazil"),
    Country(name: "Denmark"),
]

let AlphabetizedCountries = countries
    .sort{$0.name < $1.name}
    .reduce([String : [Country]]()) {
        var dict = $0
        let firstLetter = String($1.name.characters.first!)
        dict[firstLetter] = (dict[firstLetter] ?? []) + [$1]
        return dict
    }

print(AlphabetizedCountries)
公共结构国家/地区:CustomStringConverable{
let name:String
公共变量说明:字符串{
得到{
返回名称
}
}
}
var国家=[
国家(名称:“阿尔及利亚”),
国家(名称:“比利时”),
国家(名称:“安哥拉”),
国家(名称:“加拿大”),
国家(名称:“巴西”),
国家(名称:“丹麦”),
]
按字母顺序排列的国家=国家
.sort{$0.name<$1.name}
.reduce([String:[国家]]()){
var dict=$0
让firstLetter=String($1.name.characters.first!)
dict[firstLetter]=(dict[firstLetter]??[])+[$1]
返回指令
}
打印(按字母顺序排列的国家)
输出:
[“B”:[比利时、巴西],“A”:[阿尔及利亚、安哥拉],“C”:[加拿大],“D”:[丹麦]]

国家将在字母数组中排序,但字母数组本身不会在字典中排序,因为字典不保留排序


如果包含排序,则此代码为O(NlogN);如果不包含排序,则此代码为O(logN)。但是,正如大多数函数式编程的特点一样,与循环对应的
相比,此解决方案的效率非常低(除非编译器特别擅长优化所有冗余复制)。

您希望获得关于自己解决问题的指导,还是仅仅是解决方案本身?如您所愿,我认为我可以将reduce函数与filter one一起使用,但对于know don see how:)我不确定OP是否会高兴您将他的代码从O(N)带到O(N^2),至少没有提及它。
map
调用是不必要的(或者是有效的,我认为您忘记了
$0.name[0]
),您可以在reduce块中完成这项工作,并删除一个因子N。map调用的优点是不必要的,现在更新:)我不认为for循环是“坏的”,但它们(以及您的示例)要求创建可变变量,这在函数式编程中是不受欢迎的,而在上面的
reduce
解决方案中是不必要的。@Patrick Goley好吧,你要么创建可变字典,要么在每次向字典中添加元素时重新创建字典。reduce将第一个参数(dictionary)作为let常量传递,因此reduce版本从函数编程的角度看可能更干净,但显然是以二次时间运行的。
public struct Country : CustomStringConvertible {
    let name: String
    public var description : String {
        get {
            return name
        }
    }
}

var countries = [
    Country(name: "Algeria"),
    Country(name: "Belgium"),
    Country(name: "Angola"),
    Country(name: "Canada"),
    Country(name: "Brazil"),
    Country(name: "Denmark"),
]

let AlphabetizedCountries = countries
    .sort{$0.name < $1.name}
    .reduce([String : [Country]]()) {
        var dict = $0
        let firstLetter = String($1.name.characters.first!)
        dict[firstLetter] = (dict[firstLetter] ?? []) + [$1]
        return dict
    }

print(AlphabetizedCountries)