我很好奇joined()
和.flatMap(_:)
在展平多维数组时的性能特征:
let array = [[1,2,3],[4,5,6],[7,8,9]]
let j = Array(array.joined())
let f = array.flatMap{$0}
他们都将嵌套的array
展平为[1, 2, 3, 4, 5, 6, 7, 8, 9]
。我是否应该更喜欢一个而不是性能?此外,是否有更可读的方式来编写呼叫?
答案 0 :(得分:18)
当仅仅展平2D数组(没有应用任何转换或分隔符时,请参阅@dfri's answer以获取有关该方面的更多信息),array.flatMap{$0}
和Array(array.joined())
在概念上都是相同的,有类似的表现。
flatMap(_:)
和joined()
之间的主要区别(请注意,这不是一种新方法,它有just been renamed from flatten()
),joined()
总是被懒惰地应用(对于数组,它返回一个特殊的FlattenBidirectionalCollection<Base>
)。
因此,就性能而言,在您只想迭代部分扁平序列(不应用任何转换)的情况下,使用joined()
而不是flatMap(_:)
是有意义的。例如:
let array2D = [[2, 3], [8, 10], [9, 5], [4, 8]]
if array2D.joined().contains(8) {
print("contains 8")
} else {
print("doesn't contain 8")
}
因为joined()
被懒惰地应用了&amp; contains(_:)
将在找到匹配时停止迭代,只有前两个内部数组必须“展平”才能从2D数组中找到元素8
。虽然,@dfri correctly notes below,您也可以通过使用flatMap(_:)
/ LazySequence
懒惰地应用LazyCollection
- 可以通过lazy
属性创建。这对于懒惰地应用转换和放大是理想的。展平给定的2D序列。
如果完全迭代joined()
,则概念上与使用flatMap{$0}
没有什么不同。因此,这些都是平坦化2D阵列的有效(和概念相同)方式:
array2D.joined().map{$0}
的
Array(array2D.joined())
的
array2D.flatMap{$0}
就性能而言,flatMap(_:)
is documented的时间复杂度为:
O(m + n),其中m是此序列的长度,n是结果的长度
这是因为its implementation只是:
public func flatMap<SegmentOfResult : Sequence>(
_ transform: (${GElement}) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.${GElement}] {
var result: [SegmentOfResult.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
}
由于append(contentsOf:)
的时间复杂度为O(n),其中n是要追加的序列的长度,我们得到O(m + n)的总时间复杂度,其中m将是总的附加的所有序列的长度,n是2D序列的长度。
当谈到joined()
时,没有记录时间复杂性,因为它被懒惰地应用。但是,要考虑的源代码的主要部分是the implementation of FlattenIterator
,它用于迭代2D序列的展平内容(使用map(_:)
或Array(_:)
初始化程序时会发生这种情况。 joined()
)。
public mutating func next() -> Base.Element.Iterator.Element? {
repeat {
if _fastPath(_inner != nil) {
let ret = _inner!.next()
if _fastPath(ret != nil) {
return ret
}
}
let s = _base.next()
if _slowPath(s == nil) {
return nil
}
_inner = s!.makeIterator()
}
while true
}
此处_base
是基本2D序列,_inner
是来自其中一个内部序列的当前迭代器,_fastPath
&amp; _slowPath
是编译器的提示,用于辅助分支预测。
假设我正确地解释了这段代码&amp;迭代完整序列,这也具有O(m + n)的时间复杂度,其中m是序列的长度,n是结果的长度。这是因为它遍历每个外部迭代器和每个内部迭代器以获得扁平元素。
因此,性能方面,Array(array.joined())
和array.flatMap{$0}
都具有相同的时间复杂度。
如果我们在调试版本中运行快速基准测试(Swift 3.1):
import QuartzCore
func benchmark(repeatCount:Int = 1, name:String? = nil, closure:() -> ()) {
let d = CACurrentMediaTime()
for _ in 0..<repeatCount {
closure()
}
let d1 = CACurrentMediaTime()-d
print("Benchmark of \(name ?? "closure") took \(d1) seconds")
}
let arr = [[Int]](repeating: [Int](repeating: 0, count: 1000), count: 1000)
benchmark {
_ = arr.flatMap{$0} // 0.00744s
}
benchmark {
_ = Array(arr.joined()) // 0.525s
}
benchmark {
_ = arr.joined().map{$0} // 1.421s
}
flatMap(_:)
似乎是最快的。我怀疑joined()
较慢可能是由于FlattenIterator
内发生的分支(虽然编译器的提示最小化了这个成本) - 尽管map(_:)
为什么这么慢,我我不太确定。当然有兴趣知道是否有其他人知道更多。
但是,在优化的构建中,编译器能够优化这种巨大的性能差异;给所有三个选项提供可比较的速度,尽管flatMap(_:)
仍然是最快的一小段时间:
let arr = [[Int]](repeating: [Int](repeating: 0, count: 10000), count: 1000)
benchmark {
let result = arr.flatMap{$0} // 0.0910s
print(result.count)
}
benchmark {
let result = Array(arr.joined()) // 0.118s
print(result.count)
}
benchmark {
let result = arr.joined().map{$0} // 0.149s
print(result.count)
}
(请注意,执行测试的顺序会影响结果 - 上述结果均为执行各种不同订单测试的平均值)
答案 1 :(得分:4)
从Swiftdoc.org documentation of Array
(Swift 3.0/dev)我们读到[强调我的]:
func flatMap<SegmentOfResult : Sequence>(_: @noescape (Element) throws -> SegmentOfResult)
返回一个包含调用的连接结果的数组 给出了与该序列的每个元素的转换。
...
事实上,
s.flatMap(transform)
相当于Array(s.map(transform).flatten())
。
我们也可以看看Swift源代码中的两个实际实现(从中生成Swiftdoc ......)
最值得注意的是后一个源文件,其中使用的闭包(flatMap
)未产生的transform
实现和可选值(如此处的情况)都被描述为
/// Returns the concatenated results of mapping `transform` over /// `self`. Equivalent to /// /// self.map(transform).joined()
从上面的内容(假设编译器可以是一个简单的自我{ $0 }
transform
),看起来似乎在性能方面,两个选择应该是等价的,但是{{1做,imo,更好地显示操作的 intent 。
除了语义意图之外,还有一个明显的用例,joined
优先于joined
(并不完全可比)flatMap
:joined
使用init(separator:)
使用分隔符连接序列的let array = [[1,2,3],[4,5,6],[7,8,9]]
let j = Array(array.joined(separator: [42]))
print(j) // [1, 2, 3, 42, 4, 5, 6, 42, 7, 8, 9]
初始值设定项:
flatMap
使用flatMap
的相应结果并不是很整洁,因为我们明确需要在 let f = Array(array.flatMap{ $0 + [42] }.dropLast())
print(f) // [1, 2, 3, 42, 4, 5, 6, 42, 7, 8, 9]
操作之后删除最后一个额外的分隔符(两个不同的用例,或没有尾随分隔符)
flatMap
另请参阅Erica Sadun关于flatten()
与joined()
的一篇有些过时的帖子(注意:flatten()
在Swift&lt; 3)中被命名为onSubmit(props){
console.log('csrf',CSRF_TOKEN);
axios({
method:'POST',
url:'/api/review/create/',
headers:{
'X-CSRF-Token':CSRF_TOKEN,
//'Access-Control-Allow-Origin':'*',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data:{
review:props.review
}
})
.then(response => {
console.log('success');
})
.catch(error => {
throw("Error: ",error);
});
}
。