Chapter 10: Collections Library

**Work with arrays and hash maps efficiently.**

NanoLang Mascot

This chapter covers NanoLang's collection types: arrays for ordered sequences and hash maps for key-value storage. Both provide constant-time operations and type safety.

10.1 Arrays (Fixed-Size Collections)

Arrays are fixed-size, ordered collections of elements. All elements must have the same type.

Creating Arrays

**Literal syntax:**


fn create_arrays() -> array<int> {
    let numbers: array<int> = [1, 2, 3, 4, 5]
    let empty: array<int> = []
    let strings: array<string> = ["a", "b", "c"]
    return numbers
}

shadow create_arrays {
    let arr: array<int> = (create_arrays)
    assert (== (array_length arr) 5)
}

**Using array_new:**


fn create_with_new() -> array<int> {
    let zeros: array<int> = (array_new 10 0)
    let spaces: array<string> = (array_new 5 " ")
    return zeros
}

shadow create_with_new {
    let arr: array<int> = (create_with_new)
    assert (== (array_length arr) 10)
    assert (== (at arr 0) 0)
}

**Syntax:** array_new(size, default_value)

  • Size must be non-negative
  • All elements initialized to default value

Accessing Elements


fn access_example() -> int {
    let nums: array<int> = [10, 20, 30, 40, 50]
    
    let first: int = (at nums 0)   # 10
    let third: int = (at nums 2)   # 30
    let last: int = (at nums 4)    # 50
    
    return (+ first last)
}

shadow access_example {
    assert (== (access_example) 60)
}

⚠️ **Warning:** at performs bounds checking. Out-of-bounds access terminates the program.

Modifying Arrays

Arrays must be declared mut to be modified:


fn modify_array() -> array<int> {
    let mut nums: array<int> = [1, 2, 3, 4, 5]
    
    (array_set nums 0 10)  # nums[0] = 10
    (array_set nums 2 30)  # nums[2] = 30
    
    return nums
}

shadow modify_array {
    let result: array<int> = (modify_array)
    assert (== (at result 0) 10)
    assert (== (at result 1) 2)  # Unchanged
    assert (== (at result 2) 30)
}

**Syntax:** array_set(mut_array, index, value)

  • Requires mutable array
  • Bounds-checked
  • Type-checked (value must match array type)

Array Length


fn length_example() -> bool {
    let nums: array<int> = [1, 2, 3, 4, 5]
    let empty: array<int> = []
    
    return (and 
        (== (array_length nums) 5)
        (== (array_length empty) 0)
    )
}

shadow length_example {
    assert (length_example)
}

Iterating Over Arrays

**With for loop:**


fn sum_array(arr: array<int>) -> int {
    let mut sum: int = 0
    let len: int = (array_length arr)
    
    for i in (range 0 len) {
        set sum (+ sum (at arr i))
    }
    
    return sum
}

shadow sum_array {
    let nums: array<int> = [1, 2, 3, 4, 5]
    assert (== (sum_array nums) 15)
}

**With while loop:**


fn find_max(arr: array<int>) -> int {
    assert (> (array_length arr) 0)  # Non-empty
    
    let mut max: int = (at arr 0)
    let mut i: int = 1
    let len: int = (array_length arr)
    
    while (< i len) {
        let current: int = (at arr i)
        if (> current max) {
            set max current
        }
        set i (+ i 1)
    }
    
    return max
}

shadow find_max {
    assert (== (find_max [5, 2, 9, 1, 7]) 9)
    assert (== (find_max [42]) 42)
}

Common Array Patterns

**Pattern 1: Fill array**


fn fill_array(size: int, value: int) -> array<int> {
    let mut arr: array<int> = (array_new size 0)
    
    for i in (range 0 size) {
        (array_set arr i value)
    }
    
    return arr
}

shadow fill_array {
    let arr: array<int> = (fill_array 5 42)
    assert (== (at arr 0) 42)
    assert (== (at arr 4) 42)
}

**Pattern 2: Map (transform elements)**


fn double_all(arr: array<int>) -> array<int> {
    let len: int = (array_length arr)
    let mut result: array<int> = (array_new len 0)
    
    for i in (range 0 len) {
        (array_set result i (* (at arr i) 2))
    }
    
    return result
}

shadow double_all {
    let input: array<int> = [1, 2, 3]
    let output: array<int> = (double_all input)
    assert (== (at output 0) 2)
    assert (== (at output 1) 4)
    assert (== (at output 2) 6)
}

**Pattern 3: Filter**


fn count_positive(arr: array<int>) -> int {
    let mut count: int = 0
    let len: int = (array_length arr)
    
    for i in (range 0 len) {
        if (> (at arr i) 0) {
            set count (+ count 1)
        }
    }
    
    return count
}

shadow count_positive {
    let nums: array<int> = [1, -2, 3, -4, 5]
    assert (== (count_positive nums) 3)
}

**Pattern 4: Reduce (accumulate)**


fn product(arr: array<int>) -> int {
    let mut result: int = 1
    let len: int = (array_length arr)
    
    for i in (range 0 len) {
        set result (* result (at arr i))
    }
    
    return result
}

shadow product {
    assert (== (product [2, 3, 4]) 24)
    assert (== (product [5, 5]) 25)
}

10.2 Hash Maps (Key-Value Storage)

Hash maps provide O(1) average-time lookups for key-value pairs.

Creating Hash Maps


fn create_hashmap() -> HashMap<string, int> {
    let scores: HashMap<string, int> = (map_new)
    return scores
}

shadow create_hashmap {
    let hm: HashMap<string, int> = (create_hashmap)
    assert (== (map_size hm) 0)
    (map_free hm)
}

💡 **Pro Tip:** Type annotation is **required** for map_new. The compiler needs to know key and value types.

Supported Types

**Keys:** int or string

**Values:** int or string


fn all_combinations() -> void {
    let hm_si: HashMap<string, int> = (map_new)
    let hm_ss: HashMap<string, string> = (map_new)
    let hm_ii: HashMap<int, int> = (map_new)
    let hm_is: HashMap<int, string> = (map_new)
    
    (map_free hm_si)
    (map_free hm_ss)
    (map_free hm_ii)
    (map_free hm_is)
}

shadow all_combinations {
    (all_combinations)
}

Adding Elements


fn add_scores() -> HashMap<string, int> {
    let scores: HashMap<string, int> = (map_new)
    
    (map_put scores "Alice" 100)
    (map_put scores "Bob" 85)
    (map_put scores "Carol" 92)
    
    return scores
}

shadow add_scores {
    let hm: HashMap<string, int> = (add_scores)
    assert (== (map_size hm) 3)
    (map_free hm)
}

**Syntax:** map_put(hashmap, key, value)

  • Inserts new key
  • Updates existing key

Retrieving Values


fn get_score(scores: HashMap<string, int>, name: string) -> int {
    return (map_get scores name)
}

shadow get_score {
    let scores: HashMap<string, int> = (map_new)
    (map_put scores "Alice" 100)
    
    assert (== (get_score scores "Alice") 100)
    assert (== (get_score scores "Unknown") 0)  # Default
    
    (map_free scores)
}

**Default values:**

  • int keys/values: returns 0
  • string keys/values: returns ""

Checking Existence


fn safe_get(hm: HashMap<string, int>, key: string) -> int {
    if (map_has hm key) {
        return (map_get hm key)
    }
    return -1  # Not found
}

shadow safe_get {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "key" 42)
    
    assert (== (safe_get hm "key") 42)
    assert (== (safe_get hm "missing") -1)
    
    (map_free hm)
}

Removing Elements


fn remove_example() -> bool {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "a" 1)
    (map_put hm "b" 2)
    
    assert (== (map_size hm) 2)
    
    (map_remove hm "a")
    
    let result: bool = (and
        (== (map_size hm) 1)
        (not (map_has hm "a"))
    )
    
    (map_free hm)
    return result
}

shadow remove_example {
    assert (remove_example)
}

Hash Map Size


fn size_example() -> bool {
    let hm: HashMap<string, int> = (map_new)
    
    assert (== (map_size hm) 0)
    
    (map_put hm "a" 1)
    assert (== (map_size hm) 1)
    
    (map_put hm "b" 2)
    assert (== (map_size hm) 2)
    
    (map_put hm "a" 3)  # Update existing
    assert (== (map_size hm) 2)  # Size unchanged
    
    (map_free hm)
    return true
}

shadow size_example {
    assert (size_example)
}

💡 **Note:** map_length is an alias for map_size.

Clearing and Freeing


fn clear_vs_free() -> void {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "a" 1)
    (map_put hm "b" 2)
    
    # Clear removes all entries, but map still usable
    (map_clear hm)
    assert (== (map_size hm) 0)
    
    (map_put hm "c" 3)  # Can still add
    assert (== (map_size hm) 1)
    
    # Free releases all memory
    (map_free hm)
    # Cannot use hm after this!
}

shadow clear_vs_free {
    (clear_vs_free)
}

**Differences:**

  • map_clear - Removes entries, keeps capacity
  • map_free - Releases all memory

Iterating Over Keys


fn sum_all_values(hm: HashMap<string, int>) -> int {
    let keys: array<string> = (map_keys hm)
    let mut sum: int = 0
    let len: int = (array_length keys)
    
    for i in (range 0 len) {
        let key: string = (at keys i)
        let value: int = (map_get hm key)
        set sum (+ sum value)
    }
    
    return sum
}

shadow sum_all_values {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "a" 10)
    (map_put hm "b" 20)
    (map_put hm "c" 30)
    
    assert (== (sum_all_values hm) 60)
    
    (map_free hm)
}

Iterating Over Values


fn max_value(hm: HashMap<string, int>) -> int {
    let values: array<int> = (map_values hm)
    let len: int = (array_length values)
    
    if (== len 0) {
        return 0
    }
    
    let mut max: int = (at values 0)
    
    for i in (range 1 len) {
        let current: int = (at values i)
        if (> current max) {
            set max current
        }
    }
    
    return max
}

shadow max_value {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "a" 5)
    (map_put hm "b" 99)
    (map_put hm "c" 42)
    
    assert (== (max_value hm) 99)
    
    (map_free hm)
}

10.3 Practical Examples

Example 1: Word Counter


fn count_words(words: array<string>) -> HashMap<string, int> {
    let counts: HashMap<string, int> = (map_new)
    let len: int = (array_length words)
    
    for i in (range 0 len) {
        let word: string = (at words i)
        if (map_has counts word) {
            let current: int = (map_get counts word)
            (map_put counts word (+ current 1))
        } else {
            (map_put counts word 1)
        }
    }
    
    return counts
}

shadow count_words {
    let words: array<string> = ["apple", "banana", "apple", "cherry", "banana", "apple"]
    let counts: HashMap<string, int> = (count_words words)
    
    assert (== (map_get counts "apple") 3)
    assert (== (map_get counts "banana") 2)
    assert (== (map_get counts "cherry") 1)
    
    (map_free counts)
}

Example 2: Unique Elements


fn unique(arr: array<int>) -> array<int> {
    let seen: HashMap<int, int> = (map_new)
    let mut unique_list: array<int> = (array_new (array_length arr) 0)
    let mut count: int = 0
    
    for i in (range 0 (array_length arr)) {
        let value: int = (at arr i)
        if (not (map_has seen value)) {
            (map_put seen value 1)
            (array_set unique_list count value)
            set count (+ count 1)
        }
    }
    
    # Create result with exact size
    let mut result: array<int> = (array_new count 0)
    for i in (range 0 count) {
        (array_set result i (at unique_list i))
    }
    
    (map_free seen)
    return result
}

shadow unique {
    let input: array<int> = [1, 2, 3, 2, 1, 4, 5, 3]
    let output: array<int> = (unique input)
    
    assert (== (array_length output) 5)
    assert (== (at output 0) 1)
    assert (== (at output 1) 2)
    assert (== (at output 2) 3)
}

Example 3: Leaderboard


struct Player {
    name: string,
    score: int
}

fn top_score(players: array<Player>) -> string {
    if (== (array_length players) 0) {
        return "No players"
    }
    
    let mut top_player: Player = (at players 0)
    
    for i in (range 1 (array_length players)) {
        let player: Player = (at players i)
        if (> player.score top_player.score) {
            set top_player player
        }
    }
    
    return top_player.name
}

shadow top_score {
    let mut players: array<Player> = (array_new 3 Player { name: "", score: 0 })
    
    (array_set players 0 Player { name: "Alice", score: 100 })
    (array_set players 1 Player { name: "Bob", score: 150 })
    (array_set players 2 Player { name: "Carol", score: 120 })
    
    assert (== (top_score players) "Bob")
}

Example 4: Index Mapping


