Jordan H
Jordan H

Reputation: 55705

Store first few elements in array in another array if they exist

Given an array that contains any number of objects, how could you cleanly and safely get the first 3 elements out of it to store in a new array? If the array does not contain at least 3 elements, it should not trigger a runtime exception, instead it should only add the number of elements in the array to the new array.

I thought this might work, but it won't compile in Xcode 7, and if it did I don't imagine it would behave safely as I desire:

let arr1 = [1, 2, 3, 4, 5]
let arr2 = arr1[0..<3]
//Expected: arr == [1, 2, 3]

let arr1 = [1, 2]
let arr2 = arr1[0..<3]
//Expected: arr2 == [1, 2]

let arr1 = [Int]()
let arr2 = arr1[0..<3]
//Expected: arr2 == []

Of course one could always do something like this, or you could use a for loop, but neither is clean and concise. I want a to find a swiftier way.

let arr1 = [1, 2]
var arr2 = [Int]()
if photos.count > 0 {
    arr2.append(arr1[0])
}
if photos.count > 1 {
    arr2.append(arr1[1])
}
if photos.count > 2 {
    arr2.append(arr1[2])
}

Upvotes: 0

Views: 221

Answers (3)

Aaron Brager
Aaron Brager

Reputation: 66242

You can implement what you want with an extension to Array:

extension Array {
    func safeRange(range : Range<Int>) -> ArraySlice<Element> {
        guard range.startIndex >= 0 && range.endIndex >= 0 else {
            fatalError("Ranges with negative numbers aren't supported")
        }

        var shrinkingRange = range

        while shrinkingRange.endIndex > shrinkingRange.startIndex + 1 {
            if shrinkingRange.endIndex <= self.count {
                return self[shrinkingRange]
            }

            shrinkingRange.endIndex = shrinkingRange.endIndex - 1
        }

        return []
    }
}

The examples you gave behave as expected:

let arr1 = [1, 2, 3, 4, 5]
let arr2 = arr1.safeRange(0..<3)
//Expected: arr == [1, 2, 3]

let arr3 = [1, 2]
let arr4 = arr3.safeRange(0..<3)
//Expected: arr2 == [1, 2]

let arr5 = [Int]()
let arr6 = arr5.safeRange(0..<3)
//Expected: arr2 == []

You could also use filter:

extension CollectionType where Generator.Element : Equatable, Index == Int {
    func safeRange(range : Range<Int>) -> [Self.Generator.Element] {
        return self.filter {
            let index = self.indexOf($0)!
            return index >= range.startIndex && index < range.endIndex
        }
    }
}

(This may fail if your array contains duplicate elements, since indexOf returns the index of the first instance.)

Upvotes: 0

Martin Marconcini
Martin Marconcini

Reputation: 27236

Another approach is to use a function…

import Swift

let arr1 = [1, 2, 3, 4, 5]
let arr2 = [1, 2]
let arr3 = [Int]()

func firstThree(data: [Int]) -> [Int] {
    var results = [Int]()
    for (index, number) in data.enumerate() {
        if (index < 3) {
            results.append(number)
        }
    }
   return results
}

print(firstThree(arr1))
print(firstThree(arr2))
print(firstThree(arr3))

This prints:

[1, 2, 3]
[1, 2]
[]

A slightly better approach would be to use generics and get an N number of items:

func genericFirstItems<T>(array: [T], numberOfItems: Int) -> [T] {
    var results = [T]()

    for (index, item) in array.enumerate() {
        if index < numberOfItems {
            results.append(item)
        }
    }
    return results
}

print(genericFirstItems(arr1, numberOfItems: 3))
print(genericFirstItems(arr2, numberOfItems: 3))
print(genericFirstItems(arr3, numberOfItems: 3))

This has the same output.

[1, 2, 3]
[1, 2]
[]

Upvotes: 0

MirekE
MirekE

Reputation: 11555

I think the simplest way would be

let arr2 = arr1.prefix(3)

Upvotes: 3

Related Questions