创建一个扩展来从Swift中的Array过滤nils

时间:2015-01-28 11:05:15

标签: arrays swift generics swift-extensions

我试图编写一个对Array的扩展,这将允许一组可选的T转换为一个非可选的T#。

e.g。这可以写成一个像这样的自由函数:

func removeAllNils(array: [T?]) -> [T] {
    return array
        .filter({ $0 != nil })   // remove nils, still a [T?]
        .map({ $0! })            // convert each element from a T? to a T
}

但是,我无法将其作为扩展工作。我试图告诉编译器该扩展仅适用于可选值的数组。这就是我到目前为止所做的:

extension Array {
    func filterNils<U, T: Optional<U>>() -> [U] {
        return filter({ $0 != nil }).map({ $0! })
    }
}

(它没有编译!)

7 个答案:

答案 0 :(得分:80)

从Swift 2.0开始,你不需要编写自己的扩展来过滤数组中的nil值,你可以使用function A() { this.test = new B(this.foo.bind(this)); this.test.bar(); } 来展平数组并过滤nils:

browser
    .actions()
    .dragAndDrop(myEle, {x:100,y:100})
    .perform();

打印:

    img = cv2.imread(filepath)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret,thresholded = cv2.threshold(gray,200,255,cv2.THRESH_BINARY)

    img = cv2.cvtColor(thresholded, cv2.COLOR_GRAY2BGR)
    gray = thresholded
    gray = gray.astype('float32')

    #padding
    BLUE = [255,0,0]
    rows,cols = gray.shape
    nrows = cv2.getOptimalDFTSize(rows)
    ncols = cv2.getOptimalDFTSize(cols)
    right = ncols - cols
    bottom = nrows - rows
    bordertype = cv2.BORDER_CONSTANT
    gray = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)
    gray = gray.astype('float32')
    dct=cv2.dct(gray)

注意:

有2个flatMap个功能:

答案 1 :(得分:58)

TL; DR

Swift 4

使用array.compactMap { $0 }。 Apple更新了框架,以便它不再导致错误/混淆。

Swift 3

为避免潜在的错误/混淆,请勿使用array.flatMap { $0 }删除nils;使用array.removeNils()之类的扩展方法(下面的实现,为Swift 3.0更新)。

尽管array.flatMap { $0 }大部分时间都有效,但有几个理由支持array.removeNils()扩展:

  • removeNils 准确描述您要执行的操作:删除nil值。不熟悉flatMap的人需要查阅,当他们查找时,如果他们密切关注,他们会得到与我下一点相同的结论;
  • flatMap有两种不同的实现方式可以完成两个完全不同的事情。基于类型检查,编译器将决定调用哪一个。这在Swift中可能非常有问题,因为类型推断被大量使用。 (例如,要确定变量的实际类型,您可能需要检查多个文件。)重构可能导致您的应用调用flatMap的错误版本,这可能导致难以发现的错误即可。
  • 由于有两个完全不同的功能,因此您可以更加难以理解flatMap,因为您可以 easily conflate the two
  • flatMap可以在非可选数组上调用(例如[Int]),因此如果您将数组从[Int?]重构为[Int],您可能意外离开在flatMap { $0 }调用后面,编译器不会警告你。它最多只会自行返回,最坏的情况是它会导致其他实现被执行,从而可能导致错误。
  • 在Swift 3中,如果你没有明确地转换返回类型,编译器会选择错误的版本,这会导致意想不到的后果。 (参见下面的Swift 3部分)
  • 最后,它减慢了编译器的速度,因为类型检查系统需要确定要调用哪些重载函数。

