Swift function taking generic array

微笑、不失礼 提交于 2019-12-24 10:49:19

问题


I'm trying to write a simple hand-rolled function to flatten a nested array. My code works fine as a switch statement, but not when I'm recursively calling it within a "for i in 0..arr.count" loop. I look at each element in the passed in array and say "if this is an array, call the function and pass this in, else append to the output array".

func flatten (input: [Any]) -> [Any] {


    var outputArray = [Any] ()

    for i in 0..<input.count {
        let data = input[i];
        (data is Array) ? outputArray += flatten(input: [data]) : outputArray.append(data)

    }

    return outputArray

}

Because my function argument must be of type [Any], I'm forced to pass the "data" variable back into it as [data]. This just forces the function to constantly unpack the array and it just gets stuck in the loop. Any suggestions on how I can avoid this?


回答1:


To start, you're using the ternary operator (?:) in quite a complex situation. If you first modify your code to use an if statement instead, the compiler error that was appearing in the call to outputArray.append(data) - Generic parameter 'Element' could not be inferred - appears in a much more sensible place (namely, the line of the if statement).

That error is relatively easy to solve - simply replace Array with Array<Any>, giving us this:

func flatten(input: [Any]) -> [Any] {
    var outputArray = [Any]()

    for i in 0..<input.count {
        let data = input[i]
        if data is Array<Any> {
            outputArray += flatten(input: [data])
        } else {
            outputArray.append(data)
        }
    }

    return outputArray
}

At this point, the original problem still occurs, because the value that's passed in to flatten(input:) is [data]. We know, due to the fact that the if condition was true, that data is really of type Array<Any>, and so the value [data] must be an Array<Array<Any>>. Instead, we want to pass in data, which is already an array.

You say that the reason you wrote [data] is because the argument has to be an array, and so you were "forced to" by the compiler. In fact, the only thing the compiler is forcing you to do is pass in an argument whose type is declared as Array<Any>. We've made sure that data is an array using the if statement, but data is still declared as an Any (because it was an element of input, which is an Array<Any>), so the compiler has no idea that it's really an array.

The solution is - instead of using if data is Array<Any> to determine momentarily whether data is an array but immediately throw that information away - to convert data to an Array<Any>.

The new if statement becomes if let dataArray = data as? Array<Any>. The statement data as? Array<Any> attempts to convert data to an array, returning a value of type Array<Any> if successful or nil otherwise. Then the if let dataArray = ... statement stores the value in dataArray and returns true if given a non-nil value, or returns false if given a nil value (this is called conditional binding).

By doing that, in the true case of the if statement we have access to a value dataArray that is of type Array<Any> - unlike data, which is only declared as Any. Then dataArray can be passed in to flatten(input:), and won't be nested inside another Array.

func flatten(input: [Any]) -> [Any] {
    var outputArray = [Any]()

    for i in 0..<input.count {
        let data = input[i]
        if let dataArray = data as? Array<Any> {
            outputArray += flatten(input: dataArray)
        } else {
            outputArray.append(data)
        }
    }

    return outputArray
}

A couple of other notes:

Array<Any> is of course equivalent to [Any], so the if statement could be written with that (generally preferred) syntax, like so:

if let dataArray = data as? [Any] {
    outputArray += flatten(input: dataArray)
}

Also, there's no need to go through the whole for i in 0..<input.count { let data = input[i] ... ordeal if you just iterate over the array instead, like so:

func flatten(input: [Any]) -> [Any] {
    var outputArray = [Any]()

    for data in input {
        if let dataArray = data as? [Any] {
            outputArray += flatten(input: dataArray)
        } else {
            outputArray.append(data)
        }
    }

    return outputArray
}


来源:https://stackoverflow.com/questions/42587629/swift-function-taking-generic-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!