# Assign elements at index, index+1, index+2 into array

Hi,

Note: This is not school homework, I have made an attempt, but wasn't sure if that was a reasonable approach, or if there is a better way to do it.

# Overview

I have an array of numbers
I would like to group them into an array of arrays in the following way:

``````[[numbers[0], numbers[1], numbers[2],
[numbers[1], numbers[2], numbers[3],
...
...
[numbers[x], numbers[x+1], numbers[x+2],
[numbers[x+1], [numbers[x+2],
[numbers[x+2]]
``````

## Example:

If `numbers = [23, 42, 19, 7, 150, 8, 20, 41]`
Output would be as follows:

``````[[23, 42, 19], [42, 19, 7], [19, 7, 150], [7, 150, 8], [150, 8, 20], [8, 20, 41], [20, 41], [41]]
``````

# My implementation

``````let numbers = [23, 42, 19, 7, 150, 8, 20, 41]

let count = numbers.count
var groups = [[Int]]()
for index in 0 ..< count {

var group = [Int]()
group.append(numbers[index])
if index + 1 < count {
group.append(numbers[index + 1])

if index + 2 < count {
group.append(numbers[index + 2])
}
}

groups.append(group)
}

print(groups)
``````

# Questions:

• Is there a better way to achieve this?
• I thought my implementation was verbose and was hoping that there was a better way or is my approach reasonable?

This is not school homework,

It's nice you include some code.

First thing that comes to mind: is there a way to avoid those if statements. They run on each loop.
If we go beyond the last element we get an error. So stop sooner.

``````let count = numbers.count
var groups = [[Int]]()
for index in 0 ..< count -2
{
var group = [Int]()
group.append(numbers[index])
group.append(numbers[index + 1])
group.append(numbers[index + 2])
groups.append(group)
}
``````

We can now drop the count variable and will account for arrays smaller than 2.

``````var groups = [[Int]]()
for index in 0 ..< max(0, numbers.count -2)
{
var group = [Int]()
group.append(numbers[index])
group.append(numbers[index + 1])
group.append(numbers[index + 2])
groups.append(group)
}
``````

So we are actually just extracting three values from an array.

``````var groups = [[Int]]()
for index in 0 ..< max(0, numbers.count -2)
{
var group = numbers[index..<index+2]
groups.append(group)
}
``````

Might as well make it a one liner.

``````var groups = [[Int]]()
for index in 0 ..< max(0, numbers.count -2)
{
groups.append(numbers[index..<index+2])
}
``````

Oh, that doesn't compile because it's a slice. So:

``````    groups.append(Array(numbers[index..<index+2]))
``````

Unfortunately we don't have the last two groups in your output:

``````groups.append(numbers.suffix(2))
groups.append(numbers.suffix(1))
``````

Which all together gives:

``````	var groups = [[Int]]()
for index in 0 ..< max(0, numbers.count - 2)
{
groups.append(Array(numbers[index..<index+2]))
}

groups.append(numbers.suffix(2))
groups.append(numbers.suffix(1))
``````

Though this will add empty arrays if the number of elements is not a multiple of 3. An if statement can be added on these two lines. At least it's not checking the index on each loop anymore.

There might be shorter/better ways to do this. Maybe somebody else will chime in.

3 Likes

Your code is nice and simple, which is good. But some things could be improved a bit.

I would also recommend using slices, and to avoid addition for indexing operations - `Collection` already has all of the indexing operations that we need. It's also good to reserve the array capacity when we know it.

``````let numbers = [23, 42, 19, 7, 150, 8, 20, 41]

var groups = [[Int]]()
groups.reserveCapacity(numbers.count)
for index in numbers.startIndex ..< numbers.endIndex {
let groupEnd = numbers.index(index, offsetBy: 3, limitedBy: numbers.endIndex) ?? numbers.endIndex
groups.append(Array(numbers[index..<groupEnd]))
}

print(groups)
// [[23, 42, 19], [42, 19, 7], [19, 7, 150], [7, 150, 8], [150, 8, 20], [8, 20, 41], [20, 41], [41]]
``````

Note that it becomes much easier to make adjustments, such as changing the length of the groups.

However, we're also allocating an array for each group, and there will be a lot of groups - if `numbers` has n values, you will have n groups, meaning n Array allocations. If the rest of your application allows it, I would also recommend changing `groups` to be an array of slices, in other words:

``````var groups = [ArraySlice<Int>]()
...
groups.append(numbers[index..<rowEnd])
``````

This means all of the groups will share the same underlying array storage.

5 Likes

May be harder to follow, but you could do this by iterating over successive triplets with a nested `zip`.

``````let numbers = [23, 42, 19, 7, 150, 8, 20, 41]
let paddedNumbers = (numbers + [nil, nil])
.map { [\$0.0.0, \$0.0.1, \$0.1].compactMap { \$0 } }
print(groups)
``````

Padding `numbers` with two `nil`s is required to get the 2-element and single-element triplets at the end of the iteration, with the `nil`s filtered out using a `compactMap`.

Using the index type as Karl suggests is way better. Especially if you want to turn this into a generic function.

``````func group<C>(collection: C, by length: Int)
where C: Collection, C.Element == Int, C.Index == Int
{
...
}
``````

The collection might not start at 0 so `startIndex` is required.

I should have done code completion on the index type for inspiration.

If you're fine with the return type of `[ArraySlice<Int>]` instead of `[[Int]]`, then this one-liner should also do what you need:

``````numbers.indices.map { numbers[\$0...].prefix(3) }
``````

otherwise, wrap that closure output in `Array.init` like so:

``````numbers.indices.map { Array(numbers[\$0...].prefix(3)) }
``````
6 Likes

Since itâ€™s not homework, you donâ€™t need to roll your own:

``````import Algorithms

let numbers = â€¦
let groups = numbers.windows(ofCount: 3)
``````
8 Likes

to avoid any confusion: the `Collection.windows(ofCount:)` method is from the `swift-algorithms` package. it does not come with the toolchain.

it is also available on `Array`, though there is no way of knowing this via the docs, because `Collection.windows(ofCount:)` is dark API.

3 Likes

`windows(ofCount:)` is very useful but fails to handle the tail in the way the OP specified it to be.

1 Like

While we are bikeshedding, Iâ€™ll make my usual suggestion to never use indexes when counts are more appropriate:

``````for i in numbers.indices {
groups.append(Array(numbers[i...].prefix(3)))
}
``````
7 Likes

Ah yes, failed to note the tail behavior; if thatâ€™s essential then some extra finagling will be required.

Thank you so much guys!!!!

Amazing to see the different approaches to the same problem.

Been a great learning for me, thank you so much!!

4 Likes