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

问题描述 投票:1回答:3

我在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在较新的版本中不赞成使用某些功能,所以坚持基本知识并创建我们自己的处理方式不是更好吗?

谢谢您的回答

arrays swift dictionary big-o code-cleanup
3个回答
1
投票

[今天,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 })

请注意,这种方法如何使标签清楚显示键是什么,值是什么。我尚未对该代码进行基准测试,但是我希望它在时间上与其他代码非常相似。


0
投票

一般来说,由于一些原因,您应该比for循环更喜欢使用函数式习惯用法(即reduce):

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

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

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


0
投票

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

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

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

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

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

关于两段代码

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

第一种方法:

  1. 使字典不必要地可变
  2. 忘记在字典上保留容量,因此会引起多个调整大小的操作(这涉及内存的重新分配,以及字典中所有键的重新散列)
  3. 第二种方法:

  1. 使用功能(reduce),但不适合该作业(Dictionary.init(uniqueKeysWithValues:))。参见https://github.com/amomchilov/Blog/blob/master/Don't%20abuse%20reduce.md
  2. 也忘记保留容量。
  3. 相反,我建议使用Rob NapierMartin R's approach。两者都更能表达您的意图,并使用Dictionary.init(uniqueKeysWithValues:),可在内部为字典预先分配足够的内存,以适应所有值。]

© www.soinside.com 2019 - 2024. All rights reserved.