Sequences, Maps, and Flows
Four structures carry data through a J2 program: sequences for order, maps for names, pairs for twos, and flows for laziness.
Sequences
A sequence is an ordered, growable collection written in square brackets. Indexing is zero based, and elements may be reassigned in place:
xs := [10, 20, 30]
print(xs[0]) # 10
print(len(xs)) # 3
xs[1] = 99
print(xs) # [10,99,30]
Indexes must be in range and non-negative; anything else raises
IndexError. There is no from-the-end indexing; the last element is
xs[len(xs) - 1].
Growing
push appends in place. + concatenates into a new sequence,
and the compound form xs += [x] rebinds a mutable name to the enlarged
sequence:
xs := [1, 2]
push(xs, 3) # in place
print(xs) # [1,2,3]
ys := []
for n in 1..4 { ys += [n * n] }
print(ys) # [1,4,9,16]
print([1, 2] + [3]) # [1,2,3], a new seq
For numeric work, build the sequence at its final size with
make_seq(n, init) and write by index; this is both the fastest shape and
the one the parallelizer recognizes best:
n = 8
squares := make_seq(n, 0)
for i in 0..(n - 1) {
squares[i] = i * i
}
print(squares) # [0,1,4,9,16,25,36,49]
Reference semantics
A sequence is shared, not copied, when bound to another name or passed to a function. Mutation through one reference is visible through all of them:
a := [1, 2, 3]
b = a
push(b, 4)
print(a) # [1,2,3,4]: a and b are the same seq
Concatenation with + is the escape hatch: it builds a fresh sequence,
so copy = xs + [] makes an independent copy.
Slicing and reshaping
slice(s, i, j) returns elements from i up to but not
including j. The standard library rounds out the family:
sort, sort_by, reverse, unique,
flatten, zip, enumerate, take,
contains, join, and the reductions sum,
min, and max. Each is specified in the
Standard Library.
s = [3, 1, 4, 1, 5, 9, 2, 6]
print(slice(s, 1, 4)) # [1,4,1]
print(sort(s)) # [1,1,2,3,4,5,6,9]
print(unique(s)) # [3,1,4,5,9,2,6]
print(take(s, 3)) # [3,1,4]
print(sum(s)) # 31
Nesting
Sequences nest to form grids and tables. Chained indexing reads and writes the inner elements:
grid := [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(grid[1][2]) # 6
grid[1][1] = 50
print(grid[1]) # [4,50,6]
Maps
A map associates text keys with values. Literal keys are written bare, like field names, which keeps map literals close to what they usually are: lightweight records.
person := {name: "Ada", born: 1815}
print(person.name) # dot access
print(person["born"]) # index access with a text key
person.role = "engineer" # add or update a field
print(len(person)) # 3
Reading a key that is not present raises KeyError. Both access forms
read the same data: use dot syntax when the key is a fixed name in your program and
index syntax when the key arrives in a variable.
Adding or updating entries is done with dot syntax, as above. Assigning through
index syntax (m["key"] = v) is not supported in this release, and map
keys in literals must be bare identifiers.
Maps are references, like sequences, and they are records rather than iterable collections: a map cannot be looped over. When you need to iterate labeled data, use a sequence of maps:
people = [
{name: "Ann", age: 30},
{name: "Bob", age: 25}
]
for p in people {
print(fmt("{} is {}", p.name, p.age))
}
ages = map(people, func(p) = p.age)
print(sum(ages)) # 55
Pairs
A pair groups exactly two values: (a, b). Pairs appear mostly as the
output of zip and enumerate, and the way to take one apart is
the two-variable for:
points = zip([1, 2, 3], [10, 20, 30])
for x, y in points {
print(x * y) # 10 40 90
}
Pairs are not indexable and have no field names; if a value needs structure beyond position, reach for a map or a class.
Flows
A flow is a lazy stream of values. Ranges are the flows you meet every day:
1..100 holds no hundred integers, it produces them on demand. Loops,
reductions, and collect all consume flows:
print(sum(1..1000)) # 500500, nothing materialized
print(collect(3..6)) # [3,4,5,6], now it is a seq
An open-ended range a.. is an infinite flow. J2 refuses to consume one
without a bound: collect(1..) raises InfiniteFlowError rather
than hanging. The until clause of a loop is the stopping condition that
makes an infinite flow usable:
seen := []
for n in 1.. until n * n > 20 {
seen += [n]
}
print(seen) # [1,2,3,4,5]