Skip to content

OrlovEvgeny/lo.zig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI Release Zig

lo.zig is a Lodash-style Zig library

lo.zig

Generic utility library for Zig

Zero hidden allocations: functions that need memory take an Allocator. Iterator-first: most transformations return lazy iterators.

Installation

Add lo.zig as a dependency in your build.zig.zon:

zig fetch --save git+https://github.com/OrlovEvgeny/lo.zig

Then in your build.zig:

const lo_dep = b.dependency("lo", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("lo", lo_dep.module("lo"));

Quick Start

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

Function Index

  • 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

Slice Helpers

first

Returns the first element of a slice, or null if empty.

lo.first(i32, &.{ 10, 20, 30 }); // 10

last

Returns the last element of a slice, or null if empty.

lo.last(i32, &.{ 10, 20, 30 }); // 30

nth

Element at the given index. Negative indices count from the end. Returns null if out of bounds.

lo.nth(i32, &.{ 10, 20, 30 }, -1); // 30

firstOr

Returns the first element, or a default if the slice is empty.

lo.firstOr(i32, &.{ 10, 20, 30 }, 0); // 10
lo.firstOr(i32, &.{}, 42);             // 42

lastOr

Returns the last element, or a default if the slice is empty.

lo.lastOr(i32, &.{ 10, 20, 30 }, 0); // 30
lo.lastOr(i32, &.{}, 42);             // 42

nthOr

Element 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); // 99

initial

All elements except the last. Empty slice if input is empty.

lo.initial(i32, &.{ 1, 2, 3 }); // &.{ 1, 2 }

tail

All elements except the first. Empty slice if input is empty.

lo.tail(i32, &.{ 1, 2, 3 }); // &.{ 2, 3 }

drop

Remove the first n elements, returning the rest as a sub-slice.

lo.drop(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 3, 4, 5 }

dropRight

Remove the last n elements, returning the rest as a sub-slice.

lo.dropRight(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 1, 2, 3 }

dropWhile

Drop leading elements while the predicate returns true.

lo.dropWhile(i32, &.{ 1, 2, 3, 4 }, isLessThan3); // &.{ 3, 4 }

dropWhileAlloc

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 }

dropRightWhile

Drop trailing elements while the predicate returns true.

lo.dropRightWhile(i32, &.{ 1, 2, 3, 4 }, isGt2); // &.{ 1, 2 }

take

Take the first n elements as a sub-slice.

lo.take(i32, &.{ 1, 2, 3, 4, 5 }, 3); // &.{ 1, 2, 3 }

takeRight

Take the last n elements as a sub-slice.

lo.takeRight(i32, &.{ 1, 2, 3, 4, 5 }, 2); // &.{ 4, 5 }

takeWhile

Take leading elements while the predicate returns true.

lo.takeWhile(i32, &.{ 1, 2, 3, 4 }, isLessThan3); // &.{ 1, 2 }

takeWhileAlloc

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 }

takeRightWhile

Take trailing elements while the predicate returns true.

lo.takeRightWhile(i32, &.{ 1, 2, 3, 4 }, isGt2); // &.{ 3, 4 }

sample

Random element from a slice. Null if empty.

var prng = std.Random.DefaultPrng.init(0);
lo.sample(i32, &.{ 1, 2, 3 }, prng.random()); // random element

samples

N 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

map

Transform each element. Returns a lazy iterator.

var it = lo.map(i32, i64, &.{ 1, 2, 3 }, double);
it.next(); // 2

mapAlloc

Transform 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);

mapIndex

Transform each element with its index. Returns a lazy iterator.

var it = lo.mapIndex(i32, i64, &.{ 10, 20 }, addIndex);
it.next(); // addIndex(10, 0)

filter

Keep elements matching the predicate. Returns a lazy iterator.

var it = lo.filter(i32, &.{ 1, 2, 3, 4 }, isEven);
it.next(); // 2
it.next(); // 4

filterAlloc

Keep 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);

reject

Remove elements matching the predicate. Returns a lazy iterator.

var it = lo.reject(i32, &.{ 1, 2, 3, 4 }, isEven);
it.next(); // 1
it.next(); // 3

rejectAlloc

Remove 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);

compact

Remove zero/null/default values. Returns a lazy iterator.

var it = lo.compact(?i32, &.{ 1, null, 3, null });
it.next(); // 1
it.next(); // 3

compactAlloc

Remove 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

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, 4

flattenAlloc

Flatten 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 }

flattenDeep

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 }

