Generic utility library for Zig
Zero hidden allocations: functions that need memory take an Allocator.
Iterator-first: most transformations return lazy iterators.
Add lo.zig as a dependency in your build.zig.zon:
zig fetch --save git+https://github.com/OrlovEvgeny/lo.zigThen in your build.zig:
const lo_dep = b.dependency("lo", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("lo", lo_dep.module("lo"));const lo = @import("lo");
const total = lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10
const head = lo.first(i32, &.{ 10, 20, 30 }); // 10
const safe = lo.unwrapOr(i32, null, 42); // 42- Slice Helpers - first, last, nth, firstOr, lastOr, nthOr, initial, tail, drop, dropRight, dropWhile, dropWhileAlloc, dropRightWhile, take, takeRight, takeWhile, takeWhileAlloc, takeRightWhile, sample, samples
- Transform - map, mapAlloc, mapIndex, filter, filterAlloc, reject, rejectAlloc, compact, compactAlloc, flatten, flattenAlloc, flattenDeep, flatMap, flatMapAlloc, without, forEach, forEachIndex, compactMap, filterMapIter
- Aggregate - reduce, reduceRight, sum, sumBy, product, productBy, mean, meanBy, min, max, minBy, maxBy, minMax, minMaxBy, count, countBy, countValues, mode, median, variance, stddev, sampleVariance, sampleStddev, percentile
- Sort & Order - sortBy, sortByAlloc, sortByField, sortByFieldAlloc, toSortedAlloc, isSorted, equal, reverse, shuffle
- Set Operations - uniq, uniqBy, intersect, union_, difference, symmetricDifference, findDuplicates, findUniques, elementsMatch, differenceWith, intersectWith, unionWith
- Partition & Group - partition, groupBy, chunk, window, scan, scanAlloc
- Combine - concat, splice, interleave, fill, fillRange, repeat, repeatBy, times, timesAlloc
- Search - find, findIndex, findLast, findLastIndex, indexOf, lastIndexOf, contains, containsBy, every, some, none, minIndex, maxIndex, binarySearch, lowerBound, upperBound, sortedIndex, sortedLastIndex
- Map Helpers - keys, keysAlloc, values, valuesAlloc, entries, entriesAlloc, fromEntries, mapKeys, mapValues, filterMap, filterKeys, filterValues, pickKeys, omitKeys, invert, merge, assign, mapEntries, mapToSlice, valueOr, hasKey, mapCount, keyBy, associate
- String Helpers - words, wordsAlloc, camelCase, pascalCase, snakeCase, kebabCase, capitalize, lowerFirst, toLower, toUpper, trim, trimStart, trimEnd, startsWith, endsWith, includes, substr, ellipsis, strRepeat, padLeft, padRight, runeLength, randomString, split, splitAlloc, join, replace, replaceAll, chunkString
- Math - sum, mean, median, variance, stddev, sampleVariance, sampleStddev, percentile, lerp, remap, clamp, inRange, cumSum, cumProd, rangeAlloc, rangeWithStepAlloc
- Tuple Helpers - zip, zipAlloc, zipWith, unzip, enumerate
- Type Helpers - isNull, isNotNull, unwrapOr, coalesce, empty, isEmpty, isNotEmpty, ternary, toConst
- Types - Entry, Pair, MinMax, RangeError, PartitionResult, UnzipResult, AssocEntry, and iterator types
Returns the first element of a slice, or null if empty.
lo.first(i32, &.{ 10, 20, 30 }); // 10Returns the last element of a slice, or null if empty.
lo.last(i32, &.{ 10, 20, 30 }); // 30Element at the given index. Negative indices count from the end. Returns null if out of bounds.
lo.nth(i32, &.{ 10, 20, 30 }, -1); // 30Returns the first element, or a default if the slice is empty.
lo.firstOr(i32, &.{ 10, 20, 30 }, 0); // 10
lo.firstOr(i32, &.{}, 42); // 42Returns the last element, or a default if the slice is empty.
lo.lastOr(i32, &.{ 10, 20, 30 }, 0); // 30
lo.lastOr(i32, &.{}, 42); // 42Element at the given index with a default. Negative indices count from the end.
lo.nthOr(i32, &.{ 10, 20, 30 }, 1, 0); // 20
lo.nthOr(i32, &.{ 10, 20, 30 }, -1, 0); // 30
lo.nthOr(i32, &.{ 10, 20, 30 }, 5, 99); // 99All elements except the last. Empty slice if input is empty.
lo.initial(i32, &.{ 1, 2, 3 }); // &.{ 1, 2 }All elements except the first. Empty slice if input is empty.
lo.tail(i32, &.{ 1, 2, 3 }); // &.{ 2, 3 }Remove the first n elements, returning the rest as a sub-slice.
lo.drop(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 3, 4, 5 }Remove the last n elements, returning the rest as a sub-slice.
lo.dropRight(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 1, 2, 3 }Drop leading elements while the predicate returns true.
lo.dropWhile(i32, &.{ 1, 2, 3, 4 }, isLessThan3); // &.{ 3, 4 }Drop leading elements while the predicate returns true. Allocates a copy. Caller owns the returned slice.
const result = try lo.dropWhileAlloc(i32, allocator, &.{ 1, 2, 3, 4 }, isLessThan3);
defer allocator.free(result);
// result == &.{ 3, 4 }Drop trailing elements while the predicate returns true.
lo.dropRightWhile(i32, &.{ 1, 2, 3, 4 }, isGt2); // &.{ 1, 2 }Take the first n elements as a sub-slice.
lo.take(i32, &.{ 1, 2, 3, 4, 5 }, 3); // &.{ 1, 2, 3 }Take the last n elements as a sub-slice.
lo.takeRight(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 4, 5 }Take leading elements while the predicate returns true.
lo.takeWhile(i32, &.{ 1, 2, 3, 4 }, isLessThan3); // &.{ 1, 2 }Take leading elements while the predicate returns true. Allocates a copy. Caller owns the returned slice.
const result = try lo.takeWhileAlloc(i32, allocator, &.{ 1, 2, 3, 4 }, isLessThan3);
defer allocator.free(result);
// result == &.{ 1, 2 }Take trailing elements while the predicate returns true.
lo.takeRightWhile(i32, &.{ 1, 2, 3, 4 }, isGt2); // &.{ 3, 4 }Random element from a slice. Null if empty.
var prng = std.Random.DefaultPrng.init(0);
lo.sample(i32, &.{ 1, 2, 3 }, prng.random()); // random elementN random elements from a slice (with replacement). Caller owns the returned slice.
const s = try lo.samples(i32, allocator, &.{ 1, 2, 3 }, 5, rng);
defer allocator.free(s);Transform each element. Returns a lazy iterator.
var it = lo.map(i32, i64, &.{ 1, 2, 3 }, double);
it.next(); // 2Transform each element and collect into an allocated slice. Caller owns the returned slice.
const result = try lo.mapAlloc(i32, i32, allocator, &.{ 1, 2, 3 }, double);
defer allocator.free(result);Transform each element with its index. Returns a lazy iterator.
var it = lo.mapIndex(i32, i64, &.{ 10, 20 }, addIndex);
it.next(); // addIndex(10, 0)Keep elements matching the predicate. Returns a lazy iterator.
var it = lo.filter(i32, &.{ 1, 2, 3, 4 }, isEven);
it.next(); // 2
it.next(); // 4Keep elements matching the predicate, collected into an allocated slice. Caller owns the returned slice.
const result = try lo.filterAlloc(i32, allocator, &.{ 1, 2, 3, 4 }, isEven);
defer allocator.free(result);Remove elements matching the predicate. Returns a lazy iterator.
var it = lo.reject(i32, &.{ 1, 2, 3, 4 }, isEven);
it.next(); // 1
it.next(); // 3Remove elements matching the predicate, collected into an allocated slice. Caller owns the returned slice.
const result = try lo.rejectAlloc(i32, allocator, &.{ 1, 2, 3, 4 }, isEven);
defer allocator.free(result);Remove zero/null/default values. Returns a lazy iterator.
var it = lo.compact(?i32, &.{ 1, null, 3, null });
it.next(); // 1
it.next(); // 3Remove zero/null/default values into an allocated slice. Caller owns the returned slice.
const result = try lo.compactAlloc(?i32, allocator, &.{ 1, null, 3 });
defer allocator.free(result);Flatten a slice of slices into a single sequence. Returns a lazy iterator.
const data = [_][]const i32{ &.{ 1, 2 }, &.{ 3, 4 } };
var it = lo.flatten(i32, &data);
// yields 1, 2, 3, 4Flatten a slice of slices into an allocated slice. Counts total elements first, then allocates once. Caller owns the returned slice.
const data = [_][]const i32{ &.{ 1, 2 }, &.{ 3, 4, 5 } };
const result = try lo.flattenAlloc(i32, allocator, &data);
defer allocator.free(result);
// result == &.{ 1, 2, 3, 4, 5 }Flatten two levels of nesting ([][][]T to []T). Caller owns the returned slice.
const inner = [_][]const i32{ &.{ 1, 2 }, &.{ 3 } };
const outer = [_][]const []const i32{ &inner };
const result = try lo.flattenDeep(i32, allocator, &outer);
defer allocator.free(result);
// result == &.{ 1, 2, 3 }Map each element to a slice, then flatten into a single sequence. Returns a lazy iterator.
var it = lo.flatMap(i32, u8, &.{ 1, 2 }, toDigits);Map then flatten, collected into an allocated slice. Caller owns the returned slice.
const result = try lo.flatMapAlloc(i32, u8, allocator, &.{ 1, 2 }, toDigits);
defer allocator.free(result);Exclude specific values from a slice. Returns a lazy iterator.
var it = lo.without(i32, &.{ 1, 2, 3, 4 }, &.{ 2, 4 });
// yields 1, 3Invoke a function on each element.
lo.forEach(i32, &.{ 1, 2, 3 }, printFn);Invoke a function on each element with its index.
lo.forEachIndex(i32, &.{ 10, 20 }, printWithIndex);Filter and map in a single pass. The transform returns ?R; non-null values are collected into an allocated slice. Caller owns the returned slice.
const toEvenDoubled = struct {
fn f(x: i32) ?i32 {
if (@mod(x, 2) == 0) return x * 2;
return null;
}
}.f;
const result = try lo.compactMap(i32, i32, allocator, &.{ 1, 2, 3, 4 }, toEvenDoubled);
defer allocator.free(result);
// result == &.{ 4, 8 }Filter and map in a single step. Returns a lazy iterator.
var it = lo.filterMapIter(i32, i32, &.{ 1, 2, 3, 4 }, toEvenDoubled);
it.next(); // 4
it.next(); // 8
it.next(); // nullLeft fold with an accumulator.
lo.reduce(i32, i32, &.{ 1, 2, 3 }, addFn, 0); // 6Right fold with an accumulator. Processes elements right to left.
lo.reduceRight(i32, i32, &.{ 1, 2, 3 }, subtractFn, 0);Sum all elements in a slice. Returns 0 for empty slices.
lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10Sum elements after applying a transform function.
lo.sumBy(i32, i64, &.{ 1, 2, 3 }, double); // 12Multiply all elements in a slice. Returns 1 for empty slices.
lo.product(i32, &.{ 2, 3, 4 }); // 24Multiply elements after applying a transform function.
lo.productBy(i32, i64, &.{ 2, 3, 4 }, double); // 192Arithmetic mean of a slice. Returns null for empty slices.
lo.mean(i32, &.{ 2, 4, 6 }).?; // 4.0Arithmetic mean after applying a transform function.
const asF64 = struct { fn f(x: i32) f64 { return @floatFromInt(x); } }.f;
lo.meanBy(i32, &vals, asF64).?; // 20.0Returns the minimum value in a slice, or null if empty.
lo.min(i32, &.{ 3, 1, 2 }); // 1Returns the maximum value in a slice, or null if empty.
lo.max(i32, &.{ 3, 1, 2 }); // 3Returns the minimum element according to a comparator.
lo.minBy(Point, &points, Point.compareByX); // point with smallest xReturns the maximum element according to a comparator.
lo.maxBy(Point, &points, Point.compareByX); // point with largest xReturns both min and max in a single pass. Null if empty.
const mm = lo.minMax(i32, &.{ 5, 1, 9, 3 }).?;
// mm.min_val == 1, mm.max_val == 9Returns both min and max in a single pass according to a custom comparator. Null if empty.
const byX = struct { fn f(a: Point, b: Point) std.math.Order {
return std.math.order(a.x, b.x);
} }.f;
const mm = lo.minMaxBy(Point, &points, byX).?;
// mm.min_val and mm.max_valCount elements satisfying the predicate.
lo.count(i32, &.{ 1, 2, 3, 4 }, isEven); // 2Count elements by a key function. Returns a frequency map.
var m = try lo.countBy(i32, bool, allocator, &.{ 1, 2, 3, 4, 5 }, isEvenFn);
defer m.deinit();
m.get(true).?; // 2
m.get(false).?; // 3Build a frequency map: value -> number of occurrences. Caller owns the returned map.
var freq = try lo.countValues(i32, allocator, &.{ 1, 2, 2, 3 });
defer freq.deinit();
freq.get(2).?; // 2Returns the most frequently occurring value. Smallest value wins on ties. Null for empty slices.
const m = try lo.mode(i32, allocator, &.{ 1, 2, 2, 3, 2 });
// m == 2Returns the median of a numeric slice, or null if empty. Allocates a temporary copy for sorting.
const m = try lo.median(i32, allocator, &.{ 1, 2, 3, 4 });
// m == 2.5Population variance (N denominator). Returns null for empty slices.
lo.variance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 4.0Standard deviation (sqrt of population variance). Returns null for empty slices.
lo.stddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 2.0Sample variance with N-1 denominator (Bessel's correction). Returns null for slices with fewer than 2 elements.
lo.sampleVariance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // ~4.571Sample standard deviation (sqrt of sample variance). Returns null for slices with fewer than 2 elements.
lo.sampleStddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // ~2.138Returns the nth percentile using linear interpolation. Null for empty slices or if p is outside [0, 100].
const p = try lo.percentile(i32, allocator, &.{ 1, 2, 3, 4, 5 }, 50.0);
// p == 3.0Sort a slice in-place by a key extracted via a function. Stable sort.
var items = [_]i32{ 30, 10, 20 };
lo.sortBy(i32, i32, &items, struct {
fn f(x: i32) i32 { return x; }
}.f);
// items == { 10, 20, 30 }Returns a sorted copy without mutating the original. Caller owns the returned slice.
const sorted = try lo.sortByAlloc(i32, i32, allocator, &.{ 30, 10, 20 }, struct {
fn f(x: i32) i32 { return x; }
}.f);
defer allocator.free(sorted);
// sorted == { 10, 20, 30 }Sort a slice of structs in-place by a named field. Stable sort.
const Person = struct { name: []const u8, age: u32 };
var items = [_]Person{ .{ .name = "bob", .age = 30 }, .{ .name = "alice", .age = 25 } };
lo.sortByField(Person, &items, .age);
// items[0].age == 25, items[1].age == 30Returns a sorted copy of the slice sorted by a named struct field. Caller owns the returned slice.
const sorted = try lo.sortByFieldAlloc(Person, allocator, &people, .age);
defer allocator.free(sorted);Returns a sorted copy in natural ascending order. Caller owns the returned slice.
const sorted = try lo.toSortedAlloc(i32, allocator, &.{ 3, 1, 2 });
defer allocator.free(sorted);
// sorted == { 1, 2, 3 }True if the slice is sorted according to the comparator.
lo.isSorted(i32, &.{ 1, 2, 3 }, compareAsc); // trueElement-wise equality of two slices.
lo.equal(i32, &.{ 1, 2, 3 }, &.{ 1, 2, 3 }); // trueReverse a slice in-place.
var data = [_]i32{ 1, 2, 3 };
lo.reverse(i32, &data);
// data == .{ 3, 2, 1 }Fisher-Yates shuffle in-place.
var data = [_]i32{ 1, 2, 3, 4, 5 };
lo.shuffle(i32, &data, prng.random());Remove duplicate elements. Preserves first occurrence order.
const u = try lo.uniq(i32, allocator, &.{ 1, 2, 2, 3, 1 });
defer allocator.free(u);
// u == &.{ 1, 2, 3 }Remove duplicates by a key function. Preserves first occurrence order.
const u = try lo.uniqBy(Person, u32, allocator, &people, Person.id);
defer allocator.free(u);Elements present in both slices. Order follows the first slice.
const i = try lo.intersect(i32, allocator, &.{ 1, 2, 3 }, &.{ 2, 3, 4 });
defer allocator.free(i);
// i == &.{ 2, 3 }Unique elements from both slices combined.
const u = try lo.union_(i32, allocator, &.{ 1, 2, 3 }, &.{ 2, 3, 4 });
defer allocator.free(u);
// u == &.{ 1, 2, 3, 4 }Elements in the first slice but not in the second.
const d = try lo.difference(i32, allocator, &.{ 1, 2, 3 }, &.{ 2, 4 });
defer allocator.free(d);
// d == &.{ 1, 3 }Elements in either slice but not in both.
const sd = try lo.symmetricDifference(i32, allocator, &.{ 1, 2, 3 }, &.{ 2, 3, 4 });
defer allocator.free(sd);
// sd == &.{ 1, 4 }Find elements appearing more than once. Preserves first-occurrence order.
const dups = try lo.findDuplicates(i32, allocator, &.{ 1, 2, 2, 3, 3, 3 });
defer allocator.free(dups);
// dups == &.{ 2, 3 }Find elements appearing exactly once.
const uniques = try lo.findUniques(i32, allocator, &.{ 1, 2, 2, 3, 3, 3, 4 });
defer allocator.free(uniques);
// uniques == &.{ 1, 4 }True if two slices contain the same elements with the same multiplicities, regardless of order.
try lo.elementsMatch(i32, allocator, &.{ 1, 2, 3 }, &.{ 3, 2, 1 }); // true
try lo.elementsMatch(i32, allocator, &.{ 1, 1, 2 }, &.{ 1, 2, 2 }); // falseElements in the first slice but not in the second, using a custom equality predicate.
const absEq = struct { fn f(a: i32, b: i32) bool { return @abs(a) == @abs(b); } }.f;
const d = try lo.differenceWith(i32, allocator, &.{ 1, 2, 3 }, &.{ -2, 4 }, absEq);
defer allocator.free(d);
// d == &.{ 1, 3 }Elements present in both slices, using a custom equality predicate.
const i = try lo.intersectWith(i32, allocator, &.{ 1, -2, 3 }, &.{ 2, 4 }, absEq);
defer allocator.free(i);
// i == &.{ -2 }Unique elements from both slices combined, using a custom equality predicate.
const u = try lo.unionWith(i32, allocator, &.{ 1, 2 }, &.{ -2, 3 }, absEq);
defer allocator.free(u);
// u == &.{ 1, 2, 3 }Split a slice into two: elements matching the predicate and the rest.
const p = try lo.partition(i32, allocator, &.{ 1, 2, 3, 4 }, isEven);
defer p.deinit(allocator);
// p.matching == &.{ 2, 4 }, p.rest == &.{ 1, 3 }Group elements by a key function. Caller owns the returned map.
var groups = try lo.groupBy(i32, bool, allocator, &.{ 1, 2, 3, 4 }, isEvenFn);
defer {
var it = groups.valueIterator();
while (it.next()) |list| list.deinit(allocator);
groups.deinit();
}Split a slice into chunks of the given size. The last chunk may be smaller. Returns a lazy iterator.
var it = lo.chunk(i32, &.{ 1, 2, 3, 4, 5 }, 2);
it.next(); // &.{ 1, 2 }
it.next(); // &.{ 3, 4 }
it.next(); // &.{ 5 }Sliding window over a slice. Returns a lazy iterator. Windows borrow from the input (zero allocation).
var it = lo.window(i32, &.{ 1, 2, 3, 4, 5 }, 3);
it.next(); // &.{ 1, 2, 3 }
it.next(); // &.{ 2, 3, 4 }
it.next(); // &.{ 3, 4, 5 }
it.next(); // nullLike reduce but emits every intermediate result. Returns a lazy iterator.
const add = struct { fn f(a: i32, b: i32) i32 { return a + b; } }.f;
var it = lo.scan(i32, i32, &.{ 1, 2, 3 }, add, 0);
it.next(); // 1
it.next(); // 3
it.next(); // 6Eagerly compute all intermediate accumulator values into an allocated slice. Caller owns the returned slice.
const add = struct { fn f(a: i32, b: i32) i32 { return a + b; } }.f;
const result = try lo.scanAlloc(i32, i32, allocator, &.{ 1, 2, 3 }, add, 0);
defer allocator.free(result);
// result == &.{ 1, 3, 6 }Concatenate multiple slices into a single allocated slice. Caller owns the returned slice.
const result = try lo.concat(i32, allocator, &.{ &.{ 1, 2 }, &.{ 3, 4 }, &.{5} });
defer allocator.free(result);
// result == { 1, 2, 3, 4, 5 }Insert elements into a slice at a given index, returning a new allocated slice. Caller owns the returned slice.
const result = try lo.splice(i32, allocator, &.{ 1, 2, 5, 6 }, 2, &.{ 3, 4 });
defer allocator.free(result);
// result == { 1, 2, 3, 4, 5, 6 }Round-robin interleave multiple slices. Returns a lazy iterator.
var it = lo.interleave(i32, &.{ &.{ 1, 2, 3 }, &.{ 4, 5, 6 } });
it.next(); // 1
it.next(); // 4
it.next(); // 2
it.next(); // 5Fill all elements with the given value (in-place).
var data = [_]i32{ 0, 0, 0 };
lo.fill(i32, &data, 42);
// data == .{ 42, 42, 42 }Fill elements in the range [start, end) with the given value (in-place).
var data = [_]i32{ 1, 2, 3, 4, 5 };
lo.fillRange(i32, &data, 0, 1, 4);
// data == .{ 1, 0, 0, 0, 5 }Create a slice of n copies of a value. Caller owns the returned slice.
const r = try lo.repeat(i32, allocator, 42, 3);
defer allocator.free(r);
// r == &.{ 42, 42, 42 }Create a slice of n elements produced by a callback. Caller owns the returned slice.
const r = try lo.repeatBy(i32, allocator, 3, indexSquared);
defer allocator.free(r);
// r == &.{ 0, 1, 4 }Create a lazy iterator that calls a function N times with indices 0..N-1.
var iter = lo.times(usize, 4, square);
while (iter.next()) |val| { ... }Eagerly call a function N times and return the results. Caller owns the returned slice.
const squares = try lo.timesAlloc(usize, allocator, 4, square);
defer allocator.free(squares);
// squares == { 0, 1, 4, 9 }First element matching the predicate, or null.
lo.find(i32, &.{ 1, 2, 3, 4 }, isEven); // 2Index of the first element matching the predicate, or null.
lo.findIndex(i32, &.{ 1, 2, 3 }, isEven); // 1Last element matching the predicate, or null.
lo.findLast(i32, &.{ 1, 2, 3, 4 }, isEven); // 4Index of the last element matching the predicate, or null.
lo.findLastIndex(i32, &.{ 1, 2, 3, 4 }, isEven); // 3Index of the first occurrence of a value, or null.
lo.indexOf(i32, &.{ 10, 20, 30 }, 20); // 1Index of the last occurrence of a value, or null.
lo.lastIndexOf(i32, &.{ 1, 2, 3, 2 }, 2); // 3True if the slice contains the given value.
lo.contains(i32, &.{ 1, 2, 3 }, 2); // trueTrue if any element satisfies the predicate.
lo.containsBy(i32, &.{ 1, 2, 3 }, isEven); // trueTrue if all elements satisfy the predicate. True for empty slices.
lo.every(i32, &.{ 2, 4, 6 }, isEven); // trueTrue if at least one element satisfies the predicate. False for empty slices.
lo.some(i32, &.{ 1, 2, 3 }, isEven); // trueTrue if no elements satisfy the predicate. True for empty slices.
lo.none(i32, &.{ 1, 3, 5 }, isEven); // trueReturns the index of the minimum element, or null if empty. First occurrence on ties.
lo.minIndex(i32, &.{ 3, 1, 4, 1, 5 }); // 1Returns the index of the maximum element, or null if empty. First occurrence on ties.
lo.maxIndex(i32, &.{ 3, 1, 4, 1, 5 }); // 4Binary search for a target in a sorted ascending slice. Returns the index or null. O(log n).
lo.binarySearch(i32, &.{ 1, 3, 5, 7, 9 }, 5); // Some(2)
lo.binarySearch(i32, &.{ 1, 3, 5, 7, 9 }, 4); // nullIndex of the first element >= target in a sorted slice. Returns slice.len if all are less.
lo.lowerBound(i32, &.{ 1, 3, 5, 7 }, 4); // 2
lo.lowerBound(i32, &.{ 1, 3, 5, 7 }, 5); // 2
lo.lowerBound(i32, &.{ 1, 3, 5, 7 }, 9); // 4Index of the first element > target in a sorted slice. Returns slice.len if all are <= target.
lo.upperBound(i32, &.{ 1, 3, 5, 7 }, 3); // 2
lo.upperBound(i32, &.{ 1, 3, 5, 7 }, 4); // 2
lo.upperBound(i32, &.{ 1, 3, 5, 7 }, 9); // 4Insertion index to maintain sorted order. Equivalent to lowerBound.
lo.sortedIndex(i32, &.{ 1, 3, 5, 7 }, 4); // 2Insertion index after the last occurrence of a value. Equivalent to upperBound.
lo.sortedLastIndex(i32, &.{ 1, 3, 3, 5 }, 3); // 3Iterate over map keys. Returns a lazy iterator.
var it = lo.keys(u32, u8, &my_map);
while (it.next()) |key| { ... }Collect all keys into an allocated slice. Caller owns the returned slice.
const ks = try lo.keysAlloc(u32, u8, allocator, &my_map);
defer allocator.free(ks);Iterate over map values. Returns a lazy iterator.
var it = lo.values(u32, u8, &my_map);
while (it.next()) |val| { ... }Collect all values into an allocated slice. Caller owns the returned slice.
const vs = try lo.valuesAlloc(u32, u8, allocator, &my_map);
defer allocator.free(vs);Iterate over key-value pairs. Returns a lazy iterator.
var it = lo.entries(u32, u8, &my_map);
while (it.next()) |e| { _ = e.key; _ = e.value; }Collect all key-value pairs into an allocated slice. Caller owns the returned slice.
const es = try lo.entriesAlloc(u32, u8, allocator, &my_map);
defer allocator.free(es);Build a map from a slice of key-value pairs. Caller owns the returned map.
const pairs = [_]lo.Entry(u32, u8){ .{ .key = 1, .value = 'a' } };
var m = try lo.fromEntries(u32, u8, allocator, &pairs);
defer m.deinit();Transform map keys using a function. Caller owns the returned map.
var result = try lo.mapKeys(u32, u8, u64, allocator, &m, timesTwo);
defer result.deinit();Transform map values using a function. Caller owns the returned map.
var result = try lo.mapValues(u32, u8, u16, allocator, &m, multiply);
defer result.deinit();Filter map entries by a predicate on key and value. Caller owns the returned map.
var result = try lo.filterMap(u32, u8, allocator, &m, keyGt1);
defer result.deinit();Filter map entries by a predicate on the key. Caller owns the returned map.
var result = try lo.filterKeys(u32, u8, allocator, &m, isEven);
defer result.deinit();Filter map entries by a predicate on the value. Caller owns the returned map.
var result = try lo.filterValues(u32, u8, allocator, &m, isPositive);
defer result.deinit();Keep only entries with the specified keys. Caller owns the returned map.
var result = try lo.pickKeys(u32, u8, allocator, &m, &.{ 1, 3 });
defer result.deinit();Remove entries with the specified keys. Caller owns the returned map.
var result = try lo.omitKeys(u32, u8, allocator, &m, &.{ 2, 3 });
defer result.deinit();Swap keys and values. Caller owns the returned map.
var result = try lo.invert(u32, u8, allocator, &m);
defer result.deinit();Merge entries from source into dest. Source values overwrite on conflict.
try lo.merge(u32, u8, &dest, &source);Merge N maps into one with last-write-wins semantics. Caller owns the returned map.
var result = try lo.assign(u32, u8, allocator, &.{ &m1, &m2 });
defer result.deinit();Transform both keys and values of a map. Caller owns the returned map.
var result = try lo.mapEntries(u32, u8, u64, u16, allocator, &m, xform);
defer result.deinit();Transform map entries into an allocated slice. Caller owns the returned slice.
const result = try lo.mapToSlice(u32, u8, u64, allocator, &m, sumKeyVal);
defer allocator.free(result);Get a value from the map, or return a default if the key is absent.
lo.valueOr(u32, u8, &my_map, 999, 0); // 0 if 999 not in mapTrue if the map contains the given key.
lo.hasKey(u32, u8, &m, 1); // trueNumber of entries in the map.
lo.mapCount(u32, u8, &m); // 3Convert a slice to a map indexed by an extracted key. Last element wins on duplicate keys.
var m = try lo.keyBy(Person, u32, allocator, &people, getAge);
defer m.deinit();Convert a slice to a map with custom key and value extraction. Last element wins on duplicate keys.
var m = try lo.associate(Person, u32, []const u8, allocator, &people, toEntry);
defer m.deinit();Split a string into words at camelCase, PascalCase, snake_case, kebab-case, and whitespace boundaries. Returns a lazy iterator.
var it = lo.words("helloWorld");
it.next(); // "hello"
it.next(); // "World"Split a string into words, collected into an allocated slice. Caller owns the returned slice.
const ws = try lo.wordsAlloc(allocator, "camelCase");
defer allocator.free(ws);
// ws: &.{ "camel", "Case" }Convert a string to camelCase. Caller owns the returned string.
const s = try lo.camelCase(allocator, "hello_world");
defer allocator.free(s);
// s == "helloWorld"Convert a string to PascalCase. Caller owns the returned string.
const s = try lo.pascalCase(allocator, "hello_world");
defer allocator.free(s);
// s == "HelloWorld"Convert a string to snake_case. Caller owns the returned string.
const s = try lo.snakeCase(allocator, "helloWorld");
defer allocator.free(s);
// s == "hello_world"Convert a string to kebab-case. Caller owns the returned string.
const s = try lo.kebabCase(allocator, "helloWorld");
defer allocator.free(s);
// s == "hello-world"Capitalize the first letter of a string. Caller owns the returned string.
const s = try lo.capitalize(allocator, "hello");
defer allocator.free(s);
// s == "Hello"Lowercase just the first character (ASCII). Caller owns the returned string.
const s = try lo.lowerFirst(allocator, "Hello");
defer allocator.free(s);
// s == "hello"Convert an entire string to lowercase (ASCII). Caller owns the returned string.
const s = try lo.toLower(allocator, "Hello World");
defer allocator.free(s);
// s == "hello world"Convert an entire string to uppercase (ASCII). Caller owns the returned string.
const s = try lo.toUpper(allocator, "Hello World");
defer allocator.free(s);
// s == "HELLO WORLD"Trim whitespace from both ends of a string. Returns a sub-slice (zero-copy).
lo.trim(" hello "); // "hello"Trim whitespace from the start of a string. Returns a sub-slice (zero-copy).
lo.trimStart(" hello "); // "hello "Trim whitespace from the end of a string. Returns a sub-slice (zero-copy).
lo.trimEnd(" hello "); // " hello"Check if a string starts with a given prefix.
lo.startsWith("hello world", "hello"); // trueCheck if a string ends with a given suffix.
lo.endsWith("hello world", "world"); // trueCheck if a string contains a substring.
lo.includes("hello world", "world"); // trueExtract a substring by start and end byte indices. Indices are clamped. Returns a sub-slice (zero-copy).
lo.substr("hello", 2, 5); // "llo"Truncate a string and add "..." if it exceeds max_len. Caller owns the returned string.
const s = try lo.ellipsis(allocator, "hello world", 8);
defer allocator.free(s);
// s == "hello..."Repeat a string n times. Caller owns the returned string.
const s = try lo.strRepeat(allocator, "ab", 3);
defer allocator.free(s);
// s == "ababab"Left-pad a string to the given length. Caller owns the returned string.
const s = try lo.padLeft(allocator, "42", 5, '0');
defer allocator.free(s);
// s == "00042"Right-pad a string to the given length. Caller owns the returned string.
const s = try lo.padRight(allocator, "hi", 5, '.');
defer allocator.free(s);
// s == "hi..."Count the number of Unicode codepoints in a UTF-8 string. Returns error.InvalidUtf8 for invalid input.
try lo.runeLength("hello"); // 5
try lo.runeLength("\xc3\xa9"); // 1Generate a random alphanumeric string. Caller owns the returned string.
const s = try lo.randomString(allocator, 10, prng.random());
defer allocator.free(s);Split a string by a delimiter sequence. Returns a lazy iterator. Preserves empty tokens.
var it = lo.split("one,two,,four", ",");
it.next(); // "one"
it.next(); // "two"
it.next(); // ""
it.next(); // "four"Split a string by a delimiter, collected into an allocated slice. Caller owns the returned outer slice.
const parts = try lo.splitAlloc(allocator, "a-b-c", "-");
defer allocator.free(parts);
// parts[0] == "a", parts[1] == "b", parts[2] == "c"Join a slice of strings with a separator. Caller owns the returned string.
const s = try lo.join(allocator, ", ", &.{ "hello", "world" });
defer allocator.free(s);
// s == "hello, world"Replace the first occurrence of a needle. Caller owns the returned string.
const s = try lo.replace(allocator, "hello hello", "hello", "hi");
defer allocator.free(s);
// s == "hi hello"Replace all occurrences of a needle. Caller owns the returned string.
const s = try lo.replaceAll(allocator, "hello hello", "hello", "hi");
defer allocator.free(s);
// s == "hi hi"Split a string into fixed-size byte chunks. Returns a lazy iterator. The last chunk may be smaller.
var it = lo.chunkString("abcdefgh", 3);
it.next(); // "abc"
it.next(); // "def"
it.next(); // "gh"Sum all elements. Returns 0 for empty slices.
lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10Arithmetic mean. Returns null for empty slices.
lo.mean(i32, &.{ 2, 4, 6 }).?; // 4.0Median value. Allocates a temporary sorted copy. Null for empty slices.
const m = try lo.median(i32, allocator, &.{ 1, 2, 3, 4 });
// m == 2.5Population variance (N denominator). Null for empty slices.
lo.variance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 4.0Standard deviation (sqrt of population variance). Null for empty slices.
lo.stddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 2.0Sample variance with N-1 denominator (Bessel's correction). Null for slices with fewer than 2 elements.
lo.sampleVariance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // ~4.571Sample standard deviation (sqrt of sample variance). Null for slices with fewer than 2 elements.
lo.sampleStddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // ~2.138Nth percentile using linear interpolation. Null for empty slices or p outside [0, 100].
const p = try lo.percentile(i32, allocator, &.{ 1, 2, 3, 4, 5 }, 50.0);
// p == 3.0Linear interpolation between two values. Float-only.
lo.lerp(f64, 0.0, 10.0, 0.5); // 5.0Remap a value from one range to another. Float-only.
lo.remap(f64, 5.0, 0.0, 10.0, 0.0, 100.0); // 50.0Clamp a value to the range [lo, hi].
lo.clamp(i32, 15, 0, 10); // 10
lo.clamp(i32, -5, 0, 10); // 0
lo.clamp(i32, 5, 0, 10); // 5Check if a value falls within the half-open range [start, end). Returns false if start >= end.
lo.inRange(i32, 3, 1, 5); // true
lo.inRange(i32, 5, 1, 5); // false (end is exclusive)Cumulative sum. Caller owns the returned slice.
const result = try lo.cumSum(i32, allocator, &.{ 1, 2, 3, 4 });
defer allocator.free(result);
// result == { 1, 3, 6, 10 }Cumulative product. Caller owns the returned slice.
const result = try lo.cumProd(i32, allocator, &.{ 1, 2, 3, 4 });
defer allocator.free(result);
// result == { 1, 2, 6, 24 }Allocate a slice containing integers in [start, end). Caller owns the returned slice.
const r = try lo.rangeAlloc(i32, allocator, 0, 5);
defer allocator.free(r);
// r == .{ 0, 1, 2, 3, 4 }Allocate a range with a custom step. Returns error.InvalidArgument for step 0. Caller owns the returned slice.
const r = try lo.rangeWithStepAlloc(i32, allocator, 0, 10, 3);
defer allocator.free(r);
// r == .{ 0, 3, 6, 9 }Pair elements from two slices. Returns a lazy iterator. Stops at the shorter slice.
var it = lo.zip(i32, u8, &.{ 1, 2 }, &.{ 'a', 'b' });
it.next(); // .{ .a = 1, .b = 'a' }Pair elements from two slices into an allocated slice. Caller owns the returned slice.
const pairs = try lo.zipAlloc(i32, u8, allocator, &.{ 1, 2 }, &.{ 'a', 'b' });
defer allocator.free(pairs);Zip two slices with a transform function. Returns a lazy iterator.
var it = lo.zipWith(i32, i32, i32, &.{ 1, 2 }, &.{ 3, 4 }, addFn);
it.next(); // 4
it.next(); // 6Split a slice of pairs into two separate slices. Call deinit(allocator) to free.
const r = try lo.unzip(i32, u8, allocator, &pairs);
defer r.deinit(allocator);Pair each element with its index. Returns a lazy iterator.
var it = lo.enumerate(i32, &.{ 10, 20, 30 });
it.next(); // .{ .a = 0, .b = 10 }
it.next(); // .{ .a = 1, .b = 20 }Returns true if the optional value is null.
const x: ?i32 = null;
lo.isNull(i32, x); // trueReturns true if the optional value is non-null.
const x: ?i32 = 42;
lo.isNotNull(i32, x); // trueUnwrap an optional, returning the fallback if null.
const x: ?i32 = null;
lo.unwrapOr(i32, x, 99); // 99Returns the first non-null value from a slice of optionals, or null if all are null.
const vals = [_]?i32{ null, null, 42, 7 };
lo.coalesce(i32, &vals); // 42Returns the zero/default value for a type.
lo.empty(i32); // 0
lo.empty(bool); // falseReturns true if the value equals the zero/default for its type.
lo.isEmpty(i32, 0); // true
lo.isEmpty(i32, 42); // falseReturns true if the value does not equal the zero/default for its type.
lo.isNotEmpty(i32, 42); // true
lo.isNotEmpty(i32, 0); // falseSelects one of two values based on a boolean condition. Both branches are evaluated eagerly.
lo.ternary(i32, true, 10, 20); // 10
lo.ternary(i32, false, 10, 20); // 20Convert a mutable slice to a const slice.
var buf = [_]i32{ 1, 2, 3 };
const view = lo.toConst(i32, &buf);These are type constructors and result types used by the functions above.
Generic key-value pair for map utilities.
const e = lo.Entry(u32, u8){ .key = 1, .value = 'a' };Generic pair type used by zip operations.
const p = lo.Pair(i32, u8){ .a = 42, .b = 'z' };Result type returned by minMax(), holding .min_val and .max_val.
Error set for rangeWithStepAlloc. Includes InvalidArgument for zero step.
Result type from partition() holding .matching and .rest slices. Call deinit(allocator) to free.
Result type from unzip() holding .a and .b slices. Call deinit(allocator) to free.
Generic key-value entry type used by associate.
const entry = lo.AssocEntry([]const u8, u32){ .key = "alice", .value = 30 };The following iterator types are returned by their corresponding functions. They all implement a next() -> ?T method, and most provide a collect(allocator) -> ![]T method for eager evaluation.
| Iterator | Returned by |
|---|---|
MapIterator |
map() |
MapIndexIterator |
mapIndex() |
FilterIterator |
filter() |
RejectIterator |
reject() |
FlattenIterator |
flatten() |
FlatMapIterator |
flatMap() |
CompactIterator |
compact() |
ChunkIterator |
chunk() |
FilterMapIterator |
filterMapIter() |
WithoutIterator |
without() |
ScanIterator |
scan() |
WindowIterator |
window() |
InterleaveIterator |
interleave() |
TimesIterator |
times() |
KeyIterator |
keys() |
ValueIterator |
values() |
EntryIterator |
entries() |
ZipIterator |
zip() |
ZipWithIterator |
zipWith() |
EnumerateIterator |
enumerate() |
WordIterator |
words() |
StringChunkIterator |
chunkString() |
See LICENSE.
