Current section
Files
Jump to
Current section
Files
lib/list.ex
defmodule Funx.List do
@moduledoc """
The `Funx.List` module provides utility functions for working with lists while respecting `Eq` and `Ord` instances. This allows for set-like operations, uniqueness constraints, and sorted collections that align with functional programming principles.
## Features
- **Equality-based Operations**: Use `Eq` instances to compare elements for uniqueness, intersection, and difference.
- **Ordering Functions**: Leverage `Ord` instances to sort and enforce uniqueness in sorted collections.
- **Set Operations**: Perform union, intersection, difference, and symmetric difference while preserving custom equality logic.
- **Subset & Superset Checks**: Verify relationships between lists in terms of inclusion.
- **Functional Constructs**: Implements `Monad` and `Foldable` protocols for lists, supporting mapping, binding, and folding.
### Usage Overview
1. **Deduplicate**: Use `uniq/1` to remove duplicates based on `Eq`.
2. **Combine**: Use `union/2` to merge lists without duplicates.
3. **Filter**: Use `intersection/2` or `difference/2` for set operations.
4. **Sort**: Use `sort/2` or `strict_sort/2` with `Ord` instances.
5. **Check Membership**: Use `subset?/2` or `superset?/2` to verify inclusion relationships.
### Equality-Based Operations
- `uniq/1`: Removes duplicates using `Eq`.
- `union/2`: Merges lists while preserving uniqueness.
- `intersection/2`: Returns elements common to both lists.
- `difference/2`: Returns elements from the first list not in the second.
- `symmetric_difference/2`: Returns elements unique to each list.
### Ordering Functions
- `sort/2`: Sorts a list using `Ord`.
- `strict_sort/2`: Sorts while ensuring uniqueness.
### Set Operations
- `subset?/2`: Checks if one list is a subset of another.
- `superset?/2`: Checks if one list is a superset of another.
### Monad Implementation
- `map/2`: Transforms list elements.
- `bind/2`: Applies a function returning lists and flattens the result.
- `ap/2`: Applies functions in a list to elements in another list.
### Foldable Implementation
- `fold_l/3`: Performs left-associative folding.
- `fold_r/3`: Performs right-associative folding.
"""
import Funx.Foldable, only: [fold_l: 3]
import Funx.Filterable, only: [filter: 2]
import Funx.Monoid.Utils, only: [m_concat: 2]
alias Funx.Eq
alias Funx.Monoid.ListConcat
alias Funx.Ord
@doc """
Removes duplicate elements from a list based on the given equality module.
## Examples
iex> Funx.List.uniq([1, 2, 2, 3, 1, 4, 5])
[1, 2, 3, 4, 5]
"""
@spec uniq([term()], Eq.Utils.eq_t()) :: [term()]
def uniq(list, eq \\ Funx.Eq) when is_list(list) do
list
|> fold_l([], fn item, acc ->
if Enum.any?(acc, &Eq.Utils.eq?(item, &1, eq)), do: acc, else: [item | acc]
end)
|> :lists.reverse()
end
@doc """
Returns the union of two lists, removing duplicates.
## Examples
iex> Funx.List.union([1, 2, 3], [3, 4, 5])
[1, 2, 3, 4, 5]
"""
@spec union([term()], [term()], Eq.Utils.eq_t()) :: [term()]
def union(list1, list2, eq \\ Funx.Eq) when is_list(list1) and is_list(list2) do
(list1 ++ list2) |> uniq(eq)
end
@doc """
Returns the intersection of two lists.
## Examples
iex> Funx.List.intersection([1, 2, 3, 4], [3, 4, 5])
[3, 4]
"""
@spec intersection([term()], [term()], Eq.Utils.eq_t()) :: [term()]
def intersection(list1, list2, eq \\ Funx.Eq) when is_list(list1) and is_list(list2) do
list1
|> filter(fn item -> Enum.any?(list2, &Eq.Utils.eq?(item, &1, eq)) end)
|> uniq(eq)
end
@doc """
Returns the difference of two lists.
## Examples
iex> Funx.List.difference([1, 2, 3, 4], [3, 4, 5])
[1, 2]
"""
@spec difference([term()], [term()], Eq.Utils.eq_t()) :: [term()]
def difference(list1, list2, eq \\ Funx.Eq) when is_list(list1) and is_list(list2) do
list1
|> Enum.reject(fn item -> Enum.any?(list2, &Eq.Utils.eq?(item, &1, eq)) end)
|> uniq(eq)
end
@doc """
Returns the symmetric difference of two lists.
## Examples
iex> Funx.List.symmetric_difference([1, 2, 3], [3, 4, 5])
[1, 2, 4, 5]
"""
@spec symmetric_difference([term()], [term()], Eq.Utils.eq_t()) :: [term()]
def symmetric_difference(list1, list2, eq \\ Funx.Eq)
when is_list(list1) and is_list(list2) do
(difference(list1, list2, eq) ++ difference(list2, list1, eq))
|> uniq(eq)
end
@doc """
Checks if the first list is a subset of the second.
## Examples
iex> Funx.List.subset?([1, 2], [1, 2, 3, 4])
true
iex> Funx.List.subset?([1, 5], [1, 2, 3, 4])
false
"""
@spec subset?([term()], [term()], Eq.Utils.eq_t()) :: boolean()
def subset?(small, large, eq \\ Funx.Eq) when is_list(small) and is_list(large) do
Enum.all?(small, fn item -> Enum.any?(large, &Eq.Utils.eq?(item, &1, eq)) end)
end
@doc """
Checks if the first list is a superset of the second.
## Examples
iex> Funx.List.superset?([1, 2, 3, 4], [1, 2])
true
iex> Funx.List.superset?([1, 2, 3, 4], [1, 5])
false
"""
@spec superset?([term()], [term()], Eq.Utils.eq_t()) :: boolean()
def superset?(large, small, eq \\ Funx.Eq) when is_list(small) and is_list(large) do
subset?(small, large, eq)
end
@doc """
Sorts a list using the given ordering module.
## Examples
iex> Funx.List.sort([3, 1, 4, 1, 5])
[1, 1, 3, 4, 5]
"""
@spec sort([term()], Ord.Utils.ord_t()) :: [term()]
def sort(list, ord \\ Funx.Ord) when is_list(list) do
Enum.sort(list, Ord.Utils.comparator(ord))
end
@doc """
Sorts a list while ensuring uniqueness.
## Examples
iex> Funx.List.strict_sort([3, 1, 4, 1, 5])
[1, 3, 4, 5]
"""
@spec strict_sort([term()], Ord.Utils.ord_t()) :: [term()]
def strict_sort(list, ord \\ Funx.Ord) when is_list(list) do
list
|> uniq(Ord.Utils.to_eq(ord))
|> sort(ord)
end
@doc """
Concatenates a list of lists from left to right.
This uses the `ListConcat` monoid, preserving the original order of elements.
## Examples
iex> Funx.List.concat([[1], [2, 3], [4]])
[1, 2, 3, 4]
"""
@spec concat([[term()]]) :: [term()]
def concat(list) when is_list(list) do
m_concat(%ListConcat{}, list)
end
end
defimpl Funx.Monad, for: List do
@spec map([term], (term -> term)) :: [term]
def map(list, func) do
:lists.map(func, list)
end
@spec ap(list((term -> term)), list(term)) :: list(term)
def ap(funcs, list) do
:lists.flatten(
for f <- funcs do
:lists.map(f, list)
end
)
end
@spec bind([term], (term -> [term])) :: [term]
def bind(list, func) do
:lists.flatmap(func, list)
end
end
defimpl Funx.Foldable, for: List do
@spec fold_l(list(term), term, (term, term -> term)) :: term
def fold_l(list, acc, func), do: :lists.foldl(func, acc, list)
@spec fold_r(list(term), term, (term, term -> term)) :: term
def fold_r(list, acc, func), do: :lists.foldr(func, acc, list)
end
defimpl Funx.Filterable, for: List do
def guard(list, true), do: list
def guard(_list, false), do: []
def filter(list, predicate) do
:lists.filter(predicate, list)
end
def filter_map(list, func) do
:lists.foldl(
fn x, acc ->
case func.(x) do
nil -> acc
result -> [result | acc]
end
end,
[],
list
)
|> :lists.reverse()
end
end