Skip to content

Generalize `map`: make it lazy, filtering, flattening, ...

License

Notifications You must be signed in to change notification settings

JuliaAPlavin/FlexiMaps.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FlexiMaps.jl

All-familiar map on steroids: a set of functions that generalize map.

filtermap

filtermap(f, X): map and filter a collection in one go. Most useful when the mapped function shares some computations with the filter predicate.

Returns same as map(f, X), dropping elements where f(x) is nothing. Return Some(nothing) from f to keep nothing in the result.

filtermap(x -> x % 3 == 0 ? x^2 : nothing, 1:10) == [9, 36, 81]

Analogous to filter_map in Rust

flatmap/flatten

These functions are similar to Iterators.flatmap and Iterators.flatten, but operate on arrays in a more performant and generic manner.

flatmap(f, X): apply f to all elements of X and flatten the result by concatenating all f(x) collections.

flatmap(fₒᵤₜ, fᵢₙ, X): apply fₒᵤₜ to all elements of X, and apply fᵢₙ to the results. Basically, [fᵢₙ(x, y) for x in X for y in fₒᵤₜ(x)].

flatmap(f, X) is similar to mapreduce(f, vcat, X) and SplitApplyCombine.mapmany(f, A), but more efficient and generic.

Defining differences include:

  • better result type inference
  • keeps array types, eg StructArray
  • works with empty collections
  • supports arbitrary iterators, not only arrays

Analogous to flat_map in Rust, and SelectMany in C#

flatten(X): flatten a collection of collections by concatenating all elements, equivalent to flatmap(identity, X).

mapview (lazy map)

mapview(f, X):

  • like map(f, X), but works lazily, doesn't materialize the result returning a view instead.
  • like Iterators.map(f, X), but with better collection support, type stability, etc.

Works on different collections and arbitrary iterables. Collection types are preserved when possible for ranges, arrays, dictionaires. Passes length, keys and others directly to the parent. Does its best to determine the resulting eltype without evaluating f. Supports both getting and setting values (through Accessors.jl).

X = [1, 2, 3]
mapview(x -> x + 1, X) == [2, 3, 4]  # a view of X, doesn't take extra memory

X = Dict(:a => 1, :b => 2, :c => 3)
mapview(x -> x + 1, X) == Dict(:a => 2, :b => 3, :c => 4)  # same with Dict

X = [1, 2, 3]
mapview(x -> x + 1, (x for x in X))  # and with iterator
julia> X = [1, 2, 3.]

julia> Y = mapview(exp10, X)
3-element FlexiMaps.MappedArray{Float64, 1, typeof(exp10), Vector{Float64}}:
   10.0
  100.0
 1000.0

# setindex! works for all functions/optics supported by Accessors
julia> Y[2] = 10^10

# when invertible, push! also works
julia> push!(Y, 10000)

julia> X
4-element Vector{Float64}:
  1.0
 10.0
  3.0
  4.0

maprange

maprange(f, start, stop; length): length values between start and stop, so that f(x) is incremented in uniform steps. Uses mapview in order not to materialize the array.

maprange(identity, ...) is equivalent to range(...). Most common application - log-spaced ranges:

maprange(log, 10, 1000, length=5) ≈ [10, 31.6227766, 100, 316.227766, 1000]

Other transformations can also be useful:

maprange(sqrt, 16, 1024, length=5) == [16, 121, 324, 625, 1024]

About

Generalize `map`: make it lazy, filtering, flattening, ...

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages