如何在 Swift 字典中找到前 3 个最大值?
How do you find the top 3 maximum values in a Swift dictionary?
我了解到我可以通过下面的代码显示字典中最高的键和值
// champions dictionary
var champions = ["Ekko": 20, "Ahri": 10, "Vayne": 2, "Neeko": 25, "Zed": 6]
let greatestChampion = champions.max { a, b in a.value < b.value }
print greatestChampion // optional(("Ekko": 20))
我的问题是如何显示价值最高的3个英雄?示例结果为
print greatestChampion // optional(("Ekko": 20, "Neeko": 25, "Ahri": 10))
如果可能的话,我很想学习如何做到这一点。
max 方法只能return 单个值。如果您需要获得前 3 名,则需要按降序对它们进行排序,并使用前缀方法获得前 3 个元素
let greatestChampion = champions.sorted { [=10=].value > .value }.prefix(3)
print(greatestChampion)
这将打印
[(key: "Neeko", value: 25), (key: "Ekko", value: 20), (key: "Ahri", value: 10)]
Leo 的直截了当的方法奏效了。或者,您可以查看 Apple 的 Swift Algos 存储库中的 a more performant algorithm。
标准库 .sort().prefix(k)
需要 O(n log n) 时间。
对于偶尔计算的小型集合,比如说那些真正想和我约会的人,这就足够了。
但是为了在台湾最好(或最差)的boba供应商前面加上逐秒排名,您的最终用户或AWS发票会想:是否有更线性的时间复杂度方法?对大型集合进行排序会变得很昂贵。
的确如此。你可以得到接近线性的。
想象一下阅读酒单。你可以在心里列一张最合理和最离谱的价格清单,这取决于你是否能负担得起这顿饭。当你看到一个新的极端时,你会把它放在一个心理定势中并踢出一个元素。除非您的同事很无聊,否则您不会重新阅读酒单来对其进行排序。
Swift 算法存储库的 prefixSort 算法就是这样。它避免了预先对整个菜单进行排序,而是通过一次线性传递来找到最好的葡萄酒来向蒂姆收费。
如果逐行阅读,函数需要大小为 k 的前缀。它还要求您分叉出一种排序方法,该方法有望回答以下问题:“您的顺序是递增的吗?是或否?”。该方法只需要采用两个元素和 return 一个 Bool.
首先,该算法创建一个长度前缀为 k 的结果数组并对其进行排序。
然后它会读取一次集合(不包括前 k 个),根据您传入的方法检查元素是否可以在排序数组中找到位置。
字面意思是:
- 如果元素 e 小于结果数组的最后一个(最大)值
- 在结果数组中找到第一个索引,其中 e 小于下一个值 $0
- 在该索引处插入 e
- 弹出最后一个(最大)值
多田。前缀大小和排序顺序因此保持在一个小数组中,您的集合只需阅读一次。
但是,如果 k 大于集合大小的大约 10%,速度优势就会消失。届时,您将看到实现切换到标准库排序。
如果您阅读 repo 的代码,您会注意到我在编写“查找结果数组中 e 较小的第一个索引...”而不是从该数组,算法采用更快的方法:二进制搜索。它在 partioningIndex 方法中执行此操作。由于结果数组已经排序,因此通过从中心开始并将数组减半直到找到有效索引,可以更快地找到用于插入 e 的有效索引。这对概念来说不是必需的,但可以锦上添花。
extension Collection {
/// - Complexity: O(k log k + nk)
public func sortedPrefix(
_ count: Int,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> [Self.Element] {
assert(count >= 0, """
Cannot prefix with a negative amount of elements!
"""
)
guard count > 0 else {
return []
}
let prefixCount = Swift.min(count, self.count)
guard prefixCount < (self.count / 10) else {
return Array(try sorted(by: areInIncreasingOrder).prefix(prefixCount))
}
var result = try self.prefix(prefixCount).sorted(by: areInIncreasingOrder)
for e in self.dropFirst(prefixCount) {
if let last = result.last, try areInIncreasingOrder(last, e) {
continue
}
let insertionIndex =
try result.partitioningIndex { try areInIncreasingOrder(e, [=10=]) }
let isLastElement = insertionIndex == result.endIndex
result.removeLast()
if isLastElement {
result.append(e)
} else {
result.insert(e, at: insertionIndex)
}
}
return result
}
}
我了解到我可以通过下面的代码显示字典中最高的键和值
// champions dictionary
var champions = ["Ekko": 20, "Ahri": 10, "Vayne": 2, "Neeko": 25, "Zed": 6]
let greatestChampion = champions.max { a, b in a.value < b.value }
print greatestChampion // optional(("Ekko": 20))
我的问题是如何显示价值最高的3个英雄?示例结果为
print greatestChampion // optional(("Ekko": 20, "Neeko": 25, "Ahri": 10))
如果可能的话,我很想学习如何做到这一点。
max 方法只能return 单个值。如果您需要获得前 3 名,则需要按降序对它们进行排序,并使用前缀方法获得前 3 个元素
let greatestChampion = champions.sorted { [=10=].value > .value }.prefix(3)
print(greatestChampion)
这将打印
[(key: "Neeko", value: 25), (key: "Ekko", value: 20), (key: "Ahri", value: 10)]
Leo 的直截了当的方法奏效了。或者,您可以查看 Apple 的 Swift Algos 存储库中的 a more performant algorithm。
标准库 .sort().prefix(k)
需要 O(n log n) 时间。
对于偶尔计算的小型集合,比如说那些真正想和我约会的人,这就足够了。
但是为了在台湾最好(或最差)的boba供应商前面加上逐秒排名,您的最终用户或AWS发票会想:是否有更线性的时间复杂度方法?对大型集合进行排序会变得很昂贵。
的确如此。你可以得到接近线性的。
想象一下阅读酒单。你可以在心里列一张最合理和最离谱的价格清单,这取决于你是否能负担得起这顿饭。当你看到一个新的极端时,你会把它放在一个心理定势中并踢出一个元素。除非您的同事很无聊,否则您不会重新阅读酒单来对其进行排序。
Swift 算法存储库的 prefixSort 算法就是这样。它避免了预先对整个菜单进行排序,而是通过一次线性传递来找到最好的葡萄酒来向蒂姆收费。
如果逐行阅读,函数需要大小为 k 的前缀。它还要求您分叉出一种排序方法,该方法有望回答以下问题:“您的顺序是递增的吗?是或否?”。该方法只需要采用两个元素和 return 一个 Bool.
首先,该算法创建一个长度前缀为 k 的结果数组并对其进行排序。
然后它会读取一次集合(不包括前 k 个),根据您传入的方法检查元素是否可以在排序数组中找到位置。
字面意思是:
- 如果元素 e 小于结果数组的最后一个(最大)值
- 在结果数组中找到第一个索引,其中 e 小于下一个值 $0
- 在该索引处插入 e
- 弹出最后一个(最大)值
多田。前缀大小和排序顺序因此保持在一个小数组中,您的集合只需阅读一次。
但是,如果 k 大于集合大小的大约 10%,速度优势就会消失。届时,您将看到实现切换到标准库排序。
如果您阅读 repo 的代码,您会注意到我在编写“查找结果数组中 e 较小的第一个索引...”而不是从该数组,算法采用更快的方法:二进制搜索。它在 partioningIndex 方法中执行此操作。由于结果数组已经排序,因此通过从中心开始并将数组减半直到找到有效索引,可以更快地找到用于插入 e 的有效索引。这对概念来说不是必需的,但可以锦上添花。
extension Collection {
/// - Complexity: O(k log k + nk)
public func sortedPrefix(
_ count: Int,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> [Self.Element] {
assert(count >= 0, """
Cannot prefix with a negative amount of elements!
"""
)
guard count > 0 else {
return []
}
let prefixCount = Swift.min(count, self.count)
guard prefixCount < (self.count / 10) else {
return Array(try sorted(by: areInIncreasingOrder).prefix(prefixCount))
}
var result = try self.prefix(prefixCount).sorted(by: areInIncreasingOrder)
for e in self.dropFirst(prefixCount) {
if let last = result.last, try areInIncreasingOrder(last, e) {
continue
}
let insertionIndex =
try result.partitioningIndex { try areInIncreasingOrder(e, [=10=]) }
let isLastElement = insertionIndex == result.endIndex
result.removeLast()
if isLastElement {
result.append(e)
} else {
result.insert(e, at: insertionIndex)
}
}
return result
}
}