Home > Blockchain >  How to return a generic collection or iterator in Swift?
How to return a generic collection or iterator in Swift?

Time:12-24

I'm trying to do something like this:

func fullOrSubarray(array: [Int]) -> ReturnType {
    return array.count < 10 ? array : array[0..<10]
}

What should go I write into ReturnType as I'm trying to return Array<Int> or ArraySlice<Int>?

I know I can simply convert ArraySlice<Int> to Array<Int> (or vice versa), but I don't want that for performance reasons. I want to return some generic collection or iterator type, but Swift doesn't seem to have one. Do I have to write a custom iterator for a thing like this? Other languages have all sorts of generic iterators so I can return Array, Set, etc interchangeably.

CodePudding user response:

An alternative to a generic solution here is to introduce an enum with cases for each possible return type and use it as the return type of the function

enum ArrayOrSlice<ElementType> {
    case array(Array<ElementType>)
    case slice(ArraySlice<ElementType>)
}

Then change the function to

func fullOrSubarray<ElementType>(array: [ElementType]) -> ArrayOrSlice<ElementType> {
    return array.count < 10 ? .array(array) : .slice(array[0..<10])
}

I made some changes so here is an example with my alternative

func fullOrSlice<ElementType>(array: [ElementType], ceiling: Int = 10) -> ArrayOrSlice<ElementType> {
    return array.count <= ceiling ? .array(array) : .slice(array[0..<ceiling])
}

for array in [[1, 2, 3, 4, 5], ["a", "b", "c", "d"]] {
    switch fullOrSlice(array: array, ceiling: 4) {
    case .slice(let slice):
        print("Slice: \(slice)")
    case .array(let array):
        print("Array: \(array)")
    }
}

Slice: [1, 2, 3, 4]
Array: ["a", "b", "c", "d"]

CodePudding user response:

It's possible to do what you're describing, but first, you should see if you can avoid it. The specific example you've given is exactly prefix and should definitely be implemented as:

func fullOrSubarray(array: [Int]) -> ArraySlice<Int> {
    array.prefix(10)
}

The correct thing to return here is an ArraySlice. There is no reason to return something else. Take a look at the implementation of prefix to see how stdlib handles this problem.

That said, in some rare cases it may be useful to do what you're describing. For example, the underlying data may be in a Set or an Array, and you want to avoid copies. In that case, the correct tool is AnySequence or AnyCollection:

func fullOrSubarray(array: [Int]) -> AnySequence<Int> {
    array.count < 10 ? AnySequence(array) : AnySequence(array[0..<10])
}

But this is a very unusual situation, and would never be used for Array and ArraySlice. For that, just use ArraySlice, or convert back to Array. Do not assume that converting from a slice to an Array is expensive. The stdlib is very clever about internal copy-on-write Array buffers and can often avoid making any copies.

Even in cases where the data might currently be a Set, unless it is quite large or accessed quite often, it is generally better to just convert it to an Array before returning it. If it's accessed as an Array quite often, I'd lean towards storing it as an Array rather than a Set in the first place.

But if all those fail you, and you need to return an unknown sequence type, that's what AnySequence (or AnyCollection) is for.

  • Related