fn build_index(items: array<string>) -> HashMap<string, int> {
    let index: HashMap<string, int> = (map_new)
    
    for i in (range 0 (array_length items)) {
        let item: string = (at items i)
        (map_put index item i)
    }
    
    return index
}

fn find_position(items: array<string>, target: string) -> int {
    let index: HashMap<string, int> = (build_index items)
    
    if (map_has index target) {
        let pos: int = (map_get index target)
        (map_free index)
        return pos
    }
    
    (map_free index)
    return -1
}

shadow find_position {
    let items: array<string> = ["apple", "banana", "cherry", "date"]
    
    assert (== (find_position items "banana") 1)
    assert (== (find_position items "date") 3)
    assert (== (find_position items "grape") -1)
}

Example 5: Group By


fn group_by_length(words: array<string>) -> HashMap<int, int> {
    let groups: HashMap<int, int> = (map_new)
    
    for i in (range 0 (array_length words)) {
        let word: string = (at words i)
        let len: int = (str_length word)
        
        if (map_has groups len) {
            let count: int = (map_get groups len)
            (map_put groups len (+ count 1))
        } else {
            (map_put groups len 1)
        }
    }
    
    return groups
}

shadow group_by_length {
    let words: array<string> = ["a", "bb", "ccc", "dd", "e", "fff"]
    let groups: HashMap<int, int> = (group_by_length words)
    
    assert (== (map_get groups 1) 2)  # "a", "e"
    assert (== (map_get groups 2) 2)  # "bb", "dd"
    assert (== (map_get groups 3) 2)  # "ccc", "fff"
    
    (map_free groups)
}

10.4 Performance & Best Practices

Arrays

**✅ DO:**


# Pre-allocate known size
let mut arr: array<int> = (array_new 100 0)

# Cache length in loops
let len: int = (array_length arr)
for i in (range 0 len) {
    # Process (at arr i)
}

# Use for-range for clean iteration
for i in (range 0 (array_length arr)) {
    # Simpler than while
}

**❌ DON'T:**


# Don't call array_length repeatedly in loop condition
let mut i: int = 0
while (< i (array_length arr)) {  # Calls length every iteration!
    set i (+ i 1)
}

# Don't ignore bounds checking
# (at arr -1)  # Will terminate!

Hash Maps

**✅ DO:**


# Always check before getting
if (map_has hm key) {
    let value: int = (map_get hm key)
    # Use value
}

# Use clear for reuse
let hm: HashMap<string, int> = (map_new)
# ... use hm ...
(map_clear hm)
# Can add new entries

**❌ DON'T:**


# Don't forget type annotation
# let hm = (map_new)  # ERROR: can't infer type!

# Don't assume default means "not found"
let value: int = (map_get hm key)
# Is value 0 because missing or because stored 0?

# Better: Check with map_has first

Memory Management

HashMaps are **garbage collected automatically** - no manual cleanup needed:


fn automatic_cleanup() -> int {
    let hm: HashMap<string, int> = (map_new)
    (map_put hm "key" 42)

    # ... use hm ...

    # GC automatically cleans up when hm goes out of scope
    return 0
}

shadow automatic_cleanup {
    assert (== (automatic_cleanup) 0)
}

Summary

In this chapter, you learned:

  • ✅ Arrays: fixed-size, ordered, type-safe collections
  • ✅ Array operations: array_new, at, array_set, array_length
  • ✅ Array patterns: map, filter, reduce, iteration
  • ✅ Hash maps: O(1) key-value storage
  • ✅ Hash map operations: map_new, map_put, map_get, map_has, map_remove
  • ✅ Iteration with map_keys and map_values
  • ✅ Memory management with map_free

Quick Reference

OperationArrayHashMap
**Create**[1, 2, 3] or array_newmap_new (needs type annotation)
**Size**array_length(arr)map_size(hm)
**Access**at(arr, i)map_get(hm, key)
**Modify**array_set(arr, i, val)map_put(hm, key, val)
**Check**< i (array_length arr)map_has(hm, key)
**Remove**N/A (fixed size)map_remove(hm, key)
**Iterate**for i in rangemap_keys / map_values
**Clear**N/Amap_clear(hm)
**Free**N/A (auto)map_free(hm) required

---

**Previous:** Chapter 9: Core Utilities

**Next:** Chapter 11: I/O & Filesystem