flatMap

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);

flatMapAlloc

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);

without

Exclude specific values from a slice. Returns a lazy iterator.

var it = lo.without(i32, &.{ 1, 2, 3, 4 }, &.{ 2, 4 });
// yields 1, 3

forEach

Invoke a function on each element.

lo.forEach(i32, &.{ 1, 2, 3 }, printFn);

forEachIndex

Invoke a function on each element with its index.

lo.forEachIndex(i32, &.{ 10, 20 }, printWithIndex);

compactMap

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 }

filterMapIter

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(); // null

Aggregate

reduce

Left fold with an accumulator.

lo.reduce(i32, i32, &.{ 1, 2, 3 }, addFn, 0); // 6

reduceRight

Right fold with an accumulator. Processes elements right to left.

lo.reduceRight(i32, i32, &.{ 1, 2, 3 }, subtractFn, 0);

sum

Sum all elements in a slice. Returns 0 for empty slices.

lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10

sumBy

Sum elements after applying a transform function.

lo.sumBy(i32, i64, &.{ 1, 2, 3 }, double); // 12

product

Multiply all elements in a slice. Returns 1 for empty slices.

lo.product(i32, &.{ 2, 3, 4 }); // 24

productBy

Multiply elements after applying a transform function.

lo.productBy(i32, i64, &.{ 2, 3, 4 }, double); // 192

mean

Arithmetic mean of a slice. Returns null for empty slices.

lo.mean(i32, &.{ 2, 4, 6 }).?; // 4.0

meanBy

Arithmetic mean after applying a transform function.

const asF64 = struct { fn f(x: i32) f64 { return @floatFromInt(x); } }.f;
lo.meanBy(i32, &vals, asF64).?; // 20.0

min

Returns the minimum value in a slice, or null if empty.

lo.min(i32, &.{ 3, 1, 2 }); // 1

max

Returns the maximum value in a slice, or null if empty.

lo.max(i32, &.{ 3, 1, 2 }); // 3

minBy

Returns the minimum element according to a comparator.

lo.minBy(Point, &points, Point.compareByX); // point with smallest x

maxBy

Returns the maximum element according to a comparator.

lo.maxBy(Point, &points, Point.compareByX); // point with largest x

minMax

Returns 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 == 9

minMaxBy

Returns 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_val

count

Count elements satisfying the predicate.

lo.count(i32, &.{ 1, 2, 3, 4 }, isEven); // 2

countBy

Count 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).?; // 3

countValues

Build 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).?; // 2

mode

Returns 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 == 2

median

Returns 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.5

variance

Population variance (N denominator). Returns null for empty slices.

lo.variance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 4.0

stddev

Standard deviation (sqrt of population variance). Returns null for empty slices.

lo.stddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 2.0

sampleVariance

Sample 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.571

sampleStddev

Sample 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.138

percentile

Returns 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.0

Sort & Order

sortBy

Sort 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 }

sortByAlloc

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 }

sortByField

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 == 30

sortByFieldAlloc

Returns 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);

toSortedAlloc

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 }

isSorted

True if the slice is sorted according to the comparator.

lo.isSorted(i32, &.{ 1, 2, 3 }, compareAsc); // true

equal

Element-wise equality of two slices.

lo.equal(i32, &.{ 1, 2, 3 }, &.{ 1, 2, 3 }); // true

reverse

Reverse a slice in-place.

var data = [_]i32{ 1, 2, 3 };
lo.reverse(i32, &data);
// data == .{ 3, 2, 1 }

shuffle

Fisher-Yates shuffle in-place.

var data = [_]i32{ 1, 2, 3, 4, 5 };
lo.shuffle(i32, &data, prng.random());

Set Operations

uniq

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 }

uniqBy

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);

intersect

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 }

union_

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 }

difference

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 }

symmetricDifference

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 }

findDuplicates

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 }

findUniques

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 }

elementsMatch

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 }); // false

differenceWith

Elements 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 }

intersectWith

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 }

unionWith

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 }

Partition & Group

partition

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 }

groupBy

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();
}

chunk

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 }

window

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(); // null

scan

Like 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(); // 6

scanAlloc

Eagerly 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 }

Combine

concat

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 }

splice

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 }

interleave

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(); // 5

fill

Fill all elements with the given value (in-place).

var data = [_]i32{ 0, 0, 0 };
lo.fill(i32, &data, 42);
// data == .{ 42, 42, 42 }

fillRange

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 }

repeat

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 }

repeatBy

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 }

