我想用一个函数来扩展数组,该函数将返回数组中所有非零项的计数。理想情况下,这将适用于任何可选或非可选类型的数组。我尝试了很多编译失败、Xcode崩溃或两者兼而有之的事情。我本以为它会是这样的:
extension Array {
func realCount() -> Int {
var cnt = 0
for value in self {
if value != nil {
cnt++
}
}
return cnt
}
}
Swift抱怨T
不能转换为UInt8
。或者有时CCD_ 3或其他随机类。
那么,假设这是可能的,有什么诀窍呢?
编辑:从Xcode 6测试版5开始,现在已经编译了,但没有给出预期的结果。if value != nil
每次都评估为true。
您无法将任意值与nil
进行比较(编辑:但请参阅下面Sulthan的评论;可能我们应该能够将任意值与nil
进行比较;这段的其余部分今天可能是正确的,但这只是由于编译器错误(。虽然Optional
应用了一些语法糖,但它实际上只是一个枚举,而nil
只是Optional.None
。一种类型(Optional
(需要一种行为,所有其他类型需要另一种行为。Swift通过泛型实现了这一点,只是没有扩展。你必须把它变成一个功能:
func realCount<T>(x: [T?]) -> Int {
return countElements(filter(x, { $0.getLogicValue() } ) )
}
func realCount<T>(x: [T]) -> Int {
return countElements(x)
}
let l = [1,2,3]
let lop:[Int?] = [1, nil, 2]
let countL = realCount(l) // 3
let countLop = realCount(lop) // 2
这种方法要灵活得多。Optional
只是您希望以这种方式平面映射的众多类型之一(例如,您可以使用相同的技术来处理Result(。
编辑:你可以通过为你认为"真实"的东西创建一个协议来进一步做到这一点。这样你就不必把它局限于选项。例如:
protocol Realizable {
func isReal() -> Bool
}
extension Optional: Realizable {
func isReal() -> Bool { return self.getLogicValue() }
}
func countReal<S:Collection>(x: S) -> S.IndexType.DistanceType {
return countElements(x)
}
func countReal<S:Collection where S.GeneratorType.Element:Realizable>(x: S) -> Int {
return countElements(filter(x, {$0.isReal()}))
}
这意味着,如果我通过一组"可实现"的东西,然后根据它们的规则过滤它们。否则,就数数吧。虽然我可能不会真正使用这个函数(这似乎是非常特殊的情况(,但这个概念很有用。稍后的调用者可以添加新的"可实现"类型,而无需修改任何代码(甚至不知道它们是如何实现的(。这展示了如何为那些没有实现您的协议的东西设置默认行为。
顺便说一句,我在这里使用Collections只是因为它们更容易计数(而且我对返回类型有点草率;注意一个是DistanceType,另一个是Int(。在基于集合的泛型函数上正确获取类型仍然有点棘手(而且经常会使编译器崩溃(。我怀疑这一切在下一个测试版中都会有所改善。
TL;博士
通过使用协议,可以扩展SequenceType来计算非nil的数量。
let array: [Int?] = [1, nil, 3]
assert(array.realCount == 2)
如果你只是想要代码,向下滚动到下面的"解决方案"。
我需要做一些类似的事情来创建一个array.removeNils()
扩展方法。
问题是,当你试图做这样的事情时:
extension SequenceType where Generator.Element == Optional { }
你得到:
error: reference to generic type 'Optional' requires arguments in <...>
extension SequenceType where Generator.Element == Optional {
^
generic type 'Optional' declared here
所以问题是,我们应该在<>
中添加什么类型?它不能是硬编码的类型,因为我们希望它适用于任何事情,所以,我们想要像T
这样的泛型。
error: use of undeclared type 'T'
extension SequenceType where Generator.Element == Optional<T> {
^
看起来没有办法做到这一点。然而,在协议的帮助下,你实际上可以做你想做的事:
protocol OptionalType { }
extension Optional: OptionalType {}
extension SequenceType where Generator.Element: OptionalType {
func realCount() -> Int {
// ...
}
}
现在它只适用于带有选项的阵列:
([1, 2] as! [Int]).realCount() // syntax error: type 'Int' does not conform to protocol 'OptionalType'
([1, nil, 3] as! [Int?]).realCount()
拼图的最后一块是将元素与nil
进行比较。我们需要扩展OptionalType
协议,以允许我们检查项目是否为nil
。当然,我们可以创建一个isNil()
方法,但不向Optional添加任何内容将是理想的。幸运的是,它已经有了map
功能可以帮助我们
以下是map
和flatMap
函数的示例:
extension Optional {
func map2<U>(@noescape f: (Wrapped) -> U) -> U? {
if let s = self {
return f(s)
}
return nil
}
func flatMap2<U>(@noescape f: (Wrapped) -> U?) -> U? {
if let s = self {
return f(s)
}
return nil
}
}
注意,如果self != nil
,map2
(相当于map
函数(如何仅返回f(s)
。我们并不真正关心返回什么值,所以为了清楚起见,我们实际上可以让它返回true
。为了使函数更容易理解,我为每个变量添加了显式类型:
protocol OptionalType {
associatedtype Wrapped
@warn_unused_result
func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}
extension Optional: OptionalType {}
extension SequenceType where Generator.Element: OptionalType {
func realCount() -> Int {
var count = 0
for element: Generator.Element in self {
let optionalElement: Bool? = element.map {
(input: Self.Generator.Element.Wrapped) in
return true
}
if optionalElement != nil {
count += 1
}
}
return count
}
}
为了澄清,以下是通用类型映射到的内容:
- OptionalType.Wrapped==Int
- SequenceType.Generator.Element==可选
- SequenceType.Generator.Element.Wrapped==Int
- map.U==布尔
当然,realCount可以在没有所有这些显式类型的情况下实现,并且通过使用$0
而不是true
,可以避免我们需要在MirrorDisposition
0函数中指定_ in
。
解决方案
protocol OptionalType {
associatedtype Wrapped
@warn_unused_result
func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}
extension Optional: OptionalType {}
extension SequenceType where Generator.Element: OptionalType {
func realCount() -> Int {
return filter { $0.map { $0 } != nil }.count
}
}
// usage:
assert(([1, nil, 3] as! [Int?]).realCount() == 2)
需要注意的关键是,$0
是Generator.Element
(即OptionalType
(,$0.map { $0 }
将其转换为Generator.Element.Wrapped?
(例如Int?(。Generator.Element
甚至OptionalType
不能与nil
相比较,但Generator.Element.Wrapped?
可以与nil
相比较。
检查泛型类型是否为可选类型
使用函数_isOptional(T.Type)
。
_isOptional(Int.self) // -> false
_isOptional(Int?.self) // -> true
适用于普通类型或混凝土类型。