Collections & Higher-Order Functions

24 functions for transforming, searching, and restructuring lists. All collection functions are immutable — they return new lists rather than modifying the originals.

map

Apply a function to each element and return a new list of results.

map(list, fn) -> list
ParameterTypeDescription
listlistInput list
fnfunction(elem) -> valueTransformation function

Returns: list

let scores = [30, 28, 35, 22]
map(scores, |q| q - 33)   # [-3, -5, 2, -11]

# Pipeline style
["ATCG", "GCTA", "TTAA"]
  |> map(|s| len(s))      # [4, 4, 4]

# With index (two-arg callback)
map(["A", "B", "C"], |elem, i| format("{}: {}", i, elem))
# ["0: A", "1: B", "2: C"]

filter

Return a new list containing only elements for which the predicate returns true.

filter(list, fn) -> list
ParameterTypeDescription
listlistInput list
fnfunction(elem) -> boolPredicate function
let quality = [30, 12, 35, 8, 28]
filter(quality, |q| q >= 20)   # [30, 35, 28]

# Filter sequences by GC content
let seqs = ["ATATATAT", "GCGCGCGC", "ATGCATGC"]
seqs |> filter(|s| {
  let gc = len(filter(split(s, ""), |b| b == "G" or b == "C"))
  gc / len(s) > 0.4
})
# ["GCGCGCGC", "ATGCATGC"]

reduce

Accumulate a list into a single value using a reducer function.

reduce(list, fn, init?) -> value
ParameterTypeDescription
listlistInput list
fnfunction(acc, elem) -> valueReducer function
initany (optional)Initial accumulator value
reduce([1, 2, 3, 4], |acc, x| acc + x, 0)   # 10

# Count nucleotide frequencies
let seq = split("ATCGATCGAA", "")
let counts = reduce(seq, |acc, base| {
  acc[base] = (acc[base] ?? 0) + 1
  acc
}, {})
# {"A": 4, "T": 2, "C": 2, "G": 2}

Edge case: Without init, the first element is used as the initial accumulator. Calling reduce on an empty list without init raises an error.

sort

Return a new list sorted in ascending order. Numbers sort numerically, strings sort lexicographically.

sort(list) -> list
sort([3, 1, 4, 1, 5])         # [1, 1, 3, 4, 5]
sort(["chr2", "chr10", "chr1"]) # ["chr1", "chr10", "chr2"] (lexicographic!)

sort_by

Sort with a custom key or comparator function.

sort_by(list, fn) -> list
# Sort by string length
sort_by(["ATCG", "AT", "ATCGATCG"], |s| len(s))
# ["AT", "ATCG", "ATCGATCG"]

# Natural chromosome sort
let chroms = ["chr2", "chr10", "chr1", "chrX"]
sort_by(chroms, |c| {
  let n = replace(c, "chr", "")
  if n == "X" { 23 }
  else if n == "Y" { 24 }
  else { int(n) }
})
# ["chr1", "chr2", "chr10", "chrX"]

push / pop

List manipulation primitives.

push(list, value) -> list   # append to end
pop(list) -> value          # remove + return last element
let samples = ["S1", "S2"]
let updated = push(samples, "S3")   # ["S1", "S2", "S3"]

let last = pop(updated)       # "S3"

zip

Pair elements from two lists into a list of two-element lists. Stops at the shorter list.

zip(a, b) -> list
zip(["A", "T", "C"], [1, 2, 3])
# [["A", 1], ["T", 2], ["C", 3]]

let genes = ["BRCA1", "TP53", "EGFR"]
let pvals = [0.001, 0.05, 0.003]
zip(genes, pvals) |> filter(|pair| pair[1] < 0.01)
# [["BRCA1", 0.001], ["EGFR", 0.003]]

enumerate

Pair each element with its zero-based index.

enumerate(list) -> list
enumerate(["A", "B", "C"])
# [[0, "A"], [1, "B"], [2, "C"]]

