Better way to handle grabbing n elements from an array while respecting the array boundaries

I have an array of things and I'd like to say, get the item in the array I'm selecting as well as the next two. So three in total. However, if the item I'm select is the last thing, I want to say get the two preceding. And finally if it's the second to last thing, I want to say get the one before and the one after.

Right now I have code like so:

private func handleWhichSongsToPlay(_ track: Track, loadedTracks: MusicItemCollection<Track>) -> [Track] {
    // loadedTracks is where track comes from so it has to exist
    let trackIndex = loadedTracks.firstIndex(where: { $0.id == track.id })!
    var rangeOfTracks: [Track] = []
    if trackIndex == loadedTracks.endIndex - 1 {
        // two songs before
        rangeOfTracks.append(contentsOf: loadedTracks[(trackIndex - 2)...trackIndex])
    } else if trackIndex == loadedTracks.endIndex - 2 {
        // song before and after
        rangeOfTracks.append(contentsOf: loadedTracks[(trackIndex - 1)...(trackIndex + 1)])
    } else {
        // two songs after
        rangeOfTracks.append(contentsOf: loadedTracks[trackIndex...(trackIndex + 2)])
    }

    return rangeOfTracks
}

But I feel like there must be a better way.

You should be able to do something like this:

let upper = min(trackIndex + 3, loadedTracks.count)
let lower = max(upper - 3, 0)
return Array(loadedTracks[lower ..< upper])

The max call deals with the situation where there are less than 3 elements in your array. The return expression converts the Array slice to a separate Array.

4 Likes

Thanks @QuinceyMorris. I'll be sure to give this a shot tonight. This seems a lot simpler than what I've produced. :pray:t4:

@QuinceyMorris Thank you so much! This works like a charm and the code is so much shorter. Now I just need to internalize that this is an option. :rofl: