【问题标题】:Any way to iterate a tuple in swift?有什么方法可以快速迭代一个元组?
【发布时间】:2014-08-09 13:05:37
【问题描述】:

我很好奇如何在 swift 中使用元组进行 for 循环。

我知道要访问每个成员,您可以使用索引号使用点表示法

var tupleList = ("A",2.9,3,8,5,6,7,8,9)

for each in tupleList {
    println(each)
}

//错误:类型不符合协议顺序

【问题讨论】:

  • 如果要迭代它,为什么要使用元组而不是数组?
  • 似乎,至少对我来说,从 C 标头导入的固定大小的数组变成了元组。然而,这可能是没有固定大小的数组的副作用。
  • 现在让数组固定不变。我认为那又回到了 beta 3
  • 问题仍然是固定的 C 数组可以是可变的。
  • @drewag 也许你想检查一个元组的元素并将它们标准化。您可以使用数组,但您的代码更简洁,需要一个固定大小的元组和特定的元素标签。你也可以写一个规范化func,专门用于你的固定大小的元组,但是本着保持代码干燥的精神,你真的想要一个通用的解决方案。

标签: swift


【解决方案1】:

是的,你可以!

func iterate<C,R>(t:C, block:(String,Any)->R) {
    let mirror = reflect(t)
    for i in 0..<mirror.count {
        block(mirror[i].0, mirror[i].1.value)
    }
}

瞧!

let tuple = ((false, true), 42, 42.195, "42.195km")
iterate(tuple) { println("\($0) => \($1)") }
iterate(tuple.0){ println("\($0) => \($1)")}
iterate(tuple.0.0) { println("\($0) => \($1)")} // no-op

注意最后一个不是元组所以什么都不会发生(虽然它是一个 1 元组或“单”,其内容可以访问 .0reflect(it).count 为 0)。

有趣的是iterate() 甚至可以迭代其他类型的集合。

iterate([0,1])              { println("\($0) => \($1)") }
iterate(["zero":0,"one":1]) { println("\($0) => \($1)") }

该集合包括classstruct

struct Point { var x = 0.0, y = 0.0 }
class  Rect  { var tl = Point(), br = Point() }
iterate(Point()) { println("\($0) => \($1)") }
iterate(Rect())  { println("\($0) => \($1)") }

警告:作为块的第二个参数传递的值是类型Any。您必须将其转换回原始类型的值。

【讨论】:

  • 在 swift 2 中,reflect(t) 被 Mirror(reflecting: t) 取代,但这仅记录为操场/调试功能。
  • Jafar,我不认为 the documentation 暗示了镜像将由游乐场和调试器使用。文档中特别说明的是“游乐场和调试器使用镜像。”
  • 在 Swift 4 with a bit of change in syntax987654322@
【解决方案2】:

您可以使用反射 Swift 5

在操场上试试这个:

let tuple = (1, 2, "3")
let tupleMirror = Mirror(reflecting: tuple)
let tupleElements = tupleMirror.children.map({ $0.value })
tupleElements

输出:

【讨论】:

  • 考虑为非游乐场添加 print(tupleElements)
【解决方案3】:

Swift 目前不支持对元组进行迭代。

最大的原因是:

  1. 在运行时无法确定元组中元素的数量
  2. 除了像tupleList.0 这样的编译时访问器之外,无法访问特定索引处的元素。你真的想要一个下标tupleList[0],但我们没有提供这个

坦率地说,如果你想迭代它,我看不出你会使用元组而不是数组的理由。

遍历一个元组是没有意义的,因为:

  1. 元组总是有一个固定的长度,每个元素都有一个固定的类型
  2. 您可以为每个元组成员命名,以便以后访问它

数组可以很好地迭代:

  1. 任意长度
  2. 可以使用通用超类或 AnyObject 存储多种类型
  3. 可以以类似于元组的方式声明为文字:var list = ["A",2.9,3,8,5,6,7,8,9]

【讨论】:

  • 我正在考虑迭代一个函数产生的元组。
  • 您可以在函数的结果中命名该类型的每个成员,并按名称访问每个元素。元组是一种廉价的结构,您不必为其预定义类型。您也不能迭代结构的成员。 (至少直到/如果他们向 Swift 添加反射)
  • 知道了。谢谢你的解释!
  • 例如,如果您的成员不是同一类型,则不能用元组替换数组。
  • 我也遇到了同样的情况,假设我有“左”和“右”两个边。所以我更喜欢创建一个像let side = (left:"left", right:"right") 这样的元组,这样我就可以按名称访问它们以使其无错误。假设我有 10 类对象,每个类别都有左右类型,所以我想遍历每个类别。我怎样才能?我不会使用数组,因为在某些情况下我想使用 side.left 之类的东西
