golangdata structures

Arrays & Slices

Ever wondered how Go wrangles collections of data? Meet arrays and slices! They're like the dynamic duo of data structures, helping you organize your bits and bytes. One's a bit rigid, the other's more flexible, but both are essential tools in your Go programming arsenal.

Arrays: The Fixed-Size Friend

Imagine you're building a bookshelf. An array is like a bookshelf with a fixed number of shelves. Once you decide it has, say, three shelves, you can't magically add a fourth later. Each shelf holds a specific type of book (all novels, all cookbooks, etc.), and you access them by their position (shelf 0, shelf 1, etc.). Simple, right?

Array Declaration: Naming Your Bookshelves

In Go, setting up an array is straightforward. You tell it how many shelves (n) and what type of books (type) it will hold:

arr := [n]type{}

Want to stock it immediately? No problem!

arr := [3]int{1, 2, 3}

To grab a book, you just point to its shelf number. Remember, we start counting from zero (because computers are weird like that):

arr[0]
arr[len(arr)-1]

Multi-Dimensional Arrays: Bookshelves of Bookshelves!

Feeling ambitious? You can even have multi-dimensional arrays, which are like bookshelves where each "book" is actually another bookshelf. Mind-blowing, right?

arr := [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

Think of this as a 3x3 grid of books. If you prefer, imagine rows and columns, like a spreadsheet for your books:

[[1 2 3]
 [4 5 6]
 [7 8 9]]

To snag a specific book, you'll need two shelf numbers: one for the row, and one for the column.

arr[1][2] // This is the 6, row 1 (second row), column 2 (third column)

Iteration: Checking Every Shelf

When you need to look at every book on your shelves, you'll iterate. Go's for...range loop is your best friend here:

for i := range arr {
    // ...
}

Or, if you're lazy and want a copy of the book and its shelf number, Go's got you covered:

for i, v := range arr {
    // 'i' is the shelf number, 'v' is a copy of the book.
    // WARNING: Don't try to rewrite the book 'v' directly; it's just a copy!
    // To change the actual book on the shelf, you need to use 'arr[i]'.
}

Speaking of copies, here's a little gotcha to remember:

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    points := []Point{
        {1, 1},
        {2, 2},
    }

    fmt.Println(points) // [{1 1} {2 2}]

    for _, p := range points {
        p.X = 10 // Updating 'X' for the copy(!)
    }

    fmt.Println(points) // Still looks the same! [{1 1} {2 2}]
}

See? If you want to modify the original item in the array or slice, you must use the index. It's like trying to rewrite a book with a magic pen that only works on photocopies!

For our multi-level bookshelves, you'll need two nested for loops, one for each dimension:

for r := range arr {
    for c := range arr[r] {
        fmt.Println(arr[r][c])
    }
}

Slices: The Super-Flexible Shelving System

While arrays are great for fixed-size collections, real life is rarely so predictable. That's where slices come in! Think of a slice as a super-flexible, expandable shelving system. You don't have to declare its exact size upfront; it can grow and shrink as needed. In Go, you'll find yourself using slices far more often than arrays.

Slice Declaration: Shelves on Demand

Creating a slice looks almost identical to an array, but with one crucial difference: you omit the size!

s := []int{1, 2, 3}

You can also use the make function to create a slice, optionally giving it a hint about its initial capacity (how many elements it can hold before needing to reallocate memory):

s := make([]int, 3)

Slice Size Manipulation: More Books! Less Books!

This is where slices truly shine. Need to add more books? Just append them!

s := []int{1, 2, 3}
s = append(s, 4)

Notice we reassign s with the result of append. This is because append might create a new underlying array if the current one is full.

Want to merge two bookshelves? The ... (spread) operator is your friend:

s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
s1 = append(s1, s2...)

Sorting & Searching: Organizing Your Literary Empire

Now that you've got your data in arrays or slices, what if you want to find something specific or put them in order? You could, of course, roll up your sleeves and write your own sorting algorithms (like the ever-so-inefficient but charming bubble sort or the slightly better insertion sort). Or, if you're feeling particularly ambitious, the faster quicksort or merge sort. For searching, you might try a linear search (just checking every item) or, for sorted data, the super-speedy binary search.

But hey, why reinvent the wheel when Go provides a perfectly good, optimized wheel? The sort package is your go-to for making sense of your data.

package main

import (
    "fmt"
    "sort"
)

func main() {
    floats := []float64{1.0, -5.0, 7.0, 3.0, -2.0, 1.0, 5.0, -6.0, 8.0, -9.0, 9.0, 4.0, 3.0}
    sort.Float64s(floats)
    fmt.Println(floats)
}

Output:

[-9 -6 -5 -2 1 1 3 3 4 5 7 8 9]

Nice and tidy!

But what if you're a rebel and want to sort by something weird, like the absolute value? sort.Slice() is your custom sorting superhero:

package main

import (
    "fmt"
    "math"
    "sort"
)

func main() {
    floats := []float64{1.0, -5.0, 7.0, 3.0, -2.0, 1.0, 5.0, -6.0, 8.0, -9.0, 9.0, 4.0, 3.0}
    sort.Slice(floats, func(i, j int) bool {
        return math.Abs(floats[i]) < math.Abs(floats[j])
    })
    fmt.Println(floats)
}

Output:

[1 1 -2 3 3 4 5 -5 -6 7 8 -9 9]

Magical! The less function takes two indices (i and j) and asks: "Is the element at i 'less' than the element at j?" If you want ascending order, return true if i is indeed "less." For descending, just flip the comparison!

Linear Search: The Simple Scavenger Hunt

When you need to find a needle in a haystack (or a specific value in a slice), and your slice isn't sorted, linear search is your basic strategy: check every single item until you find it (or don't).

func LinearSearch(arr []int, v int) (int, error) {
    for i := range arr {
        if arr[i] == v {
            return i, nil // return index and nil-error
        }
    }
    return 0, errors.New("not found") // return 0 and an error
}

Conclusion: Go Forth and Slice!

And there you have it! Arrays and slices are fundamental building blocks in Go. While arrays offer fixed-size, contiguous storage, slices are your dynamic, flexible friends, capable of growing and shrinking as your data needs evolve. From basic declarations to looping through elements and even performing custom sorts, understanding these concepts is key to writing efficient and idiomatic Go code. So go on, conquer your data, and happy slicing!