计算Swift中多维数组的维数



假设我有一个函数,我想用多维数组(例如张量类)填充我的数据结构:

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

虽然我可以添加shape参数,但我更喜欢从数组本身自动计算维度。如果你事先知道尺寸,那么读出来就很简单了:

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的方法,因为数组的数组会使事情变得复杂。

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

如果你想获得嵌套数组的深度(Swift的标准库在技术上没有为你提供多维数组,只有锯齿状数组),那么,如本问答中所示;A、 您可以使用"伪协议"和类型转换。

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]来实现。然而,正如链接问答中所指出的;A、 这是低效的,因为它需要遍历整个数组才能将每个元素装箱到存在容器中。

还要注意,此实现假设在同构嵌套数组上调用它。正如Paul所指出的,它不会给出[[[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])

这个问题太棒了,让我大吃一惊!

需要明确的是:我在下面讨论使用最外层数组的泛型类型参数来计算维数的方法。正如Tyrelidle所示,您可以递归地检查第一个元素的运行时类型——尽管这种方法对[[[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()也会始终调用numberOfDims()Any实现,即使Element.self的运行时类型是Array。

这种令人崩溃的静态调度限制意味着即使这种看起来很有前途的方法也会失败(它会编译,但总是返回1):

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!

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

型式检验不起作用

如果有一种方法可以让它发挥作用,那就是沿着以下几条线,使用条件而不是基于类型的方法调度来遍历嵌套的数组类型:

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

上面的代码编译起来相当混乱,因为Swift将Array.Type作为Array<Element>.Type的快捷方式。这完全挫败了打开包装的企图。

解决方法是什么?没有。这种方法不起作用,因为我们需要说"如果Element是某种Array",但据我所知,Swift中没有办法说"任何东西的数组",或者"只是Array类型,而不考虑Element。"

无论您提到什么Array类型,它的泛型类型参数都必须在编译时具体化为具体类型或协议。

作弊可以奏效

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

private let arrayPat = try! NSRegularExpression(pattern: "Array<", options: [])
extension Array {
  var numberOfDims: Int {
    let typeName = "(self.dynamicType)"
    return arrayPat.numberOfMatchesInString(
        typeName, options: [], range: NSMakeRange(0, typeName.characters.count))
  }
}

可怕、邪恶、脆弱,可能不是所有国家都合法——但它有效!

不幸的是,我无法用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
    }
}

最新更新