【问题标题】:Swift convert [T?] to [T]?迅速将 [T?] 转换为 [T]?
【发布时间】:2021-07-29 17:58:25
【问题描述】:

这是将T? 的列表转换为T 的可选列表的干净方法吗? (即[T?][T]?)如果找到nil值,则nil而不是T,否则输出没有Optional的普通列表。

例如:

let x = [1, 2, 3, 4]
x.someMethod()  // [1, 2, 3, 4]

let y = [1, 2, nil, 3, 4]
y.someMethod()  // nil

【问题讨论】:

    标签: swift


    【解决方案1】:

    您可以简单地有条件地将[T?] 转换为[T],如果数组不包含 nil 元素,这将成功:

    let x: [Int?] = [1, 2, 3, 4]
    print(x as? [Int]) // Optional([1, 2, 3, 4])
    
    let y: [Int?] = [1, 2, nil, 3, 4]
    print(y as? [Int]) // nil
    

    作为Array的扩展方法

    extension Array {
        func someFunction<T>() -> [T]? where Element == Optional<T> {
            return self as? [T]
        }
    }
    

    以前的更复杂的解决方案: 您可以使用compactMap() 获取非零元素,然后检查它是否与原始元素数量相同:

    extension Array {
        func someFunction<T>() -> [T]? where Element == Optional<T> {
            let nonNils = compactMap { $0 }
            return nonNils.count == count ? nonNils : nil
        }
        
    }
    

    (这里使用Swift: Extension on [<SomeType<T>?] to produce [<T>?] possible? 的方法来定义可选元素数组的扩展方法。)

    例子:

    let x: [Int?] = [1, 2, 3, 4]
    print(x.someFunction()) // Optional([1, 2, 3, 4])
    
    let y = [1, 2, nil, 3, 4]
    print(y.someFunction()) // nil
    

    另一种选择,只是为了好玩:如果您为可选项定义“解包或抛出”方法

    extension Optional {
        struct FoundNilError : Error { }
        
        func unwrapOrThrow() throws -> Wrapped {
            switch self {
            case .some(let wrapped) : return wrapped
            case .none: throw FoundNilError()
            }
        }
    }
    

    然后您可以将其与map 和“可选尝试”一起使用:

    extension Array {
        func someFunction<T>() -> [T]? where Element == Optional<T> {
            try? map { try $0.unwrapOrThrow() }
        }
    }
    

    一旦找到 nil 元素,map 就会抛出错误,try? 返回nil

    【讨论】:

    • 有趣的是,我的基准测试表明,当没有 nil 时,“只是为了好玩”的解决方案实际上相当快:)
    • @Monolith:我添加了另一种非常简单的方法,也许您也可以对其进行基准测试。 (它非常简单,实际上不需要将它作为一种方法来实现。)
    • 是的,我刚刚添加了它!我确实同意铸造解决方案可能是最干净的,尽管它的性能仍然不如其他一些答案。
    • @Monolith 只需添加一个提前退出,以防找到 nil guard !contains(where: {$0 == nil}) else { return nil }
    • 感谢您的反馈。我主要建议的是[T] 的可选演员,其他一切都只是可能的想法。肯定可以选择比unwrapOrThrow 更好的名称(而且您也不会调用someFunction 方法)。顺便说一句,标签并不总是在论点上,例如replaceSubrange(_:with:) 而不是 replace(subrange:with:)。 – 关于你的建议:我不确定引入一个论点是否更好,只是为了给它贴上标签。并且会使用默认参数 unwrap() 调用这样的方法,觉得不清楚。
    【解决方案2】:

    这是一个快速的解决方案:

    extension Array {
        func liftNil<T>() -> [T]? where Element == Optional<T> {
            if (allSatisfy {$0 != nil}) {
                return map { $0! }
            } else {
                return nil
            }
        }
    }
    

    它检查所有元素是否不为零,如果不是,则强制解包。

    用法:

    let x: [Int?] = [1, 2, 3, 4]
    print(x.liftNil())  // prints [1, 2, 3, 4]
    
    let y = [1, 2, nil, 3, 4]
    print(y.liftNil())  // prints nil
    

    基准测试:

    name          time           std        iterations
    --------------------------------------------------
    
    all satisfy 0      147.000 ns ± 2031.47 %    1000000
    all satisfy 1   247828.000 ns ±  22.01 %        5328
    all satisfy 2   124486.000 ns ±  39.23 %       10274
    all satisfy 3  1065633.500 ns ±  21.17 %        1158
    
    exceptions 0     51785.000 ns ± 439.14 %       24331
    exceptions 1    798534.500 ns ±  19.08 %        1600
    exceptions 2    423202.000 ns ±  68.97 %        2978
    exceptions 3    807380.500 ns ±  31.74 %        1632
    
    prefix 0           174.000 ns ± 665.61 %     1000000
    prefix 1        276945.000 ns ±  16.10 %        4402
    prefix 2        139071.000 ns ±  21.79 %        8968
    prefix 3      14788294.000 ns ±  17.71 %         102
    
    loop 0            2520.000 ns ± 1559.85 %     535007
    loop 1         1645015.000 ns ±  80.56 %         823
    loop 2          861541.000 ns ±  27.66 %        1298
    loop 3         1594589.500 ns ±  17.97 %         798
    
    cast 0           55364.000 ns ± 204.48 %       22089
    cast 1        14288104.500 ns ±  16.70 %          92
    cast 2         8464850.000 ns ± 216.15 %         188
    cast 3        14432597.000 ns ±  32.76 %         100
    
    compact map 0  1653247.500 ns ±  25.78 %         696
    compact map 1  1618238.500 ns ±  25.45 %         740
    compact map 2  1685990.000 ns ±  20.90 %         649
    compact map 3  1604636.000 ns ±  20.45 %         805
    

    测试用例:

    // 0: nil at front
    var arrTest0 = [Int?](repeating: 0, count: 5000)
    arrTest1[0] = nil
    
    // 1: nil at end
    var arrTest1 = [Int?](repeating: 0, count: 5000)
    arrTest2[4999] = nil
    
    // 2: nil in middle
    var arrTest2 = [Int?](repeating: 0, count: 5000)
    arrTest3[2500] = nil
    
    // 3: no nils at all
    var arrTest3 = [Int?](repeating: 0, count: 5000)
    

    如您所见,我使用allSatisfy 的解决方案非常快。请注意,Martin R 的“只是为了好玩的解决方案”在没有 nil 的测试用例中使用异常非常快。似乎没有“明显的赢家”,只有在不同情况下的性能之间进行权衡。

    我使用谷歌的swift-benchmark 对所有内容进行了基准测试。基准代码可以在on this github gist找到。

    另请注意,值得对具有多个 nil 的数组进行基准测试。


    这段代码不是很优雅,但它是高效的,因为它只真正(最多)1 次通过数组。当有 nil 时,它也不会比 Martin R 的解决方案更快,因为它会立即返回:

    正如基准测试所示,这实际上比我原来的解决方案要慢。我认为这是因为 .append 方法的开销。

    extension Array {
        func liftNil<T>() -> [T]? where Element == Optional<T> {
            var new: [T] = []
            new.reserveCapacity(count)
            for item in self {
                if let value = item {
                    new.append(value)
                } else {
                    return nil
                }
            }
            return new
        }
    }
    

    【讨论】:

    • 你能在你的答案中提供一些基准吗?
    • 无需使用allSatisfy 遍历所有元素。只需找到一个 nil 元素就足够了 containsfirst(where: 根据您的标准将数组“取消资格”为您的非 nil 结果。
    • @Kamil.S 实际上,性能没有差异,因为allSatisfy 会短路并在看到false 值时返回false。在引擎盖下,allSatisfyuses contains。我选择使用allSatisfy,但contains 也可以。
    【解决方案3】:

    Answer 至少需要 Swift 5.3

    基于https://forums.swift.org/t/generic-function-that-requires-the-generic-type-to-be-non-optional/30936/11我想出了:

    extension Array {
        @inlinable
        public func someMethod() -> [Any]? {
            first {
                if case Optional<Any>.none = $0 as Any {
                    return true
                } else {
                    return false
                }
            } != nil ? nil : map {
                if case Optional<Any>.some(let unwrapedValue) = $0 as Any {
                    return unwrapedValue
                } else {
                    return $0
                }
            }
        }
    }
    
    let x: [Int?] = [1,2,3,4]
    let y = [1, 2, nil, 3, 4]
    let z = [1,2,3,4]
    
    print(x.someMethod())
    print(y.someMethod())
    print(z.someMethod())
    

    请注意,z 的情况不需要输入 [Int?]

    输出:

    Optional([1, 2, 3, 4])
    nil
    Optional([1, 2, 3, 4])
    

    不幸的是,我无法执行仅适用于 nonoptional Twhere 子句。另一轮似乎只适用于where Element == T?,如此处的示例所示。这使我无法正确输入[T] 并诉诸[Any]

    x & ypublic func someMethod&lt;T&gt;() -&gt; [T]? where Element == T? 相当容易。但是,任何将其推广到非可选项的尝试似乎都是一个笼统的术语,因此public func someMethod&lt;T&gt;() -&gt; [T]? where Element == T? 案例不再被调用。

    更新对上述问题的小幅改进,为z 案例提供了精确的输入和更少的处理:

    extension Array {
        @inlinable
        public func someMethod() -> [Element]? {
            return self
        }
        
        @inlinable
        public func someMethod() -> [Any]? where Element: ExpressibleByNilLiteral {
            first {
                if case Optional<Any>.none = $0 as Any {
                    return true
                } else {
                    return false
                }
            } != nil ? nil : map {
                if case Optional<Any>.some(let unwrapedValue) = $0 as Any {
                    return unwrapedValue
                } else {
                    return $0
                }
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      CompactMap 可以用来删除 nils,然后看看计数是否相同。

      extension Array {
         func allOrNone<T>() -> [T]? where Element == Optional<T>   {
            let array = compactMap{$0}
            return array.count == count ? array : nil
         }
      }
      

      【讨论】:

      • 一堆其他类似的答案发布得稍微快一些。给他们任何荣誉!
      猜你喜欢
      • 2021-12-02
      • 1970-01-01
      • 2014-10-04
      • 2012-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多