通过减少数组创建字典与通过迭代分配每个项目之间的区别

时间:2019-10-24 19:34:41

标签: arrays swift dictionary big-o code-cleanup

我在StackOverflow上遇到了一个问题:Swift - Convert Array to Dictionary 用户想要获取数组中的元素并将其放入字典中,并为每个元素分配0。 (关键是玩家,价值是他们的分数) 所以:

var playerNames = ["Harry", "Ron", "Hermione"]

成为

var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]

此问题有2个答案: 1)在数组上使用reduce

let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }

2)创建一个字典并遍历数组以向其中添加每个值键对

var dictionary = [String: Int]()
for player in playerNames {
    dictionary[player] = 0
}

我从https://github.com/nyisztor/swift-algorithms开始使用BenchTimer函数来测试这两种求解方法。而且它们似乎都在O(n)中运行。

enter image description here

enter image description here

我想知道为什么我们比第一个更喜欢第一个,因为写第二个解决方案的人对他们的编码技能有不好的评价。

编辑:Apple在较新的版本中不赞成使用某些功能,所以坚持基本知识并创建我们自己的处理方式不是更好吗?

谢谢您的回答

3 个答案:

答案 0 :(得分:2)

今天,IMO,您不应使用其中任何一个。现在,我们有了Dictionary.init(uniqueKeysWithValues:).init(_:uniquingKeysWith:),它们可以更清楚地说明其意图,并明确显示极端情况,例如重复的键。

如果您可以静态证明所有键都是唯一的,则可以使用第一个:

let scoreboard = Dictionary(uniqueKeysWithValues: playerNames.map { (name: $0, score: 0) })

如果您不能证明密钥是唯一的,则可以使用第二个密钥,这样您就可以明确决定在发生冲突时该怎么做。

let scoreboard = Dictionary(playerNames.map { (name: $0, score: 0) },
                            uniquingKeysWith: { first, _ in first })

请注意此方法如何使标签明确键名和值。我尚未对该代码进行基准测试,但是我希望它在时间上与其他代码非常相似。

答案 1 :(得分:1)

  

坚持基础知识并创建我们自己的做事方式更好吗?

我不这么认为。 Swift社区当然不是那种心态。 Swift会优先进行有意义的抽象和简化,只要它们是有价值的且逐步公开即可。

围棋社区与您有着相同的见解,但这非常痛苦(IMO)。 Go标准库甚至没有用于反转字符串的API。你必须自己煮。而且这比大多数人想的要难。如果您认为做一个循环来反转字节的简单问题,不对,对于unicode来说这是完全可以解决的(但是在诸如"hello"之类的简单ASCII测试用例中,它可能不会被注意到。)

类似地,如果您每次想实现for时都编写map循环,则可能会忘记调用Array.reserveCapacity(_:)。忘记该操作将导致多个数组分配,并使看起来O(n)的算法实际上变成O(n^2)。这样的性能或正确性“陷阱”很少,因此使用流行的,共享的实现有很大的好处。

我们站在巨人的肩膀上。如果我们全神贯注于重新发明轮子,我们将无法做到这一点。

关于这两段代码

我不会使用它们中的任何一个

第一种方法:

  1. 使用功能(reduce),但不适合工作(Dictionary.init(uniqueKeysWithValues:))。请参见https://github.com/amomchilov/Blog/blob/master/Don't%20abuse%20reduce.md
  2. 忘记在字典上保留容量,因此会导致进行多种调整大小的操作(这涉及内存重新分配以及字典中所有键的重新哈希处理)

第二种方法:

  1. 使字典不必要地可变
  2. 也忘记保留容量。

相反,我建议使用Rob NapierMartin R's approach。两者都更能表达您的意图。 Rob还使用已知大小的序列,该序列允许Dictionary.init(uniqueKeysWithValues:)在内部为字典分配足够的内存,以适应所有值。

答案 2 :(得分:0)

一般来讲,出于以下几个原因,您应该更喜欢函数式习惯用法(即reduce)而不是for循环:

  1. 功能版本传达了意图。在这种情况下,我们 将数组简化为字典-不是最好的例子,但是 通常通过一些将reduce转换为单个标量的向量 组合功能。
  2. 由于已经过测试,因此默认情况下该功能版本正确。在reduce的情况下,这看似微不足道,但在shuffled之类的情况下就显得不那么重要了。您如何轻松地查看for循环并告诉我它是否执行了Fisher Yates改组以及该改组是否正确实施?同样,比起一系列顺序的for循环或执行5种不同功能的单个for循环,函数的管道更易于阅读。

  3. 功能版本通常是(但不是在这种情况下)不可变,并且不可变值比可变状态更容易推论,因为它们永远不会改变。

  4. Swift.Sequence上的功能方法在Combine.Publisher上具有相似的方法。这意味着您可以在所有序列中使用一组功能惯用语,无论它们是同步的还是异步的/反应式的。