Drills

Drills — write slice functions

Slices have to be written, not just read. Five drills on []int, easy to harder. Write each function, run it on the examples, compare with the solution. (We have slices, range, append, len and if — no maps yet, and none of these need one.) Drill 4 has a twist worth your full attention.

1 (easy) — sum. Add every number in the slice (range + an accumulator).

sum([]int{1, 2, 3, 4}) → 10
func sum(nums []int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

2 (easy) — countAbove. How many numbers are strictly greater than min?

countAbove([]int{5, 1, 9, 3}, 4) → 2
func countAbove(nums []int, min int) int {
    count := 0
    for _, n := range nums {
        if n > min {
            count++
        }
    }
    return count
}

3 (medium) — evens. Return a new slice with only the even numbers. This is the append-to-build pattern.

evens([]int{1, 2, 3, 4, 5, 6}) → [2 4 6]
func evens(nums []int) []int {
    out := []int{}
    for _, n := range nums {
        if n%2 == 0 {
            out = append(out, n)
        }
    }
    return out
}

4 (medium) — doubleAll, and the twist. Double every number in place — no return value. Then check the caller's slice:

func doubleAll(nums []int) {
    for i := range nums {
        nums[i] *= 2
    }
}
xs := []int{1, 2, 3}
doubleAll(xs)
fmt.Println(xs) // [2 4 6] — the caller's slice DID change!

Stop and compare this with lesson 4. A struct passed to a function is copied, so changes are lost unless you return them. A slice is different: it's a small header pointing at a shared backing array, so writing nums[i] reaches straight through to the caller's data. Same for i := range + index rule as the menu loop — but now it's crossing a function boundary, and that surprises people. (One caveat: this holds for changing existing elements. append may move the data to a new array, so an append inside the function would not show up outside — that's why we always write xs = append(xs, ...).)

5 (harder) — maxOf. Return the largest number and an ok flag — because an empty slice has no maximum to return.

maxOf([]int{3, 7, 2}) → 7, true      maxOf([]int{}) → 0, false
func maxOf(nums []int) (int, bool) {
    if len(nums) == 0 {
        return 0, false
    }
    best := nums[0]
    for _, n := range nums {
        if n > best {
            best = n
        }
    }
    return best, true
}

The len(nums) == 0 guard isn't optional politeness — nums[0] on an empty slice is a panic. The (value, ok) pair lets the caller tell "the max is 0" from "there was nothing".

Tip. Run each against the examples, and pay off drill 4 by printing xs before and after. These pure functions are, again, exactly what go test locks down in lesson 13.