回顾一下,不幸的是,这个函数有两个版本,名为flatMap

  1. 通过删除嵌套级别(例如[[1, 2], [3]] -> [1, 2, 3]

    来展平序列
    public struct Array<Element> : RandomAccessCollection, MutableCollection {
        /// Returns an array containing the concatenated results of calling the
        /// given transformation with each element of this sequence.
        ///
        /// Use this method to receive a single-level collection when your
        /// transformation produces a sequence or collection for each element.
        ///
        /// In this example, note the difference in the result of using `map` and
        /// `flatMap` with a transformation that returns an array.
        ///
        ///     let numbers = [1, 2, 3, 4]
        ///
        ///     let mapped = numbers.map { Array(count: $0, repeatedValue: $0) }
        ///     // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
        ///
        ///     let flatMapped = numbers.flatMap { Array(count: $0, repeatedValue: $0) }
        ///     // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
        ///
        /// In fact, `s.flatMap(transform)`  is equivalent to
        /// `Array(s.map(transform).joined())`.
        ///
        /// - Parameter transform: A closure that accepts an element of this
        ///   sequence as its argument and returns a sequence or collection.
        /// - Returns: The resulting flattened array.
        ///
        /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
        ///   and *n* is the length of the result.
        /// - SeeAlso: `joined()`, `map(_:)`
        public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
    }
    
  2. 从序列中删除元素(例如[1, nil, 3] -> [1, 3]

    public struct Array<Element> : RandomAccessCollection, MutableCollection {
        /// Returns an array containing the non-`nil` results of calling the given
        /// transformation with each element of this sequence.
        ///
        /// Use this method to receive an array of nonoptional values when your
        /// transformation produces an optional value.
        ///
        /// In this example, note the difference in the result of using `map` and
        /// `flatMap` with a transformation that returns an optional `Int` value.
        ///
        ///     let possibleNumbers = ["1", "2", "three", "///4///", "5"]
        ///
        ///     let mapped: [Int?] = numbers.map { str in Int(str) }
        ///     // [1, 2, nil, nil, 5]
        ///
        ///     let flatMapped: [Int] = numbers.flatMap { str in Int(str) }
        ///     // [1, 2, 5]
        ///
        /// - Parameter transform: A closure that accepts an element of this
        ///   sequence as its argument and returns an optional value.
        /// - Returns: An array of the non-`nil` results of calling `transform`
        ///   with each element of the sequence.
        ///
        /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
        ///   and *n* is the length of the result.
        public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
    }
    
  3. #2是人们用{ $0 }作为transform来删除nils的人。这是有效的,因为该方法执行地图,然后过滤掉所有nil元素。

    你可能想知道&#34;为什么Apple没有将#2重命名为removeNils()&#34; 要注意的一点是使用flatMap删除nils不是#2的唯一用法。实际上,由于两个版本都采用transform函数,因此它们可能比上面的示例更强大。

    例如,#1可以轻松地将字符串数组拆分为单个字符(展平)并将每个字母(地图)大写:

    ["abc", "d"].flatMap { $0.uppercaseString.characters } == ["A", "B", "C", "D"]
    

    虽然数字#2可以轻松删除所有偶数(展平)并将每个数字乘以-1(地图):

    [1, 2, 3, 4, 5, 6].flatMap { ($0 % 2 == 0) ? nil : -$0 } == [-1, -3, -5]
    

    (请注意,最后一个示例可能会导致Xcode 7.3旋转很长时间,因为没有明确的类型说明。进一步证明了为什么这些方法应该有不同的名称。)

    盲目使用flatMap { $0 }删除nil的真正危险不是在[1, 2]上调用它时,而是在[[1], [2]]之类的时候调用它。在前一种情况下,它会无害地调用#2调用并返回[1, 2]。在后一种情况下,您可能认为它会做同样的事情(因为没有[[1], [2]]值而无害地返回nil),但实际上它将返回[1, 2],因为它使用了flatMap { $0 }调用#1。

    nil用于删除removeNils()的事实似乎更像是Swift community recommendation,而不是来自Apple。也许如果Apple注意到这种趋势,他们最终会提供// Updated for Swift 3.0 protocol OptionalType { associatedtype Wrapped func map<U>(_ f: (Wrapped) throws -> U) rethrows -> U? } extension Optional: OptionalType {} extension Sequence where Iterator.Element: OptionalType { func removeNils() -> [Iterator.Element.Wrapped] { var result: [Iterator.Element.Wrapped] = [] for element in self { if let element = element.map({ $0 }) { result.append(element) } } return result } } 函数或类似的东西。

    在此之前,我们已经提出了自己的解决方案。

    解决方案

    element.map

    (注意:不要对flatMap感到困惑......这与本帖中讨论的map()无关。它使用Optional's map function获取可以解包的可选类型。如果省略此部分,则会出现此语法错误:&#34;错误:条件绑定的初始化程序必须具有可选类型,而不是“Self.Generator”。元素&#39;。&#34;有关let a: [Int?] = [1, nil, 3] a.removeNils() == [1, 3] 如何帮助我们的详细信息,请参阅this answer I wrote about adding an extension method on SequenceType to count non-nils。)

    用法

    var myArray: [Int?] = [1, nil, 2]
    assert(myArray.flatMap { $0 } == [1, 2], "Flat map works great when it's acting on an array of optionals.")
    assert(myArray.removeNils() == [1, 2])
    
    var myOtherArray: [Int] = [1, 2]
    assert(myOtherArray.flatMap { $0 } == [1, 2], "However, it can still be invoked on non-optional arrays.")
    assert(myOtherArray.removeNils() == [1, 2]) // syntax error: type 'Int' does not conform to protocol 'OptionalType'
    
    var myBenignArray: [[Int]?] = [[1], [2, 3], [4]]
    assert(myBenignArray.flatMap { $0 } == [[1], [2, 3], [4]], "Which can be dangerous when used on nested SequenceTypes such as arrays.")
    assert(myBenignArray.removeNils() == [[1], [2, 3], [4]])
    
    var myDangerousArray: [[Int]] = [[1], [2, 3], [4]]
    assert(myDangerousArray.flatMap { $0 } == [1, 2, 3, 4], "If you forget a single '?' from the type, you'll get a completely different function invocation.")
    assert(myDangerousArray.removeNils() == [[1], [2, 3], [4]]) // syntax error: type '[Int]' does not conform to protocol 'OptionalType'
    

    实施例

    [1, 2, 3, 4]

    (注意最后一个,flatMap返回[[1], [2, 3], [4]],而removeNils()应该返回flatten。)

    该解决方案类似于链接到的answer @fabb。

    但是,我做了一些修改:

    • 我没有命名方法flatten,因为已经有一个flatten方法用于序列类型,并且给完全不同的方法赋予相同的名称是让我们陷入困境的原因第一名。更不用说错误解释removeNils所做的事情比T更容易。
    • 不是在OptionalType上创建新类型Optional,而是使用Wrapped使用的相同名称(O(M + N))。
    • 而不是导致flatMap时间的performing map{}.filter{}.map{},而是循环遍历数组。
    • 我使用Generator.Element而不是Generator.Element.Wrapped?map转到nil。我们无需在map函数中返回map值,因此flatMap就足够了。通过避免removeNils函数,将另一个(即第三个)方法与具有完全不同功能的相同名称混淆起来更加困难。

    使用flatMap[1, nil, 3].flatMap { $0 } // works [1, nil, 3].removeNils() // syntax error: type of expression is ambiguous without more context // but it's not all bad, since flatMap can have similar problems when a variable is used: let a = [1, nil, 3] // syntax error: type of expression is ambiguous without more context a.flatMap { $0 } a.removeNils() 的一个缺点是类型检查可能需要更多暗示:

    extension SequenceType {
      func removeNils() -> Self {
        return self
      }
    }
    

    我还没有调查过,但似乎你可以补充一下:

    flatMap { $0 }

    如果您希望能够在包含非可选元素的数组上调用该方法。这可以使重大名称重命名(例如removeNils() - > var a: [String?] = [nil, nil] var b = a.flatMap{$0} b // == [] a = a.flatMap{$0} a // == [nil, nil] )。

    分配给self不同于分配给新变量?!

    看看以下代码:

    a = a.flatMap { $0 }

    令人惊讶的是,a在您将其分配给b时不会删除nils,但是当您将其分配给flatMap 删除nils!我的猜测是,这与重载的a = a.flatMap { $0 } as [String] a // == [] 和Swift选择我们并不意味着使用的那个有关。

    您可以通过将问题强制转换为预期类型来暂时解决问题:

    removeNils()

    但这很容易忘记。相反,我建议使用上面的flatMap方法。

    更新

    似乎有人建议弃用{{1}}的至少一个(3)重载:https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md

答案 2 :(得分:42)

不可能限制为通用结构或类定义的类型 - 该数组旨在用于任何类型,因此您无法添加适用于类型子集的方法。只能在声明泛型类型

时指定类型约束

实现所需要的唯一方法是创建全局函数或静态方法 - 在后一种情况下:

extension Array {
    static func filterNils(array: [T?]) -> [T] {
        return array.filter { $0 != nil }.map { $0! }
    }
}

var array:[Int?] = [1, nil, 2, 3, nil]

Array.filterNils(array)

或者只使用compactMap(之前为flatMap),可用于删除所有nil值:

[1, 2, nil, 4].compactMap { $0 } // Returns [1, 2, 4]

答案 3 :(得分:11)

从Swift 2.0开始,可以使用where子句添加适用于类型子集的方法。正如本Apple Forum Thread中所讨论的,这可用于过滤掉数组的nil值。积分转到@nnnnnnn和@SteveMcQwark。

由于where条款尚不支持泛型(如Optional<T>),因此需要通过协议解决方法。

protocol OptionalType {  
    typealias T  
    func intoOptional() -> T?  
}  

extension Optional : OptionalType {  
    func intoOptional() -> T? {  
        return self.flatMap {$0}  
    }  
}  

extension SequenceType where Generator.Element: OptionalType {  
    func flatten() -> [Generator.Element.T] {  
        return self.map { $0.intoOptional() }  
            .filter { $0 != nil }  
            .map { $0! }  
    }  
}  

let mixed: [AnyObject?] = [1, "", nil, 3, nil, 4]  
let nonnils = mixed.flatten()    // 1, "", 3, 4  

答案 4 :(得分:11)

雨燕4

如果您有幸使用Swift 4,则可以使用compactMap

过滤掉nil值。

array = array.compactMap { $0 }

例如

let array = [1, 2, nil, 4]
let nonNilArray = array.compactMap { $0 }

print(nonNilArray)
// [1, 2, 4]

答案 5 :(得分:5)

Swift 4

这适用于Swift 4:

protocol OptionalType {
    associatedtype Wrapped
    var optional: Wrapped? { get }
}

extension Optional: OptionalType {
    var optional: Wrapped? { return self }
}

extension Sequence where Iterator.Element: OptionalType {
    func removeNils() -> [Iterator.Element.Wrapped] {
        return self.flatMap { $0.optional }
    }
}

测试:

class UtilitiesTests: XCTestCase {

    func testRemoveNils() {
        let optionalString: String? = nil
        let strings: [String?] = ["Foo", optionalString, "Bar", optionalString, "Baz"]
        XCTAssert(strings.count == 5)
        XCTAssert(strings.removeNils().count == 3)
        let integers: [Int?] = [2, nil, 4, nil, nil, 5]
        XCTAssert(integers.count == 6)
        XCTAssert(integers.removeNils().count == 3)
    }
}

答案 6 :(得分:0)

Swift 5.3及更高版本的解决方案

extension Array where Element == Any? {
   /**
    * Remove optionals from array
    * ## Examples:
    * Array.filterNils([2,nil,1,0]) // [2,1,0]
    * let someArr: [Int?] = [2,nil,1,0]
    * Array.filterNils(someArr) // [2,1,0]
    */
   static func filterNils<T>(_ array: [T?]) -> [T] {
      return array.compactMap { $0 }
   }
}