Skip to main content

Lists, maps and arrays

JARU has three ways of keeping many values in a single variable: lists, maps and arrays. All three take values of any type, mixed. They don't need use: their methods are always there. What applies to all data types is in Data types.

ListMapArray
Written as[10, 20, 30]{"lives": 3, "level": 1}{10, 20, 30} or var a[10]
Read withthe position: l[0]the key: m["lives"]one position per dimension: t[1][2]
Sizegrows and shrinksgrows and shrinksfixed
len()yesyesno: size()
println showsthe contentsthe contentsonly the dimensions

Assigning a list, a map or an array to another variable, or passing it to a function, doesn't copy it: both variables see the same one (see Copy and clone).

Lists, maps and arrays work the same on Windows and on every board.

Lists​

A list keeps values in order. You write it between square brackets, and it can grow and shrink:

var empty = []
var scores = [10, 20, 30]
var mixed = ["Ann", 25, true, nil, [1, 2]]
println(scores) // [10, 20, 30]
println(mixed) // [Ann, 25, true, nil, [1, 2]]

list[i] reads the item at position i, and list[i] = value changes it. The first position is 0, and the last one, list.size() - 1:

var scores = [10, 20, 30]
println(scores[0]) // 10
scores[2] = 35
println(scores) // [10, 20, 35]
  • A negative position, or one that doesn't exist, throws ERR_INDEX_OUT_OF_RANGE, also when writing. To add items, use append() or insert().
  • The position has to be an integer. With a decimal it throws ERR_TYPE_MISMATCH.
  • A list inside another one is read with two pairs of brackets: table[1][0].
  • == checks whether both are the same list, not whether they hold the same: two different lists with the same items give false.
  • + doesn't join lists: it throws ERR_TYPE_MISMATCH. To join two, walk through the second one with foreach and add its items to the first one with append().
  • println shows the strings in a list without quotes.

List methods​

MethodWhat it doesReturns
append(value)Adds an item at the end.nil
insert(position, value)Adds an item at a position.nil
pop(position)Removes the item at that position, or the last one.The removed item
remove(value)Removes the first item equal to value.true if it removed it
contains(value)Checks whether there is an item equal to value.true or false
size()Counts the items.Integer
reverse()Turns the list back to front.true
sort()Sorts the list from lowest to highest.true
clear()Empties the list.nil
copy()Makes a shallow copy: see Copy and clone.New list
clone()Makes a deep copy: see Copy and clone.New list

The methods that change the list change the list itself: they don't return a new one. With more or fewer arguments than their own they throw ERR_INVALID_ARG_NUMBER.

list.append(value)​

Adds value at the end of the list.

var names = ["Ann"]
names.append("Luis")
println(names) // [Ann, Luis]

list.insert(position, value)​

Adds value at position and moves the items from there on one place along. position goes from 0 to size(): with size(), it adds it at the end.

var letters = ["a", "c"]
letters.insert(1, "b")
letters.insert(3, "d")
println(letters) // [a, b, c, d]

With a negative position, or one over size(), it throws ERR_INDEX_OUT_OF_RANGE, and if it isn't an integer, ERR_TYPE_MISMATCH.

list.pop(position)​

Removes the item at position and returns it. Without an argument, it removes the last one.

var queue = [10, 20, 30]
var last = queue.pop()
var first = queue.pop(0)
println(last, " ", first, " ", queue) // 30 10 [20]
  • With an empty list it throws ERR_EMPTY_LIST.
  • With a negative position, or one that doesn't exist, it throws ERR_INDEX_OUT_OF_RANGE, and if it isn't an integer, ERR_TYPE_MISMATCH.

list.remove(value)​

Removes the first item equal to value and returns true. If it doesn't find it, it changes nothing and returns false, without throwing any exception.

var numbers = [5, 1, 7, 1]
println(numbers.remove(1)) // true
println(numbers) // [5, 7, 1]
println(numbers.remove(9)) // false

Two values are equal as in contains.

remove removes by value, not by position

list.remove(2) doesn't remove the item at position 2: it removes the first item worth 2, if there is one. To remove by position, use pop(position). A loop that waits for the list to shrink by calling remove(i) hangs if no item is worth i.

list.contains(value)​

Returns true if any item is equal to value.

var inventory = ["sword", "key"]
println(inventory.contains("key")) // true
println(inventory.contains("map")) // false
  • Numbers are equal if they are worth the same: in [1, 2], contains(1.0) gives true.
  • Strings are equal if they have the same text.
  • A list, a map or an object is only equal to itself: another list with the same items doesn't count.

list.size()​

Returns the number of items. It is the same as len(list).

var scores = [10, 20, 30]
var empty = []
println(scores.size(), " ", empty.size()) // 3 0

list.reverse()​

Turns the list back to front.

var countdown = [1, 2, 3]
countdown.reverse()
println(countdown) // [3, 2, 1]

list.sort()​

Sorts the list from lowest to highest. The list has to be all numbers (integers and decimals mixed are fine) or all strings. With anything else, a nil included, it throws ERR_TYPE_MISMATCH.

var scores = [30, 5.5, -2, 10]
scores.sort()
println(scores) // [-2, 5.5, 10, 30]
var names = ["pear", "Apple", "zebra", "éclair"]
names.sort()
println(names) // [Apple, pear, zebra, éclair]
  • Strings are sorted by their bytes: upper case goes before lower case, and accented letters and the ñ go after z. To sort without minding case, turn the strings into lower case first with lower().
  • To sort from highest to lowest, call sort() and then reverse().

list.clear()​

Removes all the items.

var queue = [1, 2, 3]
queue.clear()
println(queue, " ", queue.size()) // [] 0

Maps​

A map keeps pairs of key and value: you look a value up by its key. You write it between curly braces, with a colon between the key and the value. map[key] reads a value, and map[key] = value changes it or, if the key isn't there, adds it:

var player = {"name": "Ann", "lives": 3}
println(player["name"]) // Ann
player["lives"] = 2
player["level"] = 1
println(player.size()) // 3
  • map[key] with a key that isn't there throws ERR_KEY_NOT_FOUND. To read without an exception, use get; to find out whether it's there, contains.
  • {} is an empty map, not an array.
  • There is no map.key: player.lives throws ERR_ATTRIBUTE_NOT_FOUND. If you want to write it that way, use a struct or a class.
  • As with lists, == checks whether it's the same map, not whether it holds the same.

The keys​

  • A key can be any value except a list or a map: a string, a number, true, false, nil or an object. A list or a map as a key throws ERR_TYPE_MISMATCH.
  • An integer and a float with the same value are the same key: map.get(2.0) finds what you stored in map[2].
  • With an object as a key, what counts is the object, not what it holds: another object of the same class is another key.
  • Each key is there only once: writing to a key that is already there changes its value.
var names = {1: "one", 2: "two"}
names[2] = "TWO"
println(names[2], " ", names.size()) // TWO 2

With JSON.stringify, integer keys are written as text: {1: "one"} comes out {"1": "one"}, and when you read it with JSON.parse the key is the string "1". See JSON.

The order​

A map doesn't keep the order in which you put the keys in. keys(), values(), items(), foreach and println go through it in the order of its inner table, which can change when you add keys:

var data = {"bread": 1, "milk": 2}
println(data) // {milk: 2, bread: 1}

If you need an order, sort the keys: var keys = map.keys() and then keys.sort().

Map methods​

MethodWhat it doesReturns
insert(key, value)Adds a key, or changes its value.nil
get(key, default)Reads a value without throwing exceptions.The value, or default
contains(key)Checks whether the key is there.true or false
remove(key)Removes a key.true if it was there
pop(key, default)Removes a key and returns its value.The value, or default
size()Counts the keys.Integer
keys()Gives the keys.List
values()Gives the values.List
items()Gives the pairs.List of [key, value] lists
clear()Empties the map.nil
copy()Makes a shallow copy: see Copy and clone.New map
clone()Makes a deep copy: see Copy and clone.New map

With more or fewer arguments than their own they throw ERR_INVALID_ARG_NUMBER, and with a key that can't be one (a list, a map), ERR_TYPE_MISMATCH.

map.insert(key, value)​

Adds key with its value or, if it was already there, changes its value. It is the same as map[key] = value.

var settings = {}
settings.insert("volume", 8)
settings["brightness"] = 5
println(settings.size()) // 2

map.get(key, default)​

Returns the value of key. If the key isn't there, it doesn't throw any exception: it returns default, or nil if you leave it out.

var settings = {"volume": 8}
println(settings.get("volume")) // 8
println(settings.get("brightness")) // nil
println(settings.get("brightness", 5)) // 5

map.contains(key)​

Returns true if the key is in the map. It looks at the keys, not at the values.

var settings = {"volume": 8}
println(settings.contains("volume")) // true
println(settings.contains(8)) // false

map.remove(key)​

Removes the key and its value, and returns true. If the key wasn't there, it returns false, without throwing any exception.

var settings = {"volume": 8, "brightness": 5}
println(settings.remove("brightness")) // true
println(settings.remove("brightness")) // false
println(settings) // {volume: 8}

map.pop(key, default)​

Removes the key and returns its value. If the key isn't there, it returns default; without default, it throws ERR_KEY_NOT_FOUND.

var settings = {"volume": 8}
var v = settings.pop("volume")
println(v, " ", settings) // 8 {}
println(settings.pop("brightness", 0)) // 0

map.size()​

Returns the number of keys. It is the same as len(map).

var settings = {"volume": 8, "brightness": 5}
println(settings.size()) // 2

map.keys()​

Returns a new list with the keys, in the order of the map (see The order).

var prices = {"bread": 1, "milk": 2}
println(prices.keys()) // [milk, bread]

map.values()​

Returns a new list with the values, in the same order as keys().

var prices = {"bread": 1, "milk": 2}
println(prices.values()) // [2, 1]

map.items()​

Returns a new list with one item per key: a list of two, [key, value], in the same order as keys().

var prices = {"bread": 1, "milk": 2}
println(prices.items()) // [[milk, 2], [bread, 1]]

map.clear()​

Removes all the keys.

var settings = {"volume": 8}
settings.clear()
println(settings, " ", settings.size()) // {} 0

Arrays​

An array has a fixed size, decided when you create it, and it can have several dimensions. It is what suits a table of known size, such as a board or a grid. You create it in two ways:

  • With its items between curly braces. For several dimensions, braces inside braces.
  • With var name[size], one dimension per pair of brackets. All the items start out as 0, and the size can be a variable.
var primes = {2, 3, 5, 7}
var board = {{1, 2, 3}, {4, 5, 6}}
var grid[20][15]
var n = 8
var row[n]
println(primes[2], " ", board[1][0], " ", grid[19][14], " ", row.size()) // 5 4 0 8
  • You read and write it with one position per dimension, each from 0: board[1][2] = 9. A negative position, or one outside its dimension, throws ERR_INDEX_OUT_OF_RANGE, and one that isn't an integer, ERR_TYPE_MISMATCH.
  • An empty {} is a map: an array written between braces needs at least one item.
  • len() doesn't take arrays (it throws ERR_TYPE_MISMATCH): use size().
  • println of an array doesn't show the items, only its dimensions. To see them, walk through it with foreach.
var board = {{1, 2, 3}, {4, 5, 6}}
println(board) // Array <2*3*>

All the rows of an array written between braces must have the same number of items: otherwise the compiler gives the error “All the rows of an array literal must have the same number of elements.”.

var board = {{1, 2, 3}, {4, 5}}

A single position in an array of several dimensions​

An array of several dimensions has no rows you can take out: it is a table of items one after the other, row by row. With a single position, you read the item with that number in that order. In a 2 x 3 array, board[5] is the last item, and board[1] is the second item of the first row, not the second row:

var board = {{1, 2, 3}, {4, 5, 6}}
println(board[1][2]) // 6
println(board[5]) // 6
println(board[1]) // 2

Lists inside an array​

An array can hold lists, but groups[0][1] doesn't read the second item of the list: the array takes both positions as its own and throws ERR_INDEX_OUT_OF_RANGE. Put the array and its position in brackets, or keep the list in a variable first:

var groups = {["Ann", "Luis"], ["Marta"]}
println((groups[0])[1]) // Luis
var first = groups[0]
println(first[1]) // Luis

An array inside a list or a map is read with the brackets one after the other: levels[1][2].

Array methods​

MethodWhat it doesReturns
size()Counts all the items.Integer
numdim()Counts the dimensions.Integer
dim()Gives the size of each dimension.List of integers
fill(value)Puts the same value in every item.nil
copy()Makes a shallow copy: see Copy and clone.New array
clone()Makes a deep copy: see Copy and clone.New array
array(value)Returns the same array.The same array

array.size()​

Returns the total number of items: in a 3 x 4 array, 12.

array.numdim()​

Returns the number of dimensions: 1 in {1, 2, 3}, 2 in var t[3][4].

array.dim()​

Returns a list with the size of each dimension.

var t[3][4]
println(t.size(), " ", t.numdim(), " ", t.dim()) // 12 2 [3, 4]

array.fill(value)​

Puts value in every item of the array.

var t[2][2]
t.fill(-1)
println(t[0][0], " ", t[1][1]) // -1 -1

array.array(value)​

Returns the same array, without copying it: a.array(0) == a gives true. The argument is required (without it, it throws ERR_INVALID_ARG_NUMBER), but it isn't used. It doesn't create a new array: for that, var a[n], and to copy it, copy() or clone().

Walking through them with foreach​

foreach (var x in collection) walks through a collection and gives you one value in each turn (the syntax is in Control structures). What x holds depends on what you walk through:

You walk throughx holds
A listEach item, in order.
An arrayEach item, row by row: all of them, even with several dimensions.
A mapEach value, in the order of the map.
A stringEach character (see Strings).

With anything else (a number, nil, a struct) it throws ERR_TYPE_MISMATCH.

To walk through the keys of a map, walk through map.keys(), and for both keys and values, map.items():

var prices = {"bread": 1, "milk": 2}
foreach (var p in prices)
print(p, " ")
end
println("")
foreach (var pair in prices.items())
println(pair[0], ": ", pair[1])
end
2 1
milk: 2
bread: 1

Assigning to the loop variable​

Assigning to the loop variable changes the item you are walking through. In a map, it changes the value of the current entry (since 0.9.7).

var prices = {"bread": 1, "milk": 2}
foreach (var p in prices)
p = p * 10
end
println(prices) // {milk: 20, bread: 10}
var list = [1, 2, 3]
foreach (var x in list)
x = x * 2
end
println(list) // [2, 4, 6]

In a string, assigning to the variable changes that character: the variable that holds the string gets a new string, as with text[i] = "x".

Changing the collection while you walk through it​

  • In a list, foreach decides when it starts how many turns it takes. What you add inside the loop isn't walked through. If you remove items, it skips some, and at the end it throws ERR_INDEX_OUT_OF_RANGE.
  • In a map, adding keys inside the loop can make it skip entries or walk through some of the new ones.

To remove or add while you walk through, walk through a copy (list.copy(), map.keys()) and change the original:

var enemies = [3, 0, 5, 0]
foreach (var e in enemies.copy())
if (e == 0) then
enemies.remove(e)
end
end
println(enemies) // [3, 5]

Copy and clone​

Assigning doesn't copy: after b = a, both variables see the same list. The same happens when you pass it to a function (see Functions).

var a = [1, 2]
var b = a
b.append(3)
println(a) // [1, 2, 3]

To have another one, copy it. Lists, maps and arrays have two methods:

  • copy() makes a shallow copy: a new container with the same items. If an item is another list, a map or an array, the copy and the original share that inner one.
  • clone() makes a deep copy: it also copies the lists, maps, arrays, structs and objects inside, at any depth. The rest (bitmaps, sprites, files…) isn't copied: the clone uses the same one.
var original = [1, [2, 3]]
var shallow = original.copy()
var deep = original.clone()
var inner = original[1]
inner.append(4)
original[0] = 100
println(original) // [100, [2, 3, 4]]
println(shallow) // [1, [2, 3, 4]]
println(deep) // [1, [2, 3]]

Structs and objects have copy() and clone() too. Strings don't need copying, because they never change.

clone() with collections that contain themselves

If there is a cycle inside what you clone, like a list that holds itself or two maps that point to each other, clone() never finishes copying: the VM closes without any message, and try doesn't stop it. With cycles, use copy() or copy by hand what you need.

Limits​

Lists and maps written in one go​

A list or a map written between brackets or braces is put together on the VM stack, which has room for 256 values on every platform. In the main program there is room for:

ListMap
In the main program252 items127 pairs
Inside a function without parameters or local variables251 items126 pairs

Each call in progress and each local variable alive at that moment (parameters and the variables of a foreach too) takes one more place, and there is room for fewer: in a function with one parameter and two local variables, 248 items. Each pair of a map takes two places.

If it doesn't fit, the VM closes

With a list or a map written in one go that doesn't fit, the VM closes at once, with no JARU message saying why, on the boards too, and try doesn't catch it. If your list goes over about 200 items, put it together another way.

Ways of putting a big list together:

  • Write it in pieces and join them with append():

    var table = [0, 1, 2, 3]
    var rest = [4, 5, 6, 7]
    foreach (var v in rest)
    table.append(v)
    end
    println(table.size()) // 8
  • If its size isn't going to change, use an array: an array written between braces doesn't have this limit, because its items are stored one by one.

  • Read it from a file with File and convert it with JSON.

Memory​

Apart from that, the size of a list, a map or an array is only limited by memory. Boards have much less than Windows: see Memory and limits.

Complete example​

This program records the moves of a game of noughts and crosses in a 3 x 3 array, counts each player's moves in a map and draws the board with a list per row.

var board[3][3]
board.fill(".")
var moves = [[0, 0, "X"], [1, 1, "O"], [2, 2, "X"], [0, 2, "O"]]
var count = {"X": 0, "O": 0}

foreach (var m in moves)
board[m[0]][m[1]] = m[2]
count[m[2]] = count[m[2]] + 1
end

var space = " "
for (var r = 0; r < 3; r++)
var row = []
for (var c = 0; c < 3; c++)
row.append(board[r][c])
end
println(space.join(row))
end

var players = count.keys()
players.sort()
foreach (var name in players)
println(name, ": ", count[name], " moves")
end
X . O
. O .
. . X
O: 2 moves
X: 2 moves

Pitfalls and known issues​

  • remove(value) removes by value, not by position: to remove a position, pop(position). See remove.
  • Assigning doesn't copy, and neither does passing to a function: see Copy and clone.
  • == doesn't compare the contents of two lists or two maps: it checks whether they are the same one.
  • A map doesn't keep the order in which you put the keys in: see The order.
  • map[key] with a key that isn't there throws an exception: use get or contains. And there is no map.key.
  • {} is a map, not an array.
  • In an array of several dimensions, a single position counts the items one after the other, and a list inside an array is read with brackets: see Arrays.
  • Don't index what a method returns: map.keys()[0] doesn't work, and it can even close the compiler. Keep the result in a variable first: var keys = map.keys() and keys[0]. More in Basic syntax.
  • Changing a collection while you walk through it skips items: see Walking through them with foreach.
  • A list or a map written in one go has a maximum size, and going over it closes the VM: see Limits.

All the known issues of JARU, together, are in Known issues.