问题
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