# Find position of first 'N' base
let seq = split("ATCNGATCG", "")
let n_pos = enumerate(seq) |> find(|pair| pair[1] == "N")
println("N at position:", n_pos[0])   # N at position: 3

flatten

Flatten one level of nesting from a list of lists.

flatten(list) -> list
flatten([[1, 2], [3, 4], [5]])   # [1, 2, 3, 4, 5]
flatten([[1, [2, 3]], [4]])       # [1, [2, 3], 4]  (one level only)

reverse

Return a new list with elements in reverse order.

reverse(list) -> list
reverse([1, 2, 3])   # [3, 2, 1]

# Reverse complement helper
let seq = "ATCGATCG"
let complement = {"A": "T", "T": "A", "C": "G", "G": "C"}
split(seq, "") |> map(|b| complement[b]) |> reverse |> join("")
# "CGATCGAT"

unique

Remove duplicate elements, preserving first occurrence order.

unique(list) -> list
unique([1, 2, 2, 3, 1])           # [1, 2, 3]
unique(["chr1", "chr2", "chr1"])   # ["chr1", "chr2"]

first / last

Return the first or last element of a list. Returns nil for empty lists.

first(list) -> value | nil
last(list) -> value | nil
first([10, 20, 30])   # 10
last([10, 20, 30])    # 30
first([])             # nil

take / drop

Slice a list from the beginning (take) or remove leading elements (drop).

take(list, n) -> list
drop(list, n) -> list
take([1, 2, 3, 4, 5], 3)   # [1, 2, 3]
drop([1, 2, 3, 4, 5], 2)   # [3, 4, 5]
take([1, 2], 10)            # [1, 2]  (safe: no error)

# Paginate results
let all_genes = range(0, 100) |> map(|i| format("gene_{}", i))
let page2 = all_genes |> drop(20) |> take(10)   # genes 20..29

any / all

Test if any or all elements satisfy a predicate. Short-circuits on first match/failure.

any(list, fn) -> bool
all(list, fn) -> bool
any([10, 20, 30], |x| x > 25)    # true
all([10, 20, 30], |x| x > 5)     # true
all([10, 20, 30], |x| x > 15)    # false

let quality_scores = [30, 28, 35, 22, 31]
if all(quality_scores, |q| q >= 20) {
  println("All reads pass QC")
}

find / find_index

Find the first element (or its index) that matches a predicate.

find(list, fn) -> value | nil
find_index(list, fn) -> int | nil
find([10, 25, 30], |x| x > 20)         # 25
find_index([10, 25, 30], |x| x > 20)   # 1

let genes = ["BRCA1", "TP53", "EGFR", "KRAS"]
find(genes, |g| starts_with(g, "EG"))   # "EGFR"
find(genes, |g| starts_with(g, "XX"))   # nil

chunk

Split a list into sub-lists of the given size. The last chunk may be shorter.

chunk(list, size) -> list
chunk([1, 2, 3, 4, 5], 2)   # [[1, 2], [3, 4], [5]]

# Process reads in batches
let reads = range(0, 1000)
let batches = chunk(reads, 100)   # 10 batches of 100

window

Return overlapping windows (sublists) of the given size.

window(list, size) -> list
window([1, 2, 3, 4, 5], 3)
# [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

# Sliding average of coverage
let cov = [10, 12, 8, 15, 20, 18]
window(cov, 3) |> map(|w| mean(w))
# [10.0, 11.67, 14.33, 17.67]

group_by

Group list elements by a key function. Returns a map of key to list of matching elements.

group_by(list, fn) -> map
group_by([1, 2, 3, 4, 5, 6], |x| if x % 2 == 0 { "even" } else { "odd" })
# {"odd": [1, 3, 5], "even": [2, 4, 6]}

# Group sequences by length
let seqs = ["AT", "GC", "ATCG", "CG", "ATCGATCG"]
group_by(seqs, |s| len(s))
# {2: ["AT", "GC", "CG"], 4: ["ATCG"], 8: ["ATCGATCG"]}