【问题标题】:How to unwrap the elements of an Array in Swift? (ie. Array<Int?> as Array<Int>)如何在 Swift 中解开数组的元素? (即 Array<Int?> as Array<Int>)
【发布时间】:2017-03-22 00:17:59
【问题描述】:

假设我有一个字符串数组,我想将它映射到一个 Int 数组 我可以使用地图功能:

var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.map { $0.toInt() }
// numbersOptional = "[Optional(0), nil]"

Numbers 现在是一个 Int 数组?但我想要一个 Int 数组。 我知道我可以做到:

let numbers = arrayOfStrings.map { $0.toInt() }.filter { $0 != nil }.map { $0! }
// numbers = [0]

但这似乎不是很快。从 Int 数组转换? to Array of Int 需要使用非常多的样板文件调用过滤器和映射。有没有更快捷的方法来做到这一点?

【问题讨论】:

  • 你希望“a”如何映射到Int
  • @Zaph "a".toInt() 返回 nil,所以我希望它从我的数组中过滤掉。

标签: ios swift


【解决方案1】:

更新:从 Swift 1.2 开始,数组有一个内置的 flatMap 方法,但它不接受 Optionals,所以下面的帮助器仍然有用。


我喜欢使用辅助函数 flatMap 来处理这类事情,就像 Scala 对集合的 flatMap 方法(可以将 Scala Option 视为包含 0 或 1 个元素的集合,粗略地说):

func flatMap<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T?) -> [T] {
    var buffer = [T]()
    for elem in source {
        if let mappedElem = transform(elem) {
            buffer.append(mappedElem)
        }
    }
    return buffer
}

let a = ["0", "a", "42"]
let b0 = map(a, { $0.toInt() }) // [Int?] - [{Some 0}, nil, {Some 42}]
let b1 = flatMap(a, { $0.toInt() }) // [Int] - [0, 42]

flatMap 的这个定义是Optional 的一个特例,更一般的flatMap 应该做什么:

func flatMap<C : CollectionType, T : CollectionType>(source: C, transform: (C.Generator.Element) -> T) -> [T.Generator.Element] {
    var buffer = [T.Generator.Element]()
    for elem in source {
        buffer.extend(transform(elem))
    }
    return buffer
}

我们会去哪里

let b2 = flatMap(a, { [$0, $0, $0] }) // [String] - ["0", "0", "0", "a", "a", "a", "42", "42", "42"]

【讨论】:

    【解决方案2】:

    使用reduce 来构建新数组可能更惯用

    func filterInt(a: Array<String>) -> Array<Int> {
        return a.reduce(Array<Int>()) {
            var a = $0
            if let x = $1.toInt() {
                a.append(x)
            }
            return a
        }
    }
    

    例子

    filterInt(["0", "a", "42"]) // [0, 42] 
    

    您真正想要的是collect (map + filter) 方法。鉴于您需要应用的特定过滤器,在这种情况下,即使是 flatMap 也可以使用(请参阅 Jean-Philippe 的回答)。可惜 swift 标准库没有提供这两种方法。

    【讨论】:

    • 同时使用'a'作为参数名和变量名有点混乱。
    • 在我看来就像为每个附加项目创建了一个新数组,所有的复制开销.. O(n^2)
    【解决方案3】:

    更新:Xcode 7.2 • Swift 2.1.1

    let arrayOfStrings = ["0", "a", "1"]
    let numbersOnly = arrayOfStrings.flatMap { Int($0) }
    
    print(numbersOnly)   // [0,1]
    

    【讨论】:

      【解决方案4】:

      没有好的内置 Swift 标准库方法可以做到这一点。但是,Haskell 有一个函数 mapMaybe,它可以满足您的需求(假设您只想抛弃 nil 值)。这是 Swift 中的等价物:

      func mapSome<S: SequenceType, D: ExtensibleCollectionType>
        (source: S, transform: (S.Generator.Element)->D.Generator.Element?)
        -> D {
              var result = D()
              for x in source {
                  if let y = transform(x) {
                      result.append(y)
                  }
              }
              return result
      }
      
      // version that defaults to returning an array if unspecified
      func mapSome<S: SequenceType, T>
        (source: S, transform: (S.Generator.Element)->T?) -> [T] {
          return mapSome(source, transform)
      }
      
      let s = ["1","2","elephant"]
      mapSome(s) { $0.toInt() }  // [1,2]
      

      【讨论】:

        【解决方案5】:

        可以考虑使用reduce,更灵活:

        var arrayOfStrings: Array = ["0", "a"]
        let numbersOptional = arrayOfStrings.reduce([Int]()) { acc, str in
          if let i = str.toInt() {
            return acc + [i]
          }
        
          return acc
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-06-10
          • 1970-01-01
          • 2013-05-20
          • 2019-06-18
          • 2020-07-19
          • 2020-04-12
          • 2020-08-10
          相关资源
          最近更新 更多