times

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| { ... }

timesAlloc

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 }

Search

find

First element matching the predicate, or null.

lo.find(i32, &.{ 1, 2, 3, 4 }, isEven); // 2

findIndex

Index of the first element matching the predicate, or null.

lo.findIndex(i32, &.{ 1, 2, 3 }, isEven); // 1

findLast

Last element matching the predicate, or null.

lo.findLast(i32, &.{ 1, 2, 3, 4 }, isEven); // 4

findLastIndex

Index of the last element matching the predicate, or null.

lo.findLastIndex(i32, &.{ 1, 2, 3, 4 }, isEven); // 3

indexOf

Index of the first occurrence of a value, or null.

lo.indexOf(i32, &.{ 10, 20, 30 }, 20); // 1

lastIndexOf

Index of the last occurrence of a value, or null.

lo.lastIndexOf(i32, &.{ 1, 2, 3, 2 }, 2); // 3

contains

True if the slice contains the given value.

lo.contains(i32, &.{ 1, 2, 3 }, 2); // true

containsBy

True if any element satisfies the predicate.

lo.containsBy(i32, &.{ 1, 2, 3 }, isEven); // true

every

True if all elements satisfy the predicate. True for empty slices.

lo.every(i32, &.{ 2, 4, 6 }, isEven); // true

some

True if at least one element satisfies the predicate. False for empty slices.

lo.some(i32, &.{ 1, 2, 3 }, isEven); // true

none

True if no elements satisfy the predicate. True for empty slices.

lo.none(i32, &.{ 1, 3, 5 }, isEven); // true

minIndex

Returns the index of the minimum element, or null if empty. First occurrence on ties.

lo.minIndex(i32, &.{ 3, 1, 4, 1, 5 }); // 1

maxIndex

Returns the index of the maximum element, or null if empty. First occurrence on ties.

lo.maxIndex(i32, &.{ 3, 1, 4, 1, 5 }); // 4

binarySearch

Binary 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); // null

lowerBound

Index 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); // 4

upperBound

Index 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); // 4

sortedIndex

Insertion index to maintain sorted order. Equivalent to lowerBound.

lo.sortedIndex(i32, &.{ 1, 3, 5, 7 }, 4); // 2

sortedLastIndex

Insertion index after the last occurrence of a value. Equivalent to upperBound.

lo.sortedLastIndex(i32, &.{ 1, 3, 3, 5 }, 3); // 3

Map Helpers

keys

Iterate over map keys. Returns a lazy iterator.

var it = lo.keys(u32, u8, &my_map);
while (it.next()) |key| { ... }

keysAlloc

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);

values

Iterate over map values. Returns a lazy iterator.

var it = lo.values(u32, u8, &my_map);
while (it.next()) |val| { ... }

valuesAlloc

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);

entries

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; }

entriesAlloc

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);

fromEntries

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();

mapKeys

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();

mapValues

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();

filterMap

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();

filterKeys

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();

filterValues

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();

pickKeys

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();

omitKeys

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();

invert

Swap keys and values. Caller owns the returned map.

var result = try lo.invert(u32, u8, allocator, &m);
defer result.deinit();

merge

Merge entries from source into dest. Source values overwrite on conflict.

try lo.merge(u32, u8, &dest, &source);

assign

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();

mapEntries

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();

mapToSlice

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);

valueOr

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 map

hasKey

True if the map contains the given key.

lo.hasKey(u32, u8, &m, 1); // true

mapCount

Number of entries in the map.

lo.mapCount(u32, u8, &m); // 3

keyBy

Convert 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();

associate

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();

String Helpers

words

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"

wordsAlloc

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" }

camelCase

Convert a string to camelCase. Caller owns the returned string.

const s = try lo.camelCase(allocator, "hello_world");
defer allocator.free(s);
// s == "helloWorld"

pascalCase

Convert a string to PascalCase. Caller owns the returned string.

const s = try lo.pascalCase(allocator, "hello_world");
defer allocator.free(s);
// s == "HelloWorld"

snakeCase

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"

kebabCase

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

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"

lowerFirst

Lowercase just the first character (ASCII). Caller owns the returned string.

const s = try lo.lowerFirst(allocator, "Hello");
defer allocator.free(s);
// s == "hello"

toLower

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"

toUpper

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

Trim whitespace from both ends of a string. Returns a sub-slice (zero-copy).

lo.trim("  hello  "); // "hello"

trimStart

Trim whitespace from the start of a string. Returns a sub-slice (zero-copy).

