Collections¶
Desi provides three built-in collection types: list, set, and dict.
List¶
Ordered, mutable sequence of elements.
# Create list
let numbers: list[int] = [1, 2, 3, 4, 5]
let empty: list[str] = []
# Operations
numbers.append(6) # Add element
let first = numbers[0] # Index access
let length = len(numbers) # Length
# Iteration
for n: int in numbers:
print(n)
List with Custom Types¶
Lists can contain any type, including classes:
class Point:
pub x: int
pub y: int
let points: list[Point] = [
Point(1, 2),
Point(3, 4)
]
print(points[0].x) # 1
List Transformation Methods¶
Lists have built-in methods for transforming and filtering elements:
let numbers: list[int] = [1, 2, 3, 4, 5]
# Transform each element
let doubled = numbers.map(lambda<int> x: int: x * 2)
# doubled = [2, 4, 6, 8, 10]
# Filter elements
let evens = numbers.filter(lambda<bool> x: int: x % 2 == 0)
# evens = [2, 4]
# Chain transformations — an expression cannot be split across lines
let result = numbers.map(lambda<int> x: int: x * 2).filter(lambda<bool> x: int: x > 5)
# result = [6, 8, 10]
# Pipe syntax also works
let piped = numbers |> map(lambda<int> x: int: x * 2) |> filter(lambda<bool> x: int: x > 5)
One expression, one line
Desi has no line-continuation. A chain or pipeline has to stay on a single line; bind an intermediate result to a name when it gets long.
See also: Built-in Functions for functional-style
map()andfilter()
Set¶
Unordered collection of unique elements.
# Create set (use #{} syntax)
let tags: set[str] = #{"rust", "go", "desi"}
let empty: set[int] = #{}
# Operations
tags.add("python") # Add element
let has_rust = "rust" in tags # Membership
let count = len(tags) # Length
# Iteration (order not guaranteed)
for tag: str in tags:
print(tag)
Note: Sets are unordered. Iteration order may vary.
Set with Custom Types¶
Sets use pointer identity by default:
class Item:
pub id: int
let i1 = Item(1)
let items: set[Item] = #{}
items.add(i1)
if i1 in items: # Found - same pointer
print("found")
let i2 = Item(1)
if i2 in items: # NOT found - different pointer
print("not found")
Dict¶
Key-value mapping.
# Create dict
let ages: dict[str, int] = {"Alice": 30, "Bob": 25}
let empty: dict[str, int] = {}
# Operations
ages.insert("Charlie", 35) # Insert/update
let age = ages.get("Alice", 0) # Get with default
let val = ages.setdefault("Dave", 40) # Get or insert default
let has = "Alice" in ages # Key exists
# Iteration
for key, val: str, int in ages.items():
print(key)
print(val)
# Mutable iteration: modify values during iteration
let mut scores = {"alice": 80, "bob": 65}
for name, mut score in scores.items():
if score < 70:
score := 70 # Write-back to dict
Dict with Custom Keys¶
Custom types can be dict keys. By default, they use pointer identity:
class Point:
pub x: int
pub y: int
let p = Point(1, 2)
let cache: dict[Point, str] = {}
cache.insert(p, "origin")
if p in cache: # Found - same pointer
print("found")
For value-based lookup, implement __hash__ and __eq__:
class Point:
pub x: int
pub y: int
pub def __hash__(self) -> u64:
return self.x * 31 + self.y
pub def __eq__(self, other: Point) -> bool:
return self.x == other.x and self.y == other.y
let cache: dict[Point, str] = {Point(1, 2): "origin"}
# Different object, same values - FOUND!
if Point(1, 2) in cache:
print("found via value equality")
Nested Collections¶
Collections can be nested arbitrarily:
# List of dicts
let records: list[dict[str, int]] = []
records.append({"x": 100, "y": 200})
print(records[0].get("x", 0)) # 100
# Dict with list values
let groups: dict[str, list[int]] = {}
groups.insert("evens", [2, 4, 6])
let evens = groups.get("evens", [])
print(evens[0]) # 2
# Deeply nested
let matrix: list[list[set[int]]] = []
# ... build structure ...
Memory Ownership¶
- Collections own their elements
dict.getreturns a borrowed reference (not a copy)- When a collection is freed, its elements are freed
let cache: dict[str, Item] = {}
cache.insert("key", Item(42))
let item = cache.get("key", Item(0)) # Borrowed reference
print(item.id) # 42
# item is NOT freed here - dict owns it