在Swift

时间:2015-11-12 03:58:59

标签: swift multidimensional-array dimensions

假设我有一些函数需要使用多维数组(例如Tensor类)填充我的数据结构:

class Tensor {
   init<A>(array:A) { /* ... */ }
}

虽然我可以添加shape参数,但我更愿意自动计算数组本身的尺寸。如果你知道apriori的尺寸,阅读它是微不足道的:

let d1 = array.count
let d2 = array[0].count

但是,对于N维数组,如何做到这一点还不太清楚。我想可能有办法通过扩展Array类来实现它:

extension Int {
   func numberOfDims() -> Int {
      return 0
   }
}

extension Array {
   func numberOfDims() -> Int {
     return 1+Element.self.numberOfDims()
   }
}

不幸的是,这不会(理所当然地)编译,因为大多数类型都没有定义numberOfDims。但是,我没有看到任何约束Element的方法,因为Arrays-of-Arrays使事情变得复杂。

我希望其他人可能对如何解决这个问题有所了解(或解释为什么这是不可能的)。

3 个答案:

答案 0 :(得分:2)

一个很棒的问题,让我在鹅追逐!

要明确:我在下面谈论使用最外层数组的泛型类型参数来计算维数。正如Tyrelidrel所示,您可以递归检查第一个元素的运行时类型 - 尽管这种方法为异构数组(如[[[1], 2], 3])提供了无意义的答案。

基于类型的调度无法正常工作

如您所知,您编写的代码不起作用,因为没有为所有类型定义numberOfDims。但是有解决方法吗?这个方向会导致某个方向吗?

不,这是死路一条。原因是对于非类类型静态调度扩展方法,如下面的代码片段所示:

extension CollectionType {
  func identify() {
    print("I am a collection of some kind")
  }

  func greetAndIdentify() {
    print("Hello!")
    identify()
  }
}

extension Array {
  func identify() {
    print("I am an array")
  }
}

[1,2,3].identify()         // prints "I am an array"
[1,2,3].greetAndIdentify() // prints "Hello!" and "I am a collection of some kind"

即使如果 Swift允许您扩展Any(但事实并非如此),Element.self.numberOfDims()总是调用{{1} Any的实现,即使numberOfDims()的运行时类型是数组。

这种破碎的静态调度限制意味着即使这种看起来很有希望的方法也会失败(它编译,但总是返回1):

Element.self

同样的约束也适用于函数重载。

类型检查无法正常工作

如果有一种方法可以使它工作,那么沿着这些方向,它将使用条件而不是基于类型的方法调度来遍历嵌套的数组类型:

extension CollectionType {
  var numberOfDims: Int {
    return self.dynamicType.numberOfDims
  }

  static var numberOfDims: Int {
    return 1
  }
}

extension CollectionType where Generator.Element: CollectionType {
  static var numberOfDims: Int {
    return 1 + Generator.Element.numberOfDims
  }
}

[[1],[2],[3]].numberOfDims   // return 1 ... boooo!

上面的代码编译 - 相当容易混淆 - 因为Swift将extension Array { var numberOfDims: Int { return self.dynamicType.numberOfDims } static var numberOfDims: Int { if let nestedArrayType = Generator.Element.self as? Array.Type { return 1 + nestedArrayType.numberOfDims } else { return 1 } } } [[1,2],[2],[3]].numberOfDims 作为Array.Type的快捷方式。这完全打败了解开的尝试。

解决方法是什么?没有一个。这种方法不起作用,因为我们需要说“if Array<Element>.Type是某种Element”,但据我所知,Swift无法说“任何事物的数组”,或者“只有Array类型,无论Array。”

无论你提到Element类型,它的泛型类型参数必须在编译时具体化为具体类型或协议。

作弊可以起作用

那么反思怎么样?有一种方法。不是一个好方法,但有一种方法。 Swift的Array目前还不足以告诉我们元素类型是什么,但还有另一种足够强大的反射方法:将类型转换为字符串。

Mirror

可怕的,邪恶的,脆弱的,可能在所有国家都不合法 - 但它有效!

答案 1 :(得分:1)

不幸的是,我无法使用Swift数组执行此操作,但您可以轻松地将swift数组转换为NSArray。

extension NSArray {
    func numberOfDims() -> Int {
        var count = 0
        if let x = self.firstObject as? NSArray {
            count += x.numberOfDims() + 1
        } else {
            return 1
        }
        return count
    }
}

答案 2 :(得分:1)

如果您希望获得嵌套数组的深度(Swift的标准库并不是技术上为您提供多维数组,只有锯齿状数组) - 然后,如this Q&A所示,您可以使用“虚拟协议”&#39;和类型转换。

protocol _Array {
    var nestingDepth: Int { get }
}

extension Array : _Array {
    var nestingDepth: Int {
        return 1 + ((first as? _Array)?.nestingDepth ?? 0)
    }
}

let a = [1, 2, 3]
print(a.nestingDepth) // 1

let b = [[1], [2, 3], [4]]
print(b.nestingDepth) // 2

let c = [[[1], [2]], [[3]], [[4], [5]]]
print(c.nestingDepth) // 3

(我相信当你最初发布问题时,这种方法仍然有效)

在Swift 3中,这也可以在没有伪协议的情况下实现,而是通过转换为[Any]来实现。但是,如链接的Q&amp; A中所述,这是低效的,因为它需要遍历整个数组以便在存在容器中封装每个元素。

另请注意,此实现假定您在同构嵌套数组上调用它。 As Paul notes,它无法为[[[1], 2], 3]提供正确答案。

如果需要考虑这个问题,你可以编写一个递归方法,它将迭代每个嵌套数组并返回嵌套的最小深度。

protocol _Array {
    func _nestingDepth(minimumDepth: Int?, currentDepth: Int) -> Int
}

extension Array : _Array {

    func _nestingDepth(minimumDepth: Int?, currentDepth: Int) -> Int {

        // for an empty array, the minimum depth is the current depth, as we know
        // that _nestingDepth is called where currentDepth <= minimumDepth.
        guard !isEmpty else { return currentDepth }

        var minimumDepth = minimumDepth

        for element in self {

            // if current depth has exceeded minimum depth, then return the minimum.
            // this allows for the short-circuiting of the function.
            if let minimumDepth = minimumDepth, currentDepth >= minimumDepth {
                return minimumDepth
            }

            // if element isn't an array, then return the current depth as the new minimum,
            // given that currentDepth < minimumDepth.
            guard let element = element as? _Array else { return currentDepth }

            // get the new minimum depth from the next nesting,
            // and incrementing the current depth.
            minimumDepth = element._nestingDepth(minimumDepth: minimumDepth,
                                                 currentDepth: currentDepth + 1)
        }

        // the force unwrap is safe, as we know array is non-empty, therefore minimumDepth 
        // has been assigned at least once.
        return minimumDepth!
    }

    var nestingDepth: Int {
        return _nestingDepth(minimumDepth: nil, currentDepth: 1)
    }
}

let a = [1, 2, 3]
print(a.nestingDepth) // 1

let b = [[1], [2], [3]]
print(b.nestingDepth) // 2

let c = [[[1], [2]], [[3]], [[5], [6]]]
print(c.nestingDepth) // 3

let d: [Any] = [ [[1], [2], [[3]] ], [[4]], [5] ]
print(d.nestingDepth) // 2 (the minimum depth is at element [5])