Lesson 12 of 25 · Data and types

Arrays and slices

An array holds a fixed number of values of the same type one after another, and you reach each of them by its position. Arrays are the simplest and fastest way to keep a list whose length you know in advance.

Building and reading an array

Build an array with square brackets. Positions start at zero, and len() gives the number of elements. To change elements, declare the array mut. println prints a whole array at once, and an array may itself hold arrays, as table does here.

arr-basics.salam
func main:
    mut numbers := [5, 3, 8, 1]
    println numbers[0], numbers[3], len(numbers)
    numbers[1] = 90
    println numbers
    table := [[1, 2, 3], [4, 5, 6]]
    println table[1][2]
end
Output5 1 4 [5, 90, 8, 1] 6

The type of numbers is int[4]: the length is part of the type and never changes. When you need a list that grows, use a Vector from the collections lesson.

An array of a given size

When you know the length but not yet the values, declare the array with its type: mut votes: int[4]. Numbers start at zero and bool elements at false. Counting things by number is the classic use:

arr-sized.salam
func main:
    mut votes: int[4]
    ballots := [2, 0, 2, 3, 2, 0]
    each b in ballots:
        votes[b] += 1
    end
    println votes
    each option, count in votes:
        println "option", option, "got", count
    end
end
Output[2, 0, 3, 1] option 0 got 2 option 1 got 0 option 2 got 3 option 3 got 1

Do not read the elements of a str array declared this way before you have set them: they hold no string yet, and reading one crashes the program. Build a str array from a literal instead, such as ["", "", ""].

Walking an array

each item in array visits the elements in order. With two names, each i, item in array, you also get the position.

arrays.salam
func main:
    fruits := ["apple", "pomegranate", "grape"]
    each fruit in fruits:
        println "I like", fruit
    end
    println "count:", fruits.len()
end
OutputI like apple I like pomegranate I like grape count: 3

Most work on an array is one loop that carries a result along: a running total, the position of the largest value so far, or the first position that matches. Keep the position rather than the value when you will need both, as with wettest below. A search function returns -1 when nothing matches, a value that cannot be a real position.

arr-stats.salam
func index of(items: str[], wanted: str): int:
    each i, item in items:
        if item == wanted:
            ret i
        end
    end
    ret -1
end

func main:
    months := ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
    rain := [78, 64, 51, 33, 12, 4]
    mut total := 0
    mut wettest := 0
    each i, mm in rain:
        total += mm
        if mm > rain[wettest]:
            wettest = i
        end
    end
    println "total:", total, "average:", total / len(rain)
    println "wettest:", months[wettest], rain[wettest]
    println "Apr is month", index of(months, "Apr") + 1
    println "Dec found at", index of(months, "Dec")
end
Outputtotal: 242 average: 40 wettest: Jan 78 Apr is month 4 Dec found at -1

Two arrays of the same element type and length compare with == and !=: they are equal when every element is equal, so [1, 2, 3] == [1, 2, 3] is true. Arrays of different lengths cannot be compared, and neither can arrays with < or >; both are error E021.

Two dimensional arrays

An array of arrays is a table: sales[r][c] is row r, column c. len(sales) is the number of rows and len(sales[0]) the number of columns. An array literal may span several lines, which keeps a table readable. each does not walk a two dimensional array (E065), so use repeat with indexes. A single row, sales[r], is an ordinary array and can be handed to a function.

arr-grid.salam
func total(values: int[]): int:
    mut sum := 0
    each n in values:
        sum += n
    end
    ret sum
end

func main:
    sales := [
        [12, 7, 3, 9],
        [5, 11, 8, 6],
        [9, 4, 10, 2]
    ]
    shops := ["north", "south", "east"]
    repeat len(sales) in r:
        println shops[r], total(sales[r])
    end
    mut days := ""
    repeat len(sales[0]) in c:
        mut sum := 0
        repeat len(sales) in r:
            sum += sales[r][c]
        end
        days += " " + sum
    end
    println "per day:" + days
end
Outputnorth 31 south 30 east 25 per day: 26 22 21 17

To declare an empty table, give both sizes: mut grid: int[3][4].

Arrays are values

Assigning an array to another variable copies all of it. So does passing it to a parameter with a fixed size such as int[3], and so does returning one. Changing the copy never touches the original.

arr-copy.salam
func with bonus(scores: int[3]): int[3]:
    mut out := scores
    repeat len(out) in i:
        out[i] += 5
    end
    ret out
end

func main:
    original := [70, 85, 90]
    mut draft := original
    draft[0] = 0
    println original, draft
    curved := with bonus(original)
    println original, curved
end
Output[70, 85, 90] [0, 85, 90] [70, 85, 90] [75, 90, 95]

That makes arrays safe to pass around, but copying a large array on every call costs time. When a function should only read an array, or should change the caller's array, give it a slice instead.

Slices

array[start: end] makes a slice over the elements from start up to but not including end. It is not a copy: it points at the same data. [: 2] takes from the beginning and [4:] runs to the end. A parameter of type int[] accepts any array or slice of ints, whatever its length, and an array passes to it directly, with no copy.

arr-slices.salam
func sum(view: int[]): int:
    mut total := 0
    each n in view:
        total += n
    end
    ret total
end

func main:
    data := [1, 2, 3, 4, 5, 6]
    middle := data[1: 4]
    println len(middle), sum(middle)
    println sum(data), sum(data[: 2]), sum(data[4:])
end
Output3 9 21 3 11

Slices write through

Because a slice points at the caller's data, a function that changes elements of an int[] parameter changes the caller's array. Pass a slice of part of the array and only that part changes.

arr-slice-write.salam
func discount(prices: int[], percent: int):
    repeat len(prices) in i:
        prices[i] -= prices[i] * percent / 100
    end
end

func main:
    mut prices := [200, 150, 80, 60, 40]
    discount(prices[3:], 50)
    println prices
    discount(prices, 10)
    println prices
end
Output[200, 150, 80, 30, 20] [180, 135, 72, 27, 18]

A function that returns a slice must not return one that points into its own local array, since that array is gone once the function returns; the compiler rejects it (E012).

Index errors

Reading or writing past the end of an array is always a mistake, and Salam catches it. When the index is a constant, the build stops: a[3] on a three element array gives "index 3 is out of bounds for an array of length 3" (E021). When the index is only known at run time, the program stops with "array index 5 out of bounds [0,3)" instead of reading memory that is not part of the array.

A build with --release leaves out these run time checks to save time, so an index error there is no longer caught. Test without --release first.

Rules of thumb

  • Use an array when the length is known and fixed, and a Vector when it is not.
  • Take an array parameter as int[] (a slice) unless the function needs its own copy.
  • Remember that a slice writes through to the array it came from.
  • Walk a two dimensional array with repeat and two indexes.
  • Return -1 from a search that finds nothing, and check for it.