【解决方案4】:

@dankogai's excellent solution,为 Swift 3.0 更新:

func iterate<Tuple>(_ tuple:Tuple, body:(_ label:String?,_ value:Any)->Void) {
    for child in Mirror(reflecting: tuple).children {
        body(child.label, child.value)
    }
}

用法与@dankogai 的示例保持一致(Swift 2 的println()print() 重命名除外)

请注意,标签现在的类型是 String?,而以前是 String,以匹配从 Swift 1 的 MirrorType.subscript(…).0 到 Swift 3 的 Mirror.Child.label 的类型更改。但是,对于无标签元组,label arg 以 ".0"".1"".2" 等形式返回 - 对于某些其他类型,只有 nil

另外,我冒昧地重命名类型和参数以更好地匹配Swift 3's solidified naming standards,并将闭包返回类型更改为Void

旁注:我注意到有人在这里对我投了反对票——我无法想象为什么,除了(公平的)论点之外,在 Swift 中围绕反射构建应用程序功能正在破解类型系统,并且很可能导致整个代码很糟糕(Swift 的元组不应被视为抽象数据类型,而应被视为变量的小集合,类似于方法 args)。作为反驳,我最初最终将它移植到项目中的 Swift 3 是因为我需要它——为了更好的 descriptions 和 debugDescriptions。因为理智的调试输出将为您节省数小时的挫败感。 ;-)  此外,这对于单元测试可能非常有用……因为测试最终最感兴趣的是“此操作的结果是否符合我们的预期?”

【讨论】:

    【解决方案5】:

    详情

    • Xcode 11.2.1 (11B500)、Swift 5.1

    基础溶液

    struct Tuple<T> {
        let original: T
        private let array: [Mirror.Child]
        init(_ value: T) {
            self.original = value
            array = Array(Mirror(reflecting: original).children)
        }
        func forEach(closure: (Mirror.Child) -> Void) { array.forEach { closure($0) } }
        func getOnlyValues<T: Any>() -> [T] { array.compactMap { $0.value as? T } }
        func getAllValues() -> [Any] { array.compactMap { $0.value } }
    }
    

    在基础溶液中的使用

    let num: Int? = 3
    let str: String? = nil
    let x = (1, "stew", str, 5.4, 2, num)
    
    let tuple = Tuple(x)
    tuple.forEach { print("\($0)") }
    
    print("\(tuple.getAllValues())")            // [1, "stew", nil, 5.4, 2, Optional(3)]
    print("\(tuple.getOnlyValues() as [Int])")  // [1, 2, 3]
    

    多加糖

    func valuesFrom<V>(tuple: V) -> [Any] { return Tuple(tuple).getAllValues() }
    func onlyValuesFrom<T,V>(tuple: V) -> [T] { return Tuple(tuple).getOnlyValues() as [T] }
    
    print(valuesFrom(tuple: x))                 // [1, "stew", nil, 5.4, 2, Optional(3)]
    print(onlyValuesFrom(tuple: x) as [Int])    // [1, 2, 3]
    

    【讨论】:

      【解决方案6】:

      不,你不能。原因是元组项并不都必须具有相同的类型,因此您将无法知道each 应该具有什么类型。

      【讨论】:

      • 这至少是我所听到的任何理由都是有效的任意理由 :) 但是,要完全购买它,将意味着您无法迭代 NSArray :)
      • AnyAnyObject 可以用来表示任何东西。这就是 NSArray 或来自 Objective-C 的 id 的任何其他翻译中使用的内容
      • 函数类型除外,出于某种原因:“任何类型都可以表示任何类型的实例,除了函数类型。”他们应该解决这个问题。
      • @jasamer 我不同意。 Any 可以表示任何 数据类型,这在 Swift 等 OO 语言中在逻辑上是不同的。函数引用是 OO 中的另一种野兽——它们旨在直接调用或静态绑定(编译时),而不是在集合中传递或动态转换(这将需要运行时解释)。也就是说,许多看似合理的“任何功能”用例都可以在 Swift 中使用泛型来解决。
      猜你喜欢
      • 1970-01-01
      • 2016-07-06
      • 2012-08-16
      • 2020-06-12
      • 2011-10-22
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多