lo.trimStart("  hello  "); // "hello  "

trimEnd

Trim whitespace from the end of a string. Returns a sub-slice (zero-copy).

lo.trimEnd("  hello  "); // "  hello"

startsWith

Check if a string starts with a given prefix.

lo.startsWith("hello world", "hello"); // true

endsWith

Check if a string ends with a given suffix.

lo.endsWith("hello world", "world"); // true

includes

Check if a string contains a substring.

lo.includes("hello world", "world"); // true

substr

Extract a substring by start and end byte indices. Indices are clamped. Returns a sub-slice (zero-copy).

lo.substr("hello", 2, 5); // "llo"

ellipsis

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..."

strRepeat

Repeat a string n times. Caller owns the returned string.

const s = try lo.strRepeat(allocator, "ab", 3);
defer allocator.free(s);
// s == "ababab"

padLeft

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"

padRight

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..."

runeLength

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");  // 1

randomString

Generate a random alphanumeric string. Caller owns the returned string.

const s = try lo.randomString(allocator, 10, prng.random());
defer allocator.free(s);

split

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"

splitAlloc

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

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

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"

replaceAll

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"

chunkString

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"

Math

sum

Sum all elements. Returns 0 for empty slices.

lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10

mean

Arithmetic mean. Returns null for empty slices.

lo.mean(i32, &.{ 2, 4, 6 }).?; // 4.0

median

Median value. Allocates a temporary sorted copy. Null for empty slices.

const m = try lo.median(i32, allocator, &.{ 1, 2, 3, 4 });
// m == 2.5

variance

Population variance (N denominator). Null for empty slices.

lo.variance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 4.0

stddev

Standard deviation (sqrt of population variance). Null for empty slices.

lo.stddev(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 2.0

sampleVariance

Sample 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.571

sampleStddev

Sample 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.138

percentile

Nth 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.0

lerp

Linear interpolation between two values. Float-only.

lo.lerp(f64, 0.0, 10.0, 0.5); // 5.0

remap

Remap a value from one range to another. Float-only.

lo.remap(f64, 5.0, 0.0, 10.0, 0.0, 100.0); // 50.0

clamp

Clamp 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);  // 5

inRange

Check 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)

cumSum

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 }

cumProd

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 }

rangeAlloc

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 }

rangeWithStepAlloc

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 }

Tuple Helpers

zip

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' }

zipAlloc

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);

zipWith

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(); // 6

unzip

Split 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);

enumerate

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 }

Type Helpers

isNull

Returns true if the optional value is null.

const x: ?i32 = null;
lo.isNull(i32, x); // true

isNotNull

Returns true if the optional value is non-null.

const x: ?i32 = 42;
lo.isNotNull(i32, x); // true

unwrapOr

Unwrap an optional, returning the fallback if null.

const x: ?i32 = null;
lo.unwrapOr(i32, x, 99); // 99

coalesce

Returns 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); // 42

empty

Returns the zero/default value for a type.

lo.empty(i32);  // 0
lo.empty(bool); // false

isEmpty

Returns true if the value equals the zero/default for its type.

lo.isEmpty(i32, 0);   // true
lo.isEmpty(i32, 42);  // false

isNotEmpty

Returns true if the value does not equal the zero/default for its type.

lo.isNotEmpty(i32, 42); // true
lo.isNotEmpty(i32, 0);  // false

ternary

Selects 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); // 20

toConst

Convert a mutable slice to a const slice.

var buf = [_]i32{ 1, 2, 3 };
const view = lo.toConst(i32, &buf);

Types

These are type constructors and result types used by the functions above.

Entry

Generic key-value pair for map utilities.

const e = lo.Entry(u32, u8){ .key = 1, .value = 'a' };

Pair

Generic pair type used by zip operations.

const p = lo.Pair(i32, u8){ .a = 42, .b = 'z' };

MinMax

Result type returned by minMax(), holding .min_val and .max_val.

RangeError

Error set for rangeWithStepAlloc. Includes InvalidArgument for zero step.

PartitionResult

Result type from partition() holding .matching and .rest slices. Call deinit(allocator) to free.

UnzipResult

Result type from unzip() holding .a and .b slices. Call deinit(allocator) to free.

AssocEntry

Generic key-value entry type used by associate.

const entry = lo.AssocEntry([]const u8, u32){ .key = "alice", .value = 30 };

Iterator Types

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()

License

See LICENSE.

About

A Lodash-style Zig library

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages