# Lists, Maps and API support for Containers Scarpet supports basic container types: lists and maps (aka hashmaps, dicts etc..) ## Container manipulation Here is a list of operations that work on all types of containers: lists, maps, as well as other Minecraft specific modifyable containers, like NBTs ### `get(container, address, ...), get(lvalue), ':' operator` Returns the value at `address` element from the `value`. For lists it indicates an index, use negative numbers to reach elements from the end of the list. `get` call will always be able to find the index. In case there is few items, it will loop over for maps, retrieves the value under the key specified in the `address` or null otherwise [Minecraft specific usecase]: In case `value` is of `nbt` type, uses address as the nbt path to query, returning null, if path is not found, one value if there was one match, or list of values if result is a list. Returned elements can be of numerical type, string texts, or another compound nbt tags In case to simplify the access with nested objects, you can add chain of addresses to the arguments of `get` rather than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
get(l(range(10)), 5)  => 5
get(l(range(10)), -1)  => 9
get(l(range(10)), 10)  => 0
l(range(10)):93  => 3
get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar')  => 3
### `has(container, address, ...), has(lvalue)` Similar to `get`, but returns boolean value indicating if the given index / key / path is in the container. Can be used to determine if `get(...)==null` means the element doesn't exist, or the stored value for this address is `null`, and is cheaper to run than `get`. Like get, it can accept multiple addresses for chains in nested containers. In this case `has(foo:a:b)` is equivalent to `has(get(foo,a), b)` or `has(foo, a, b)` ### `delete(container, address, ...), delete(lvalue)` Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it removes the key from the map, and for nbt - removes content from a given path. For lists and maps returns previous entry at the address, for nbt's - number of removed objects, with 0 indicating that the original value was unaffected. Like with the `get` and `has`, `delete` can accept chained addresses, as well as l-value container access, removing the value from the leaf of the path provided, so `delete(foo, a, b)` is the same as `delete(get(foo,a),b)` or `delete(foo:a:b)` Returns true, if container was changed, false, if it was left unchanged, and null if operation was invalid. ### `put(container, address, value), put(container, address, value, mode), put(lvalue, value)` **Lists** Modifies the container by replacing the value under the address with the supplied `value`. For lists, a valid index is required, but can be negative as well to indicate positions from the end of the list. If `null` is supplied as the address, it always means - add to the end of the list. There are three modes that lists can have items added to them: * `replace`(default): Replaces item under given index(address). Doesn't change the size of the array unless `null` address is used, which is an exception and then it appends to the end * `insert`: Inserts given element at a specified index, shifting the rest of the array to make space for the item. Note that index of -1 points to the last element of the list, thus inserting at that position and moving the previous last element to the new last element position. To insert at the end, use `+=` operator, or `null` address in put * `extend`: treats the supplied value as an iterable set of values to insert at a given index, extending the list by this amount of items. Again use `null` address/index to point to the end of the list Due to the extra mode parameter, there is no chaining for `put`, but you can still use l-value container access to indicate container and address, so `put(foo, key, value)` is the same as `put(foo:key, value)` or `foo:key=value` Returns true, if container got modified, false otherwise, and null if operation was invalid. **Maps** For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied key (address), or sets it if not currently present. **NBT Tags** The address for nbt values is a valid nbt path that you would use with `/data` command, and tag is any tag that would be applicable for a given insert operation. Note that to distinguish between proper types (like integer types, you need to use command notation, i.e. regular ints is `123`, while byte size int would be `123b` and an explicit string would be `"5"`, so it helps that scarpet uses single quotes in his strings. Unlike for lists and maps, it returns the number of affected nodes, or 0 if none were affected. There are three modes that NBT tags can have items added to them: * `replace`(default): Replaces item under given path(address). Removes them first if possible, and then adds given element to the supplied position. The target path can indicate compound tag keys, lists, or individual elements of the lists. * ``: Index for list insertions. Inserts given element at a specified index, inside a list specified with the path address. Fails if list is not specified. It behaves like `insert` mode for lists, i.e. it is not removing any of the existing elements. Use `replace` to remove and replace existing element. * `merge`: assumes that both path and replacement target are of compound type (dictionaries, maps, `{}` types), and merges keys from `value` with the compound tag under the path
a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
a = l(1, 2, 3); put(a, null, 4); a  => [1, 2, 3, 4]
a = l(1, 2, 3); put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a  => [1, 2, 3, 4, 5, 6]
a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a  => [1, 4, 5, 6, 2, 3]
a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = m(1,2,3,4); put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag  => {BlockData:{Properties:[1,2,3,4]}}
tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag  => {a:[{lvl:1},{lvl:1},{lvl:1}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
     => {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag
     => {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]}
## List operations ### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `l(iterator)` Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all elements are variables, you coujld use it to return multiple results from one function call, if that function returns a list of results with the same size as the `[]` call uses. In case there is only one argument and it is an iterator (vanilla expression specification has `range`, but Minecraft API implements a bunch of them, like `diamond`), it will convert it to a proper list. Iterators can only be used in high order functions, and are treated as empty lists, unless unrolled with `[]`. Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to `l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]` not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid although not recommended as they will make your code far less readable.
l(1,2,'foo') <=> [1, 2, 'foo']
l() <=> [] (empty list)
[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2] = [3, 4] => Error: l is not a variable
[foo, bar] = [3, 4]; foo==3 && bar==4 => 1
[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz]  => [3, 4, 5]
In the last example `[min(foo, bar), baz]` creates a valid L-value, as `min(foo, bar)` finds the lower of the variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values will be assigned new values ### `join(delim, list), join(delim, values ...)` Returns a string that contains joined elements of the list, iterator, or all values, concatenated with `delim` delimiter
join('-',range(10))  => 0-1-2-3-4-5-6-7-8-9
join('-','foo')  => foo
join('-', 'foo', 'bar')  => foo-bar
### `split(delim?, expr)` Splits a string under `expr` by `delim` which can be a regular expression. If no delimiter is specified, it splits by characters.
split('foo') => [f, o, o]
split('','foo')  => [f, o, o]
split('.','foo.bar')  => []
split('\\.','foo.bar')  => [foo, bar]
### `slice(expr, from, to?)` extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index of `from`, and ending at `to` if provided, or the end, if omitted
slice(l(0,1,2,3,4,5), 1, 3)  => [1, 2, 3]
slice('foobar', 0, 1)  => 'f'
slice('foobar', 3)  => 'bar'
slice(range(10), 3, 5)  => [3, 4, 5]
slice(range(10), 5)  => [5, 6, 7, 8, 9]
### `sort(list), sort(values ...)` Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new sorted list, not affecting the list passed to the argument
sort(3,2,1)  => [1, 2, 3]
sort('a',3,11,1)  => [1, 3, 11, 'a']
list = l(4,3,2,1); sort(list)  => [1, 2, 3, 4]
### `sort_key(list, key_expr)` Sorts a copy of the list in the order or keys as defined by the `key_expr` for each element
sort_key([1,3,2],_)  => [1, 2, 3]
sort_key([1,3,2],-_)  => [3, 2, 1]
sort_key(l(range(10)),rand(1))  => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
sort_key(l(range(20)),str(_))  => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
### `range(to), range(from, to), range(from, to, step)` Creates a range of numbers from `from`, no greater/larger than `to`. The `step` parameter dictates not only the increment size, but also direction (can be negative). The returned value is not a proper list, just the iterator but if for whatever reason you need a proper list with all items evaluated, use `l(range(to))`. Primarily to be used in higher order functions
range(10)  => [...]
l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(range(10),_a+_, 0)  => 45
range(5,10)  => [5, 6, 7, 8, 9]
range(20, 10, -2)  => [20, 18, 16, 14, 12]
range(-0.3, 0.3, 0.1)  => [-0.3, -0.2, -0.1, 0, 0.1, 0.2]
range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2]
## Map operations Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. Apart from container access functions, (`. , get, put, has, delete`), the following functions: ### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))` creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to `{l(foo, bar), l(baz, quux)}`, which is equivalent to somewhat older, but more traditional functional form of `m(l(foo, bar),l(baz, quuz))`. Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to `m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}` not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid although not recommended as they will make your code far less readable. When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`.
{1, 2, 'foo'} => {1: null, 2: null, foo: null}
m() <=> {} (empty map)
{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
reduce(range(10), put(_a, _, _*_); _a, {})
     => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
### `keys(map), values(map), pairs(map)` Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map