Janet 1.5.0-719f7ba Documentation
(Other Versions: 1.5.0 1.4.0 1.3.1)

Data Structures

Once you have a handle on functions and the primitive value types, you may be wondering how to work with collections of things. Janet has a small number of core data structure types that are very versatile. Tables, Structs, Arrays, Tuples, Strings, and Buffers, are the 6 main built in data structure types. These data structures can be arranged in a useful table describing their relationship to each other.

Interface Mutable Immutable
Indexed Array Tuple
Dictionary Table Struct
Bytes Buffer String (Symbol, Keyword)

Indexed types are linear lists of elements than can be accessed in constant time with an integer index. Indexed types are backed by a single chunk of memory for fast access, and are indexed from 0 as in C. Dictionary types associate keys with values. The difference between dictionaries and indexed types is that dictionaries are not limited to integer keys. They are backed by a hashtable and also offer constant time lookup (and insertion for the mutable case). Finally, the 'bytes' abstraction is any type that contains a sequence of bytes. A 'bytes' value or byteseq associates integer keys (the indices) with integer values between 0 and 255 (the byte values). In this way, they behave much like Arrays and Tuples. However, one cannot put non integer values into a byteseq.

The table below summarizes the big-O complexity of various operations and other information on the built-in data structures. All primitive operations on data structures will run in constant time regarding the number of items in the data structure.

Data Structure Access Insert/Append Delete Space Complexity Mutable
Array * O(1) O(1) O(1) O(n) Yes
Tuple O(1) - - O(n) No
Table O(1) O(1) O(1) O(n) Yes
Struct O(1) - - O(n) No
Buffer O(1) O(1) O(1) O(n) Yes
String/Keyword/Symbol - - - O(n) No

*: Append and delete for an array correspond to array/push and array/pop. Removing or inserting elements at random indices will run in O(n) time where n is the number of elements in the array.

(def mytuple (tuple 1 2 3))

(def myarray @(1 2 3))
(def myarray (array 1 2 3))

(def mystruct {
 :key "value"
 :key2 "another"
 1 2
 4 3})

(def another-struct
 (struct :a 1 :b 2))

(def my-table @{
 :a :b
 :c :d
 :A :qwerty})
(def another-table
 (table 1 2 3 4))

(def my-buffer @"thisismutable")
(def my-buffer2 @``This is also mutable``)

To read the values in a data structure, use the get function. The first parameter is the data structure itself, and the second parameter is the key. An optional third paramter can be used to specify a default if the value is not found.

(get @{:a 1} :a) # -> 1
(get {:a 1} :a) # -> 1
(get @[:a :b :c] 2) # -> :c
(get (tuple "a" "b" "c") 1) # -> "b"
(get @"hello, world" 1) # -> 101
(get "hello, world" 0) # -> 104
(get {:a :b} :a) # -> :b
(get {:a :b} :c :d) # -> :d

Similar to the `get` function and added in `1.5.0`, the `in` function also get values contained in a data structure but will throw errors on bad keys to arrays, tuple, and string-likes. You should prefer `in` going forward as it can be used to better detect errors. For tables, structs, and abstract types, `in` behaves identically to `get`.

(in @[:a :b :c] 2) # -> :c
(in @[:a :b :c] 3) # -> raises error

To update a mutable data structure, use the put function. It takes 3 arguments, the data structure, the key, and the value, and returns the data structure. The allowed types keys and values depend on what data structure is passed in.

(put @[] 100 :a)
(put @{} :key "value")
(put @"" 100 92)

Note that for Arrays and Buffers, putting an index that is outside the length of the data structure will extend the data structure and fill it with nils in the case of the Array, or 0s in the case of the Buffer.

The last generic function for all data structures is the length function. This returns the number of values in a data structure (the number of keys in a dictionary type).