# ExBags
Enhanced map operations for Elixir with set-like functions including intersection, difference, symmetric difference, and reconciliation.
## Installation
Add `ex_bags` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_bags, "~> 0.1.0"}
]
end
```
## Overview
ExBags extends Elixir's built-in Map module with powerful set-like operations that are commonly needed when working with maps as collections of key-value pairs. These operations are particularly useful for:
- Data reconciliation and synchronization
- Set operations on map keys
- Data analysis and comparison
- Database-like operations on in-memory data
## Functions
### `intersection/2`
Returns a map containing only the key-value pairs that exist in both maps.
```elixir
iex> ExBags.intersection(%{a: 1, b: 2}, %{b: 2, c: 3})
%{b: 2}
iex> ExBags.intersection(%{a: 1, b: 2}, %{b: 3, c: 4})
%{b: 2} # Uses value from first map when keys match but values differ
```
### `difference/2`
Returns a map containing only the key-value pairs that exist in the first map but not in the second map.
```elixir
iex> ExBags.difference(%{a: 1, b: 2}, %{b: 2, c: 3})
%{a: 1}
iex> ExBags.difference(%{a: 1, b: 2}, %{a: 1, b: 2})
%{}
```
### `symmetric_difference/2`
Returns a map containing key-value pairs that exist in either map but not in both.
```elixir
iex> ExBags.symmetric_difference(%{a: 1, b: 2}, %{b: 2, c: 3})
%{a: 1, c: 3}
iex> ExBags.symmetric_difference(%{a: 1, b: 2}, %{c: 3, d: 4})
%{a: 1, b: 2, c: 3, d: 4}
```
### `reconcile/2`
Performs a reconciliation operation similar to SQL's FULL OUTER JOIN. Returns a tuple of three maps:
1. **Intersection**: Key-value pairs that exist in both maps
2. **Only in first**: Key-value pairs that exist only in the first map
3. **Only in second**: Key-value pairs that exist only in the second map
```elixir
iex> ExBags.reconcile(%{a: 1, b: 2}, %{b: 2, c: 3})
{%{b: 2}, %{a: 1}, %{c: 3}}
iex> ExBags.reconcile(%{a: 1}, %{b: 2})
{%{}, %{a: 1}, %{b: 2}}
```
## Stream Functions
For memory-efficient processing of large maps, ExBags also provides stream versions of all functions:
### `intersection_stream/2`, `difference_stream/2`, `symmetric_difference_stream/2`, `reconcile_stream/2`
These functions return streams instead of maps, allowing you to process large datasets without loading everything into memory at once.
```elixir
# Stream versions return lazy enumerables
iex> ExBags.intersection_stream(%{a: 1, b: 2}, %{b: 2, c: 3}) |> Enum.to_list()
[{:b, 2}]
# Streams can be processed incrementally
iex> stream = ExBags.intersection_stream(large_map1, large_map2)
iex> first_ten = stream |> Stream.take(10) |> Enum.to_list()
# Streams can be filtered and transformed
iex> result = ExBags.intersection_stream(map1, map2)
|> Stream.filter(fn {_key, value} -> value > 5 end)
|> Stream.map(fn {key, value} -> {key, value * 2} end)
|> Enum.to_list()
# Reconcile stream returns three streams
iex> {common, only_first, only_second} = ExBags.reconcile_stream(map1, map2)
iex> {Enum.to_list(common), Enum.to_list(only_first), Enum.to_list(only_second)}
```
## Use Cases
### Data Synchronization
```elixir
# Compare two datasets and identify changes
local_data = %{user1: %{name: "Alice", email: "alice@example.com"}}
remote_data = %{user1: %{name: "Alice", email: "alice@newdomain.com"}, user2: %{name: "Bob"}}
{common, local_only, remote_only} = ExBags.reconcile(local_data, remote_data)
# common: %{user1: %{name: "Alice", email: "alice@example.com"}}
# local_only: %{}
# remote_only: %{user2: %{name: "Bob"}}
```
### Configuration Management
```elixir
# Compare configuration versions
old_config = %{database_url: "postgres://old", api_key: "secret123", debug: true}
new_config = %{database_url: "postgres://new", api_key: "secret456", timeout: 5000}
{unchanged, removed, added} = ExBags.reconcile(old_config, new_config)
# unchanged: %{database_url: "postgres://old", api_key: "secret123"}
# removed: %{debug: true}
# added: %{timeout: 5000}
```
### Set Operations on Map Keys
```elixir
# Find common permissions between user roles
admin_perms = %{read: true, write: true, delete: true, admin: true}
user_perms = %{read: true, write: true, comment: true}
common_perms = ExBags.intersection(admin_perms, user_perms)
# %{read: true, write: true}
admin_only = ExBags.difference(admin_perms, user_perms)
# %{delete: true, admin: true}
```
## Performance Considerations
- All functions are optimized for performance and use Elixir's built-in Map operations
- Time complexity is generally O(n) where n is the number of keys in the maps
- Memory usage is efficient, creating new maps only when necessary
- Functions handle edge cases like empty maps gracefully
## Testing
Run the test suite:
```bash
mix test
```
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Changelog
### 0.1.0
- Initial release
- Added `intersection/2`, `difference/2`, `symmetric_difference/2`, and `reconcile/2` functions
- Comprehensive test coverage
- Full documentation