├── .credo.exs ├── .editorconfig ├── .formatter.exs ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── lib ├── bimap.ex └── bimultimap.ex ├── mix.exs ├── mix.lock └── test ├── bimap_properties_test.exs ├── bimap_test.exs ├── bimultimap_properties_test.exs ├── bimultimap_test.exs └── test_helper.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | %{ 2 | configs: [ 3 | %{ 4 | name: "default", 5 | checks: [ 6 | {Credo.Check.Refactor.MapInto, false}, 7 | {Credo.Check.Warning.LazyLogging, false} 8 | ] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | insert_final_newline = true 10 | tab_width = 2 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], 3 | import_deps: [:stream_data] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | name: Elixir ${{matrix.elixir}} / OTP ${{matrix.otp}} 12 | runs-on: ${{matrix.os}} 13 | 14 | strategy: 15 | matrix: 16 | include: 17 | # TODO: fix this 18 | #- otp: "23" 19 | # elixir: "1.10" 20 | # os: ubuntu-18.04 21 | - otp: "25" 22 | elixir: "main" 23 | os: ubuntu-latest 24 | 25 | env: 26 | MIX_ENV: test 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v2 31 | 32 | - name: Set up Elixir 33 | uses: erlef/setup-elixir@v1 34 | with: 35 | elixir-version: ${{ matrix.elixir }} 36 | otp-version: ${{ matrix.otp }} 37 | 38 | - name: Restore deps cache 39 | uses: actions/cache@v2 40 | with: 41 | path: | 42 | deps 43 | _build 44 | key: deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}-${{ github.sha }} 45 | restore-keys: | 46 | deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }} 47 | deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }} 48 | 49 | - name: Install package dependencies 50 | run: mix deps.get 51 | 52 | - name: Check for valid formatting 53 | run: mix format --check-formatted 54 | 55 | - name: Run unit tests 56 | run: mix test --color 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build/ 2 | /cover/ 3 | /deps/ 4 | /doc/ 5 | /.fetch 6 | erl_crash.dump 7 | *.ez 8 | .idea/ 9 | *.iml 10 | /.vscode/ 11 | .tool-versions 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 Marek Kaput 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BiMap 2 | 3 | [![Version](https://img.shields.io/hexpm/v/bimap.svg)](https://hex.pm/packages/bimap) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/bimap/) 5 | 6 | Elixir implementation of bidirectional map (`BiMap`) and multimap (`BiMultiMap`). 7 | 8 | ## Installation 9 | 10 | The package can be installed by adding `bimap` to your list of dependencies in `mix.exs`: 11 | 12 | ```elixir 13 | def deps do 14 | [{:bimap, "~> 1.3"}] 15 | end 16 | ``` 17 | 18 | ## Getting started 19 | 20 | For more examples, checkout [`BiMap`](https://hexdocs.pm/bimap/BiMap.html) and [`BiMultiMap`](https://hexdocs.pm/bimap/BiMultiMap.html) on hex docs. 21 | 22 | ### BiMap 23 | 24 | ```elixir 25 | iex(1)> Mix.install [:bimap] 26 | iex(2)> bm = BiMap.new(a: 1, b: 2) 27 | BiMap.new([a: 1, b: 2]) 28 | iex(3)> BiMap.get(bm, :a) 29 | 1 30 | iex(4)> BiMap.get_key(bm, 2) 31 | :b 32 | iex(5)> BiMap.put(bm, :a, 3) 33 | BiMap.new([a: 3, b: 2]) 34 | iex(6)> BiMap.put(bm, :c, 2) 35 | BiMap.new([a: 1, c: 2]) 36 | ``` 37 | 38 | ### BiMultiMap 39 | 40 | ```elixir 41 | iex(1)> Mix.install [:bimap] 42 | iex(2)> mm = BiMultiMap.new(a: 1, b: 2, b: 1) 43 | BiMultiMap.new([a: 1, b: 1, b: 2]) 44 | iex(3)> BiMultiMap.get(mm, :a) 45 | [1] 46 | iex(4)> BiMultiMap.get_keys(mm, 1) 47 | [:a, :b] 48 | iex(5)> BiMultiMap.put(mm, :a, 3) 49 | BiMultiMap.new([a: 1, a: 3, b: 1, b: 2]) 50 | ``` 51 | 52 | ## Changelog 53 | 54 | All notable changes to this project are documented on the [GitHub releases] page. 55 | 56 | ## License 57 | 58 | See the [LICENSE] file for license rights and limitations (MIT). 59 | 60 | [github releases]: https://github.com/mkaput/elixir-bimap/releases 61 | [license]: https://github.com/mkaput/elixir-bimap/blob/master/LICENSE.txt 62 | -------------------------------------------------------------------------------- /lib/bimap.ex: -------------------------------------------------------------------------------- 1 | defmodule BiMap do 2 | @moduledoc """ 3 | Bi-directional map implementation backed by two maps. 4 | 5 | > In computer science, a bidirectional map, or hash bag, is an associative data 6 | > structure in which the `(key, value)` pairs form a one-to-one correspondence. 7 | > Thus the binary relation is functional in each direction: `value` can also 8 | > act as a key to `key`. A pair `(a, b)` thus provides a unique coupling 9 | > between a `a` and `b` so that `b` can be found when `a` is used as a key and 10 | > `a` can be found when `b` is used as a key. 11 | > 12 | > ~[Wikipedia](https://en.wikipedia.org/wiki/Bidirectional_map) 13 | 14 | Entries in bimap do not follow any order. 15 | 16 | BiMaps do not impose any restriction on the key and value type: anything can be 17 | a key in a bimap, and also anything can be a value. As a bidirectional 18 | key-value structure, bimaps do not allow duplicated keys and values. This means 19 | it is not possible to store `[(A, B), (A, C)]` or `[(X, Z), (Y, Z)]` in 20 | the bimap. If you need to lift this restriction to only not allowing duplicated 21 | key-value pairs, check out `BiMultiMap`. 22 | 23 | Keys and values are compared using the exact-equality operator (`===`). 24 | 25 | ## Example 26 | 27 | iex> bm = BiMap.new(a: 1, b: 2) 28 | BiMap.new([a: 1, b: 2]) 29 | iex> BiMap.get(bm, :a) 30 | 1 31 | iex> BiMap.get_key(bm, 2) 32 | :b 33 | iex> BiMap.put(bm, :a, 3) 34 | BiMap.new([a: 3, b: 2]) 35 | iex> BiMap.put(bm, :c, 2) 36 | BiMap.new([a: 1, c: 2]) 37 | 38 | ## Protocols 39 | 40 | `BiMap` implements `Enumerable`, `Collectable` and `Inspect` protocols. 41 | """ 42 | 43 | @typedoc "Key type" 44 | @type k :: term 45 | 46 | @typedoc "Value type" 47 | @type v :: term 48 | 49 | @typedoc false 50 | @opaque internal_keys(k, v) :: %{optional(k) => v} 51 | 52 | @typedoc false 53 | @opaque internal_values(k, v) :: %{optional(v) => k} 54 | 55 | @type t(k, v) :: %BiMap{ 56 | keys: internal_keys(k, v), 57 | values: internal_values(k, v) 58 | } 59 | 60 | @type t :: t(term, term) 61 | 62 | defstruct keys: %{}, values: %{} 63 | 64 | @doc """ 65 | Creates a new bimap. 66 | 67 | ## Examples 68 | 69 | iex> BiMap.new 70 | BiMap.new([]) 71 | """ 72 | @spec new :: t 73 | def new, do: %BiMap{} 74 | 75 | @doc """ 76 | Creates a bimap from `enumerable` of key-value pairs. 77 | 78 | Duplicated pairs are removed; the latest one prevails. 79 | 80 | ## Examples 81 | 82 | iex> BiMap.new([a: "foo", b: "bar"]) 83 | BiMap.new([a: "foo", b: "bar"]) 84 | """ 85 | @spec new(Enum.t()) :: t 86 | def new(enumerable) 87 | def new(%BiMap{} = bimap), do: bimap 88 | 89 | def new(enum) do 90 | Enum.reduce(enum, new(), fn pair, bimap -> 91 | BiMap.put(bimap, pair) 92 | end) 93 | end 94 | 95 | @doc """ 96 | Creates a bimap from `enumerable` via transform function returning key-value 97 | pairs. 98 | 99 | ## Examples 100 | 101 | iex> BiMap.new([1, 2, 1], fn x -> {x, x * 2} end) 102 | BiMap.new([{1, 2}, {2, 4}]) 103 | """ 104 | @spec new(Enum.t(), (term -> {k, v})) :: t 105 | def new(enumerable, transform) 106 | 107 | def new(enum, f) do 108 | Enum.reduce(enum, new(), fn term, bimap -> 109 | BiMap.put(bimap, f.(term)) 110 | end) 111 | end 112 | 113 | @doc """ 114 | Returns the number of elements in `bimap`. 115 | 116 | The size of a bimap is the number of key-value pairs that the map contains. 117 | 118 | ## Examples 119 | 120 | iex> BiMap.size(BiMap.new) 121 | 0 122 | 123 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 124 | iex> BiMap.size(bimap) 125 | 2 126 | """ 127 | @spec size(t) :: non_neg_integer 128 | def size(bimap) 129 | 130 | def size(%BiMap{keys: keys}) do 131 | map_size(keys) 132 | end 133 | 134 | @doc """ 135 | Returns `key ➜ value` mapping of `bimap`. 136 | 137 | ## Examples 138 | 139 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 140 | iex> BiMap.left(bimap) 141 | %{a: "foo", b: "bar"} 142 | """ 143 | @spec left(t) :: %{k => v} 144 | def left(bimap) 145 | def left(%BiMap{keys: keys}), do: keys 146 | 147 | @doc """ 148 | Returns `value ➜ key` mapping of `bimap`. 149 | 150 | ## Examples 151 | 152 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 153 | iex> BiMap.right(bimap) 154 | %{"foo" => :a, "bar" => :b} 155 | """ 156 | @spec right(t) :: %{v => k} 157 | def right(bimap) 158 | def right(%BiMap{values: values}), do: values 159 | 160 | @doc """ 161 | Returns all keys from `bimap`. 162 | 163 | ## Examples 164 | 165 | iex> bimap = BiMap.new([a: 1, b: 2]) 166 | iex> BiMap.keys(bimap) 167 | [:a, :b] 168 | """ 169 | @spec keys(t) :: [k] 170 | def keys(bimap) 171 | def keys(%BiMap{keys: keys}), do: Map.keys(keys) 172 | 173 | @doc """ 174 | Returns all values from `bimap`. 175 | 176 | ## Examples 177 | 178 | iex> bimap = BiMap.new([a: 1, b: 2]) 179 | iex> BiMap.values(bimap) 180 | [1, 2] 181 | """ 182 | @spec values(t) :: [v] 183 | def values(bimap) 184 | def values(%BiMap{values: values}), do: Map.keys(values) 185 | 186 | @doc """ 187 | Checks if `bimap` contains `{key, value}` pair. 188 | 189 | ## Examples 190 | 191 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 192 | iex> BiMap.member?(bimap, :a, "foo") 193 | true 194 | iex> BiMap.member?(bimap, :a, "bar") 195 | false 196 | """ 197 | @spec member?(t, k, v) :: boolean 198 | def member?(bimap, key, value) 199 | 200 | def member?(%BiMap{keys: keys}, key, value) do 201 | Map.has_key?(keys, key) and keys[key] === value 202 | end 203 | 204 | @doc """ 205 | Convenience shortcut for `member?/3`. 206 | """ 207 | @spec member?(t, {k, v}) :: boolean 208 | def member?(bimap, kv) 209 | def member?(bimap, {key, value}), do: member?(bimap, key, value) 210 | 211 | @doc """ 212 | Checks if `bimap` contains `key`. 213 | 214 | ## Examples 215 | 216 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 217 | iex> BiMap.has_key?(bimap, :a) 218 | true 219 | iex> BiMap.has_key?(bimap, :x) 220 | false 221 | """ 222 | @spec has_key?(t, k) :: boolean 223 | def has_key?(bimap, key) 224 | 225 | def has_key?(%BiMap{keys: keys}, left) do 226 | Map.has_key?(keys, left) 227 | end 228 | 229 | @doc """ 230 | Checks if `bimap` contains `value`. 231 | 232 | ## Examples 233 | 234 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 235 | iex> BiMap.has_value?(bimap, "foo") 236 | true 237 | iex> BiMap.has_value?(bimap, "moo") 238 | false 239 | """ 240 | @spec has_value?(t, v) :: boolean 241 | def has_value?(bimap, value) 242 | 243 | def has_value?(%BiMap{values: values}, value) do 244 | Map.has_key?(values, value) 245 | end 246 | 247 | @doc """ 248 | Checks if two bimaps are equal. 249 | 250 | Two bimaps are considered to be equal if they contain the same keys and those 251 | keys contain the same values. 252 | 253 | ## Examples 254 | 255 | iex> Map.equal?(BiMap.new([a: 1, b: 2]), BiMap.new([b: 2, a: 1])) 256 | true 257 | iex> Map.equal?(BiMap.new([a: 1, b: 2]), BiMap.new([b: 1, a: 2])) 258 | false 259 | """ 260 | @spec equal?(t, t) :: boolean 261 | def equal?(bimap1, bimap2) 262 | 263 | def equal?(%BiMap{keys: keys1}, %BiMap{keys: keys2}) do 264 | Map.equal?(keys1, keys2) 265 | end 266 | 267 | @doc """ 268 | Gets the value for specific `key` in `bimap` 269 | 270 | If `key` is present in `bimap` with value `value`, then `value` is returned. 271 | Otherwise, `default` is returned (which is `nil` unless specified otherwise). 272 | 273 | ## Examples 274 | 275 | iex> BiMap.get(BiMap.new(), :a) 276 | nil 277 | iex> bimap = BiMap.new([a: 1]) 278 | iex> BiMap.get(bimap, :a) 279 | 1 280 | iex> BiMap.get(bimap, :b) 281 | nil 282 | iex> BiMap.get(bimap, :b, 3) 283 | 3 284 | """ 285 | @spec get(t, k, v) :: v 286 | def get(bimap, key, default \\ nil) 287 | 288 | def get(%BiMap{keys: keys}, key, default) do 289 | Map.get(keys, key, default) 290 | end 291 | 292 | @doc """ 293 | Gets the key for specific `value` in `bimap` 294 | 295 | This function is exact mirror of `get/3`. 296 | 297 | ## Examples 298 | 299 | iex> BiMap.get_key(BiMap.new, 1) 300 | nil 301 | iex> bimap = BiMap.new([a: 1]) 302 | iex> BiMap.get_key(bimap, 1) 303 | :a 304 | iex> BiMap.get_key(bimap, 2) 305 | nil 306 | iex> BiMap.get_key(bimap, 2, :b) 307 | :b 308 | """ 309 | @spec get_key(t, v, k) :: k 310 | def get_key(bimap, value, default \\ nil) 311 | 312 | def get_key(%BiMap{values: values}, value, default) do 313 | Map.get(values, value, default) 314 | end 315 | 316 | @doc """ 317 | Fetches the value for specific `key` in `bimap` 318 | 319 | If `key` is present in `bimap` with value `value`, then `{:ok, value}` is 320 | returned. Otherwise, `:error` is returned. 321 | 322 | ## Examples 323 | 324 | iex> BiMap.fetch(BiMap.new(), :a) 325 | :error 326 | iex> bimap = BiMap.new([a: 1]) 327 | iex> BiMap.fetch(bimap, :a) 328 | {:ok, 1} 329 | iex> BiMap.fetch(bimap, :b) 330 | :error 331 | """ 332 | @spec fetch(t, k) :: {:ok, v} | :error 333 | def fetch(bimap, key) 334 | 335 | def fetch(%BiMap{keys: keys}, key) do 336 | Map.fetch(keys, key) 337 | end 338 | 339 | @doc """ 340 | Fetches the value for specific `key` in `bimap`. 341 | 342 | Raises `ArgumentError` if the key is absent. 343 | 344 | ## Examples 345 | 346 | iex> bimap = BiMap.new([a: 1]) 347 | iex> BiMap.fetch!(bimap, :a) 348 | 1 349 | """ 350 | @spec fetch!(t, k) :: v 351 | def fetch!(bimap, key) 352 | 353 | def fetch!(bimap, key) do 354 | case fetch(bimap, key) do 355 | {:ok, value} -> value 356 | :error -> raise ArgumentError, "key #{inspect(key)} not found in: #{inspect(bimap)}" 357 | end 358 | end 359 | 360 | @doc """ 361 | Fetches the key for specific `value` in `bimap` 362 | 363 | This function is exact mirror of `fetch/2`. 364 | 365 | ## Examples 366 | 367 | iex> BiMap.fetch_key(BiMap.new, 1) 368 | :error 369 | iex> bimap = BiMap.new([a: 1]) 370 | iex> BiMap.fetch_key(bimap, 1) 371 | {:ok, :a} 372 | iex> BiMap.fetch_key(bimap, 2) 373 | :error 374 | """ 375 | @spec fetch_key(t, v) :: {:ok, k} | :error 376 | def fetch_key(bimap, value) 377 | 378 | def fetch_key(%BiMap{values: values}, value) do 379 | Map.fetch(values, value) 380 | end 381 | 382 | @doc """ 383 | Fetches the key for specific `value` in `bimap`. 384 | 385 | Raises `ArgumentError` if the value is absent. This function is exact mirror of `fetch!/2`. 386 | 387 | ## Examples 388 | 389 | iex> bimap = BiMap.new([a: 1]) 390 | iex> BiMap.fetch_key!(bimap, 1) 391 | :a 392 | """ 393 | @spec fetch_key!(t, v) :: k 394 | def fetch_key!(bimap, value) 395 | 396 | def fetch_key!(bimap, value) do 397 | case fetch_key(bimap, value) do 398 | {:ok, key} -> key 399 | :error -> raise ArgumentError, "value #{inspect(value)} not found in: #{inspect(bimap)}" 400 | end 401 | end 402 | 403 | @doc """ 404 | Inserts `{key, value}` pair into `bimap`. 405 | 406 | If either `key` or `value` is already in `bimap`, any overlapping bindings are 407 | deleted. 408 | 409 | ## Examples 410 | 411 | iex> bimap = BiMap.new 412 | BiMap.new([]) 413 | iex> bimap = BiMap.put(bimap, :a, 0) 414 | BiMap.new([a: 0]) 415 | iex> bimap = BiMap.put(bimap, :a, 1) 416 | BiMap.new([a: 1]) 417 | iex> BiMap.put(bimap, :b, 1) 418 | BiMap.new([b: 1]) 419 | """ 420 | @spec put(t, k, v) :: t 421 | def put(%BiMap{} = bimap, key, value) do 422 | %{keys: keys, values: values} = bimap |> BiMap.delete_key(key) |> BiMap.delete_value(value) 423 | %{bimap | keys: Map.put(keys, key, value), values: Map.put(values, value, key)} 424 | end 425 | 426 | @doc """ 427 | Convenience shortcut for `put/3` 428 | """ 429 | @spec put(t, {k, v}) :: t 430 | def put(bimap, kv) 431 | def put(bimap, {key, value}), do: put(bimap, key, value) 432 | 433 | @doc """ 434 | Inserts `{key, value}` pair into `bimap` if `key` is not already in `bimap`. 435 | 436 | If `key` already exists in `bimap`, `bimap` is returned unchanged. 437 | 438 | If `key` does not exist and `value` is already in `bimap`, any overlapping bindings are 439 | deleted. 440 | 441 | ## Examples 442 | 443 | iex> bimap = BiMap.new 444 | BiMap.new([]) 445 | iex> bimap = BiMap.put_new_key(bimap, :a, 0) 446 | BiMap.new([a: 0]) 447 | iex> bimap = BiMap.put_new_key(bimap, :a, 1) 448 | BiMap.new([a: 0]) 449 | iex> BiMap.put_new_key(bimap, :b, 1) 450 | BiMap.new([a: 0, b: 1]) 451 | iex> BiMap.put_new_key(bimap, :c, 1) 452 | BiMap.new([a: 0, c: 1]) 453 | """ 454 | @spec put_new_key(t, k, v) :: t 455 | def put_new_key(%BiMap{} = bimap, key, value) do 456 | if BiMap.has_key?(bimap, key) do 457 | bimap 458 | else 459 | put(bimap, key, value) 460 | end 461 | end 462 | 463 | @doc """ 464 | Inserts `{key, value}` pair into `bimap` if `value` is not already in `bimap`. 465 | 466 | If `value` already exists in `bimap`, `bimap` is returned unchanged. 467 | 468 | If `value` does not exist and `key` is already in `bimap`, any overlapping bindings are 469 | deleted. 470 | 471 | ## Examples 472 | 473 | iex> bimap = BiMap.new 474 | BiMap.new([]) 475 | iex> bimap = BiMap.put_new_value(bimap, :a, 0) 476 | BiMap.new([a: 0]) 477 | iex> bimap = BiMap.put_new_value(bimap, :a, 1) 478 | BiMap.new([a: 1]) 479 | iex> BiMap.put_new_value(bimap, :b, 1) 480 | BiMap.new([a: 1]) 481 | iex> BiMap.put_new_value(bimap, :c, 2) 482 | BiMap.new([a: 1, c: 2]) 483 | """ 484 | @spec put_new_value(t, k, v) :: t 485 | def put_new_value(%BiMap{} = bimap, key, value) do 486 | if BiMap.has_value?(bimap, value) do 487 | bimap 488 | else 489 | put(bimap, key, value) 490 | end 491 | end 492 | 493 | @doc """ 494 | Deletes `{key, value}` pair from `bimap`. 495 | 496 | If the `key` does not exist, or `value` does not match, returns `bimap` 497 | unchanged. 498 | 499 | ## Examples 500 | 501 | iex> bimap = BiMap.new([a: 1, b: 2]) 502 | iex> BiMap.delete(bimap, :b, 2) 503 | BiMap.new([a: 1]) 504 | iex> BiMap.delete(bimap, :c, 3) 505 | BiMap.new([a: 1, b: 2]) 506 | iex> BiMap.delete(bimap, :b, 3) 507 | BiMap.new([a: 1, b: 2]) 508 | """ 509 | @spec delete(t, k, v) :: t 510 | def delete(%BiMap{keys: keys, values: values} = bimap, key, value) do 511 | case Map.fetch(keys, key) do 512 | {:ok, ^value} -> 513 | %{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)} 514 | 515 | _ -> 516 | bimap 517 | end 518 | end 519 | 520 | @doc """ 521 | Deletes `{key, _}` pair from `bimap`. 522 | 523 | If the `key` does not exist, returns `bimap` unchanged. 524 | 525 | ## Examples 526 | 527 | iex> bimap = BiMap.new([a: 1, b: 2]) 528 | iex> BiMap.delete_key(bimap, :b) 529 | BiMap.new([a: 1]) 530 | iex> BiMap.delete_key(bimap, :c) 531 | BiMap.new([a: 1, b: 2]) 532 | """ 533 | @spec delete_key(t, k) :: t 534 | def delete_key(%BiMap{keys: keys, values: values} = bimap, key) do 535 | case Map.fetch(keys, key) do 536 | {:ok, value} -> 537 | %{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)} 538 | 539 | :error -> 540 | bimap 541 | end 542 | end 543 | 544 | @doc """ 545 | Deletes `{_, value}` pair from `bimap`. 546 | 547 | If the `value` does not exist, returns `bimap` unchanged. 548 | 549 | ## Examples 550 | 551 | iex> bimap = BiMap.new([a: 1, b: 2]) 552 | iex> BiMap.delete_value(bimap, 2) 553 | BiMap.new([a: 1]) 554 | iex> BiMap.delete_value(bimap, 3) 555 | BiMap.new([a: 1, b: 2]) 556 | """ 557 | @spec delete_value(t, v) :: t 558 | def delete_value(%BiMap{keys: keys, values: values} = bimap, value) do 559 | case Map.fetch(values, value) do 560 | {:ok, key} -> 561 | %{bimap | keys: Map.delete(keys, key), values: Map.delete(values, value)} 562 | 563 | :error -> 564 | bimap 565 | end 566 | end 567 | 568 | @doc """ 569 | Convenience shortcut for `delete/3`. 570 | """ 571 | @spec delete(t, {k, v}) :: t 572 | def delete(bimap, kv) 573 | def delete(bimap, {key, value}), do: delete(bimap, key, value) 574 | 575 | @doc """ 576 | Returns list of unique key-value pairs in `bimap`. 577 | 578 | ## Examples 579 | 580 | iex> bimap = BiMap.new([a: "foo", b: "bar"]) 581 | iex> BiMap.to_list(bimap) 582 | [a: "foo", b: "bar"] 583 | """ 584 | @spec to_list(t) :: [{k, v}] 585 | def to_list(bimap) 586 | 587 | def to_list(%BiMap{keys: keys}) do 588 | Map.to_list(keys) 589 | end 590 | 591 | defimpl Enumerable do 592 | def reduce(bimap, acc, fun) do 593 | Enumerable.List.reduce(BiMap.to_list(bimap), acc, fun) 594 | end 595 | 596 | def member?(bimap, val) do 597 | {:ok, BiMap.member?(bimap, val)} 598 | end 599 | 600 | def count(bimap) do 601 | {:ok, BiMap.size(bimap)} 602 | end 603 | 604 | def slice(_bimap) do 605 | {:error, __MODULE__} 606 | end 607 | end 608 | 609 | defimpl Collectable do 610 | def into(original) do 611 | {original, 612 | fn 613 | bimap, {:cont, pair} -> BiMap.put(bimap, pair) 614 | bimap, :done -> bimap 615 | _, :halt -> :ok 616 | end} 617 | end 618 | end 619 | 620 | defimpl Inspect do 621 | import Inspect.Algebra 622 | 623 | def inspect(bimap, opts) do 624 | concat(["BiMap.new(", to_doc(BiMap.to_list(bimap), opts), ")"]) 625 | end 626 | end 627 | end 628 | -------------------------------------------------------------------------------- /lib/bimultimap.ex: -------------------------------------------------------------------------------- 1 | defmodule BiMultiMap do 2 | @moduledoc """ 3 | Bi-directional multimap implementation backed by two multimaps. 4 | 5 | Entries in bimap do not follow any order. 6 | 7 | BiMultiMaps do not impose any restriction on the key and value type: anything 8 | can be a key in a bimap, and also anything can be a value. 9 | 10 | BiMultiMaps differ from `BiMap`s by disallowing duplicates only among key-value 11 | pairs, not among keys and values separately. This means it is possible to store 12 | `[(A, B), (A, C)]` or `[(X, Z), (Y, Z)]` in BiMultiMap. 13 | 14 | Keys and values are compared using the exact-equality operator (`===`). 15 | 16 | ## Example 17 | 18 | iex> mm = BiMultiMap.new(a: 1, b: 2, b: 1) 19 | BiMultiMap.new([a: 1, b: 1, b: 2]) 20 | iex> BiMultiMap.get(mm, :a) 21 | [1] 22 | iex> BiMultiMap.get_keys(mm, 1) 23 | [:a, :b] 24 | iex> BiMultiMap.put(mm, :a, 3) 25 | BiMultiMap.new([a: 1, a: 3, b: 1, b: 2]) 26 | 27 | ## Protocols 28 | 29 | `BiMultiMap` implements `Enumerable`, `Collectable` and `Inspect` protocols. 30 | """ 31 | 32 | @typedoc "Key type" 33 | @type k :: term 34 | 35 | @typedoc "Value type" 36 | @type v :: term 37 | 38 | @typedoc false 39 | @opaque internal_keys(k, v) :: %{optional(k) => MapSet.t(v)} 40 | 41 | @typedoc false 42 | @opaque internal_values(k, v) :: %{optional(v) => MapSet.t(k)} 43 | 44 | @typedoc false 45 | @opaque internal_size :: non_neg_integer 46 | 47 | @type t(k, v) :: %BiMultiMap{ 48 | keys: internal_keys(k, v), 49 | values: internal_values(k, v), 50 | size: internal_size 51 | } 52 | 53 | @type t :: t(term, term) 54 | 55 | defstruct keys: %{}, values: %{}, size: 0 56 | 57 | @doc """ 58 | Creates a new bimultimap. 59 | 60 | ## Examples 61 | 62 | iex> BiMultiMap.new 63 | BiMultiMap.new([]) 64 | """ 65 | @spec new :: t 66 | def new, do: %BiMultiMap{} 67 | 68 | @doc """ 69 | Creates a bimultimap from `enumerable` of key-value pairs. 70 | 71 | Duplicated pairs are removed; the latest one prevails. 72 | 73 | ## Examples 74 | 75 | iex> BiMultiMap.new([a: 1, a: 2]) 76 | BiMultiMap.new([a: 1, a: 2]) 77 | """ 78 | @spec new(Enum.t()) :: t 79 | def new(enumerable) 80 | def new(%BiMultiMap{} = bimultimap), do: bimultimap 81 | 82 | def new(enum) do 83 | Enum.reduce(enum, new(), fn pair, bimultimap -> 84 | BiMultiMap.put(bimultimap, pair) 85 | end) 86 | end 87 | 88 | @doc """ 89 | Creates a bimultimap from `enumerable` via transform function returning 90 | key-value pairs. 91 | 92 | ## Examples 93 | 94 | iex> BiMultiMap.new([1, 2, 1], fn x -> {x, x * 2} end) 95 | BiMultiMap.new([{1, 2}, {2, 4}]) 96 | """ 97 | @spec new(Enum.t(), (term -> {k, v})) :: t 98 | def new(enumerable, transform) 99 | 100 | def new(enum, f) do 101 | Enum.reduce(enum, new(), fn term, bimultimap -> 102 | BiMultiMap.put(bimultimap, f.(term)) 103 | end) 104 | end 105 | 106 | @doc """ 107 | Returns the number of elements in `bimultimap`. 108 | 109 | The size of a bimultimap is the number of key-value pairs that the map 110 | contains. 111 | 112 | ## Examples 113 | 114 | iex> BiMultiMap.size(BiMultiMap.new) 115 | 0 116 | 117 | iex> bimultimap = BiMultiMap.new([a: "foo", a: "bar"]) 118 | iex> BiMultiMap.size(bimultimap) 119 | 2 120 | """ 121 | @spec size(t) :: non_neg_integer 122 | def size(bimultimap) 123 | def size(%BiMultiMap{size: size}), do: size 124 | 125 | @doc """ 126 | Returns `key ➜ [value]` mapping of `bimultimap`. 127 | 128 | ## Examples 129 | 130 | iex> bimultimap = BiMultiMap.new([a: "foo", b: "bar", b: "moo"]) 131 | iex> BiMultiMap.left(bimultimap) 132 | %{a: ["foo"], b: ["bar", "moo"]} 133 | """ 134 | @spec left(t) :: %{k => [v]} 135 | def left(bimultimap) 136 | 137 | def left(%BiMultiMap{keys: keys}) do 138 | for {k, vs} <- keys, into: %{} do 139 | {k, MapSet.to_list(vs)} 140 | end 141 | end 142 | 143 | @doc """ 144 | Returns `value ➜ key` mapping of `bimultimap`. 145 | 146 | ## Examples 147 | 148 | iex> bimultimap = BiMultiMap.new([a: "foo", b: "bar", c: "bar"]) 149 | iex> BiMultiMap.right(bimultimap) 150 | %{"foo" => [:a], "bar" => [:b, :c]} 151 | """ 152 | @spec right(t) :: %{v => [k]} 153 | def right(bimultimap) 154 | 155 | def right(%BiMultiMap{values: values}) do 156 | for {v, ks} <- values, into: %{} do 157 | {v, MapSet.to_list(ks)} 158 | end 159 | end 160 | 161 | @doc """ 162 | Returns all unique keys from `bimultimap`. 163 | 164 | ## Examples 165 | 166 | iex> bimultimap = BiMultiMap.new([a: 1, b: 2, b: 3]) 167 | iex> BiMultiMap.keys(bimultimap) 168 | [:a, :b] 169 | """ 170 | @spec keys(t) :: [k] 171 | def keys(bimultimap) 172 | def keys(%BiMultiMap{keys: keys}), do: Map.keys(keys) 173 | 174 | @doc """ 175 | Returns all unique values from `bimultimap`. 176 | 177 | ## Examples 178 | 179 | iex> bimultimap = BiMultiMap.new([a: 1, b: 2, c: 2]) 180 | iex> BiMultiMap.values(bimultimap) 181 | [1, 2] 182 | """ 183 | @spec values(t) :: [v] 184 | def values(bimultimap) 185 | def values(%BiMultiMap{values: values}), do: Map.keys(values) 186 | 187 | @doc """ 188 | Checks if `bimultimap` contains `{key, value}` pair. 189 | 190 | ## Examples 191 | 192 | iex> bimultimap = BiMultiMap.new([a: "foo", a: "moo", b: "bar"]) 193 | iex> BiMultiMap.member?(bimultimap, :a, "foo") 194 | true 195 | iex> BiMultiMap.member?(bimultimap, :a, "moo") 196 | true 197 | iex> BiMultiMap.member?(bimultimap, :a, "bar") 198 | false 199 | """ 200 | @spec member?(t, k, v) :: boolean 201 | def member?(bimultimap, key, value) 202 | 203 | def member?(%BiMultiMap{keys: keys}, key, value) do 204 | Map.has_key?(keys, key) and value in keys[key] 205 | end 206 | 207 | @doc """ 208 | Convenience shortcut for `member?/3`. 209 | """ 210 | @spec member?(t, {k, v}) :: boolean 211 | def member?(bimultimap, kv) 212 | def member?(bimultimap, {key, value}), do: member?(bimultimap, key, value) 213 | 214 | @doc """ 215 | Checks if `bimultimap` contains `key`. 216 | 217 | ## Examples 218 | 219 | iex> bimultimap = BiMultiMap.new([a: "foo", b: "bar"]) 220 | iex> BiMultiMap.has_key?(bimultimap, :a) 221 | true 222 | iex> BiMultiMap.has_key?(bimultimap, :x) 223 | false 224 | """ 225 | @spec has_key?(t, k) :: boolean 226 | def has_key?(bimultimap, key) 227 | 228 | def has_key?(%BiMultiMap{keys: keys}, left) do 229 | Map.has_key?(keys, left) 230 | end 231 | 232 | @doc """ 233 | Checks if `bimultimap` contains `value`. 234 | 235 | ## Examples 236 | 237 | iex> bimultimap = BiMultiMap.new([a: "foo", b: "bar"]) 238 | iex> BiMultiMap.has_value?(bimultimap, "foo") 239 | true 240 | iex> BiMultiMap.has_value?(bimultimap, "moo") 241 | false 242 | """ 243 | @spec has_value?(t, v) :: boolean 244 | def has_value?(bimultimap, value) 245 | 246 | def has_value?(%BiMultiMap{values: values}, value) do 247 | Map.has_key?(values, value) 248 | end 249 | 250 | @doc """ 251 | Checks if two bimultimaps are equal. 252 | 253 | Two bimultimaps are considered to be equal if they contain the same keys and 254 | those keys are bound with the same values. 255 | 256 | ## Examples 257 | 258 | iex> Map.equal?(BiMultiMap.new([a: 1, b: 2, b: 3]), BiMultiMap.new([b: 2, b: 3, a: 1])) 259 | true 260 | iex> Map.equal?(BiMultiMap.new([a: 1, b: 2, b: 3]), BiMultiMap.new([b: 1, b: 3, a: 2])) 261 | false 262 | """ 263 | @spec equal?(t, t) :: boolean 264 | def equal?(bimultimap1, bimultimap2) 265 | 266 | def equal?(%BiMultiMap{keys: keys1}, %BiMultiMap{keys: keys2}) do 267 | Map.equal?(keys1, keys2) 268 | end 269 | 270 | @doc """ 271 | Gets all values for specific `key` in `bimultimap` 272 | 273 | If `key` is present in `bimultimap` with values `values`, then `values` are 274 | returned. Otherwise, `default` is returned (which is `[]` unless specified 275 | otherwise). 276 | 277 | ## Examples 278 | 279 | iex> BiMultiMap.get(BiMultiMap.new(), :a) 280 | [] 281 | iex> bimultimap = BiMultiMap.new([a: 1, c: 1, c: 2]) 282 | iex> BiMultiMap.get(bimultimap, :a) 283 | [1] 284 | iex> BiMultiMap.get(bimultimap, :b) 285 | [] 286 | iex> BiMultiMap.get(bimultimap, :b, 3) 287 | 3 288 | iex> BiMultiMap.get(bimultimap, :c) 289 | [1, 2] 290 | """ 291 | @spec get(t, k, any) :: [v] | any 292 | def get(bimultimap, key, default \\ []) 293 | 294 | def get(%BiMultiMap{keys: keys}, key, default) do 295 | case Map.fetch(keys, key) do 296 | {:ok, values} -> MapSet.to_list(values) 297 | :error -> default 298 | end 299 | end 300 | 301 | @doc """ 302 | Gets all keys for specific `value` in `bimultimap` 303 | 304 | This function is exact mirror of `get/3`. 305 | 306 | ## Examples 307 | 308 | iex> BiMultiMap.get_keys(BiMultiMap.new, 1) 309 | [] 310 | iex> bimultimap = BiMultiMap.new([a: 1, c: 3, d: 3]) 311 | iex> BiMultiMap.get_keys(bimultimap, 1) 312 | [:a] 313 | iex> BiMultiMap.get_keys(bimultimap, 2) 314 | [] 315 | iex> BiMultiMap.get_keys(bimultimap, 2, :b) 316 | :b 317 | iex> BiMultiMap.get_keys(bimultimap, 3) 318 | [:c, :d] 319 | """ 320 | @spec get_keys(t, v, any) :: [k] | any 321 | def get_keys(bimultimap, value, default \\ []) 322 | 323 | def get_keys(%BiMultiMap{values: values}, value, default) do 324 | case Map.fetch(values, value) do 325 | {:ok, keys} -> MapSet.to_list(keys) 326 | :error -> default 327 | end 328 | end 329 | 330 | @doc """ 331 | Fetches all values for specific `key` in `bimultimap` 332 | 333 | If `key` is present in `bimultimap` with values `values`, then `{:ok, values}` 334 | is returned. Otherwise, `:error` is returned. 335 | 336 | ## Examples 337 | 338 | iex> BiMultiMap.fetch(BiMultiMap.new(), :a) 339 | :error 340 | iex> bimultimap = BiMultiMap.new([a: 1, c: 1, c: 2]) 341 | iex> BiMultiMap.fetch(bimultimap, :a) 342 | {:ok, [1]} 343 | iex> BiMultiMap.fetch(bimultimap, :b) 344 | :error 345 | iex> BiMultiMap.fetch(bimultimap, :c) 346 | {:ok, [1, 2]} 347 | """ 348 | @spec fetch(t, k) :: {:ok, [v]} | :error 349 | def fetch(bimultimap, key) 350 | 351 | def fetch(%BiMultiMap{keys: keys}, key) do 352 | case Map.fetch(keys, key) do 353 | {:ok, values} -> {:ok, MapSet.to_list(values)} 354 | :error -> :error 355 | end 356 | end 357 | 358 | @doc """ 359 | Fetches all values for specific `key` in `bimultimap` 360 | 361 | Raises `ArgumentError` if the key is absent. 362 | 363 | ## Examples 364 | 365 | iex> bimultimap = BiMultiMap.new([a: 1, c: 1, c: 2]) 366 | iex> BiMultiMap.fetch!(bimultimap, :a) 367 | [1] 368 | iex> BiMultiMap.fetch!(bimultimap, :c) 369 | [1, 2] 370 | """ 371 | @spec fetch!(t, k) :: [v] 372 | def fetch!(bimultimap, key) 373 | 374 | def fetch!(bimultimap, key) do 375 | case fetch(bimultimap, key) do 376 | {:ok, values} -> values 377 | :error -> raise ArgumentError, "key #{inspect(key)} not found in: #{inspect(bimultimap)}" 378 | end 379 | end 380 | 381 | @doc """ 382 | Fetches all keys for specific `value` in `bimultimap` 383 | 384 | This function is exact mirror of `fetch/2`. 385 | 386 | ## Examples 387 | 388 | iex> BiMultiMap.fetch_keys(BiMultiMap.new, 1) 389 | :error 390 | iex> bimultimap = BiMultiMap.new([a: 1, c: 3, d: 3]) 391 | iex> BiMultiMap.fetch_keys(bimultimap, 1) 392 | {:ok, [:a]} 393 | iex> BiMultiMap.fetch_keys(bimultimap, 2) 394 | :error 395 | iex> BiMultiMap.fetch_keys(bimultimap, 3) 396 | {:ok, [:c, :d]} 397 | """ 398 | @spec fetch_keys(t, v) :: {:ok, [k]} | :error 399 | def fetch_keys(bimultimap, value) 400 | 401 | def fetch_keys(%BiMultiMap{values: values}, value) do 402 | case Map.fetch(values, value) do 403 | {:ok, keys} -> {:ok, MapSet.to_list(keys)} 404 | :error -> :error 405 | end 406 | end 407 | 408 | @doc """ 409 | Fetches all keys for specific `value` in `bimultimap` 410 | 411 | Raises `ArgumentError` if the key is absent. This function is exact mirror of `fetch!/2`. 412 | 413 | ## Examples 414 | 415 | iex> bimultimap = BiMultiMap.new([a: 1, c: 3, d: 3]) 416 | iex> BiMultiMap.fetch_keys!(bimultimap, 1) 417 | [:a] 418 | iex> BiMultiMap.fetch_keys!(bimultimap, 3) 419 | [:c, :d] 420 | """ 421 | @spec fetch_keys!(t, v) :: [k] 422 | def fetch_keys!(bimultimap, value) 423 | 424 | def fetch_keys!(bimultimap, value) do 425 | case fetch_keys(bimultimap, value) do 426 | {:ok, keys} -> 427 | keys 428 | 429 | :error -> 430 | raise ArgumentError, "value #{inspect(value)} not found in: #{inspect(bimultimap)}" 431 | end 432 | end 433 | 434 | @doc """ 435 | Inserts `{key, value}` pair into `bimultimap`. 436 | 437 | If `{key, value}` is already in `bimultimap`, it is deleted. 438 | 439 | ## Examples 440 | 441 | iex> bimultimap = BiMultiMap.new 442 | BiMultiMap.new([]) 443 | iex> bimultimap = BiMultiMap.put(bimultimap, :a, 1) 444 | BiMultiMap.new([a: 1]) 445 | iex> bimultimap = BiMultiMap.put(bimultimap, :a, 2) 446 | BiMultiMap.new([a: 1, a: 2]) 447 | iex> BiMultiMap.put(bimultimap, :b, 2) 448 | BiMultiMap.new([a: 1, a: 2, b: 2]) 449 | """ 450 | @spec put(t, k, v) :: t 451 | def put( 452 | %BiMultiMap{keys: keys, values: values, size: size} = bimultimap, 453 | key, 454 | value 455 | ) do 456 | {upd, keys} = put_side(keys, key, value) 457 | {^upd, values} = put_side(values, value, key) 458 | 459 | size = 460 | if upd do 461 | size + 1 462 | else 463 | size 464 | end 465 | 466 | %{bimultimap | keys: keys, values: values, size: size} 467 | end 468 | 469 | defp put_side(keys, key, value) do 470 | Map.get_and_update(keys, key, fn 471 | nil -> {true, MapSet.new([value])} 472 | set -> {!MapSet.member?(set, value), MapSet.put(set, value)} 473 | end) 474 | end 475 | 476 | @doc """ 477 | Convenience shortcut for `put/3` 478 | """ 479 | @spec put(t, {k, v}) :: t 480 | def put(bimultimap, kv) 481 | def put(bimultimap, {key, value}), do: put(bimultimap, key, value) 482 | 483 | @doc """ 484 | Deletes `{key, value}` pair from `bimultimap`. 485 | 486 | If the `key` does not exist, or `value` does not match, returns `bimultimap` 487 | unchanged. 488 | 489 | ## Examples 490 | 491 | iex> bimultimap = BiMultiMap.new([a: 1, b: 2, c: 2]) 492 | iex> BiMultiMap.delete(bimultimap, :b, 2) 493 | BiMultiMap.new([a: 1, c: 2]) 494 | iex> BiMultiMap.delete(bimultimap, :c, 3) 495 | BiMultiMap.new([a: 1, b: 2, c: 2]) 496 | """ 497 | @spec delete(t, k, v) :: t 498 | def delete( 499 | %BiMultiMap{keys: keys, values: values, size: size} = bimultimap, 500 | key, 501 | value 502 | ) do 503 | {upd, keys} = delete_side(keys, key, value) 504 | {^upd, values} = delete_side(values, value, key) 505 | 506 | size = 507 | if upd do 508 | size - 1 509 | else 510 | size 511 | end 512 | 513 | %{bimultimap | keys: keys, values: values, size: size} 514 | end 515 | 516 | defp delete_side(keys, key, value) do 517 | case Map.fetch(keys, key) do 518 | {:ok, set} -> 519 | upd = MapSet.member?(set, value) 520 | set = MapSet.delete(set, value) 521 | 522 | keys = 523 | if MapSet.size(set) == 0 do 524 | Map.delete(keys, key) 525 | else 526 | put_in(keys[key], set) 527 | end 528 | 529 | {upd, keys} 530 | 531 | :error -> 532 | {false, keys} 533 | end 534 | end 535 | 536 | @doc """ 537 | Deletes `{key, _}` pair from `bimultimap`. 538 | 539 | If the `key` does not exist, returns `bimultimap` unchanged. 540 | 541 | ## Examples 542 | 543 | iex> bimultimap = BiMultiMap.new([a: 1, b: 2, b: 3]) 544 | iex> BiMultiMap.delete_key(bimultimap, :b) 545 | BiMultiMap.new([a: 1]) 546 | iex> BiMultiMap.delete_key(bimultimap, :c) 547 | BiMultiMap.new([a: 1, b: 2, b: 3]) 548 | """ 549 | @spec delete_key(t, k) :: t 550 | def delete_key(%BiMultiMap{keys: keys} = bimultimap, key) do 551 | case Map.fetch(keys, key) do 552 | {:ok, values} -> 553 | Enum.reduce(values, bimultimap, fn value, map -> 554 | delete(map, key, value) 555 | end) 556 | 557 | :error -> 558 | bimultimap 559 | end 560 | end 561 | 562 | @doc """ 563 | Deletes `{_, value}` pair from `bimultimap`. 564 | 565 | If the `value` does not exist, returns `bimultimap` unchanged. 566 | 567 | ## Examples 568 | 569 | iex> bimultimap = BiMultiMap.new([a: 1, b: 2, c: 1]) 570 | iex> BiMultiMap.delete_value(bimultimap, 1) 571 | BiMultiMap.new([b: 2]) 572 | iex> BiMultiMap.delete_value(bimultimap, 3) 573 | BiMultiMap.new([a: 1, b: 2, c: 1]) 574 | """ 575 | @spec delete_value(t, v) :: t 576 | def delete_value(%BiMultiMap{values: values} = bimultimap, value) do 577 | case Map.fetch(values, value) do 578 | {:ok, keys} -> 579 | Enum.reduce(keys, bimultimap, fn key, map -> 580 | delete(map, key, value) 581 | end) 582 | 583 | :error -> 584 | bimultimap 585 | end 586 | end 587 | 588 | @doc """ 589 | Convenience shortcut for `delete/3`. 590 | """ 591 | @spec delete(t, {k, v}) :: t 592 | def delete(bimultimap, kv) 593 | def delete(bimultimap, {key, value}), do: delete(bimultimap, key, value) 594 | 595 | @doc """ 596 | Returns list of unique key-value pairs in `bimultimap`. 597 | 598 | ## Examples 599 | 600 | iex> bimultimap = BiMultiMap.new([a: "foo", b: "bar"]) 601 | iex> BiMultiMap.to_list(bimultimap) 602 | [a: "foo", b: "bar"] 603 | """ 604 | @spec to_list(t) :: [{k, v}] 605 | def to_list(bimultimap) 606 | 607 | def to_list(%BiMultiMap{keys: keys}) do 608 | for {k, vs} <- keys, v <- vs do 609 | {k, v} 610 | end 611 | end 612 | 613 | defimpl Enumerable do 614 | def reduce(bimultimap, acc, fun) do 615 | Enumerable.List.reduce(BiMultiMap.to_list(bimultimap), acc, fun) 616 | end 617 | 618 | def member?(bimultimap, val) do 619 | {:ok, BiMultiMap.member?(bimultimap, val)} 620 | end 621 | 622 | def count(bimultimap) do 623 | {:ok, BiMultiMap.size(bimultimap)} 624 | end 625 | 626 | def slice(_bimultimap) do 627 | {:error, __MODULE__} 628 | end 629 | end 630 | 631 | defimpl Collectable do 632 | def into(original) do 633 | {original, 634 | fn 635 | bimultimap, {:cont, pair} -> BiMultiMap.put(bimultimap, pair) 636 | bimultimap, :done -> bimultimap 637 | _, :halt -> :ok 638 | end} 639 | end 640 | end 641 | 642 | defimpl Inspect do 643 | import Inspect.Algebra 644 | 645 | def inspect(bimultimap, opts) do 646 | concat(["BiMultiMap.new(", to_doc(BiMultiMap.to_list(bimultimap), opts), ")"]) 647 | end 648 | end 649 | end 650 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule BiMap.Mixfile do 2 | use Mix.Project 3 | 4 | @version "1.3.0" 5 | @github "https://github.com/mkaput/elixir-bimap" 6 | 7 | def project do 8 | [ 9 | app: :bimap, 10 | name: "BiMap", 11 | description: description(), 12 | version: @version, 13 | elixir: "~> 1.3", 14 | source_url: @github, 15 | docs: [ 16 | source_ref: "v#{@version}", 17 | main: "readme", 18 | extras: ~w(README.md) 19 | ], 20 | deps: deps(), 21 | package: package() 22 | ] 23 | end 24 | 25 | # Run "mix help compile.app" to learn about applications. 26 | def application do 27 | [] 28 | end 29 | 30 | # Run "mix help deps" to learn about dependencies. 31 | defp deps do 32 | [ 33 | # Development dependencies 34 | {:ex_doc, "~> 0.31", only: :dev, runtime: false}, 35 | {:stream_data, "~> 0.5.0", only: [:dev, :test]} 36 | ] 37 | end 38 | 39 | defp description() do 40 | "Elixir implementation of bidirectional map and multimap" 41 | end 42 | 43 | defp package() do 44 | [ 45 | files: ~w( 46 | lib 47 | mix.exs 48 | README.md 49 | LICENSE.txt 50 | ), 51 | maintainers: ["Marek Kaput "], 52 | licenses: ["MIT"], 53 | links: %{ 54 | "GitHub" => @github, 55 | "Changelog" => "#{@github}/releases" 56 | } 57 | ] 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, 3 | "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, 4 | "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, 5 | "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, 6 | "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, 7 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, 8 | "ex_doc": {:hex, :ex_doc, "0.37.0", "970f92b39e62c460aa8a367508e938f5e4da6e2ff3eaed3f8530b25870f45471", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "b0ee7f17373948e0cf471e59c3a0ee42f3bd1171c67d91eb3626456ef9c6202c"}, 9 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, 10 | "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, 11 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, 12 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, 13 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, 14 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, 15 | "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, 16 | } 17 | -------------------------------------------------------------------------------- /test/bimap_properties_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BiMapPropertiesTest do 2 | use ExUnit.Case, async: true 3 | use ExUnitProperties 4 | 5 | property "finds present items in bimap" do 6 | check all key_set <- nonempty(uniq_list_of(term())), 7 | value_set <- uniq_list_of(term(), length: Enum.count(key_set)) do 8 | regular_map = Enum.zip(key_set, value_set) |> Enum.into(%{}) 9 | bimap = BiMap.new(regular_map) 10 | {random_key, random_value} = Enum.random(regular_map) 11 | 12 | assert BiMap.fetch(bimap, random_key) == {:ok, random_value} 13 | assert BiMap.fetch_key(bimap, random_value) == {:ok, random_key} 14 | end 15 | end 16 | 17 | property "deletes items from bimap" do 18 | check all key_set <- nonempty(uniq_list_of(term())), 19 | value_set <- uniq_list_of(term(), length: Enum.count(key_set)) do 20 | regular_map = Enum.zip(key_set, value_set) |> Enum.into(%{}) 21 | bimap = BiMap.new(regular_map) 22 | {random_key, random_value} = Enum.random(regular_map) 23 | 24 | assert BiMap.equal?( 25 | BiMap.delete(bimap, {random_key, random_value}), 26 | Map.delete(regular_map, random_key) |> BiMap.new() 27 | ) 28 | 29 | assert BiMap.equal?( 30 | BiMap.delete(bimap, random_key, random_value), 31 | Map.delete(regular_map, random_key) |> BiMap.new() 32 | ) 33 | 34 | assert BiMap.equal?( 35 | BiMap.delete_key(bimap, random_key), 36 | Map.delete(regular_map, random_key) |> BiMap.new() 37 | ) 38 | 39 | assert BiMap.equal?( 40 | BiMap.delete_value(bimap, random_value), 41 | Map.delete(regular_map, random_key) |> BiMap.new() 42 | ) 43 | end 44 | end 45 | 46 | property "it turns bimaps into lists" do 47 | check all key_set <- nonempty(uniq_list_of(term())), 48 | value_set <- uniq_list_of(term(), length: Enum.count(key_set)) do 49 | regular_map = Enum.zip(key_set, value_set) |> Enum.into(%{}) 50 | bimap = BiMap.new(regular_map) 51 | 52 | assert BiMap.to_list(bimap) |> MapSet.new() == Map.to_list(regular_map) |> MapSet.new() 53 | end 54 | end 55 | 56 | property "it puts items into bimaps" do 57 | check all key_set <- nonempty(uniq_list_of(term())), 58 | value_set <- uniq_list_of(term(), length: Enum.count(key_set)), 59 | random_key <- term(), 60 | random_value <- term() do 61 | regular_map = Enum.zip(key_set, value_set) |> Enum.into(%{}) 62 | bimap = BiMap.new(regular_map) 63 | 64 | put_regular_map = 65 | regular_map 66 | |> Enum.reject(fn {_, v} -> v === random_value end) 67 | |> Enum.into(%{}) 68 | |> Map.put(random_key, random_value) 69 | 70 | put_bimap = BiMap.put(bimap, random_key, random_value) 71 | 72 | assert BiMap.equal?(put_bimap, BiMap.new(put_regular_map)) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/bimap_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BiMapTest do 2 | use ExUnit.Case, async: true 3 | doctest BiMap 4 | end 5 | -------------------------------------------------------------------------------- /test/bimultimap_properties_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BiMultiMapPropertiesTest do 2 | use ExUnit.Case, async: true 3 | use ExUnitProperties 4 | require BiMultiMap 5 | 6 | property "finds present items in bimultimap" do 7 | check all key_set <- nonempty(list_of(term())), 8 | value_set <- list_of(term(), length: Enum.count(key_set)) do 9 | kv_list = Enum.zip(key_set, value_set) |> MapSet.new() 10 | bimultimap = BiMultiMap.new(kv_list) 11 | {random_key, random_value} = Enum.random(bimultimap) 12 | 13 | kv_list_values = 14 | kv_list 15 | |> Enum.filter(fn {k, _v} -> k === random_key end) 16 | |> Enum.map(fn {_k, v} -> v end) 17 | 18 | kv_list_keys = 19 | kv_list 20 | |> Enum.filter(fn {_k, v} -> v === random_value end) 21 | |> Enum.map(fn {k, _v} -> k end) 22 | 23 | {:ok, bimultimap_values} = BiMultiMap.fetch(bimultimap, random_key) 24 | {:ok, bimultimap_keys} = BiMultiMap.fetch_keys(bimultimap, random_value) 25 | 26 | assert bimultimap_values |> Enum.sort() == kv_list_values |> Enum.sort() 27 | assert bimultimap_keys |> Enum.sort() == kv_list_keys |> Enum.sort() 28 | end 29 | end 30 | 31 | property "deletes items from bimultimap" do 32 | check all key_set <- nonempty(list_of(term())), 33 | value_set <- list_of(term(), length: Enum.count(key_set)) do 34 | kv_list = Enum.zip(key_set, value_set) |> MapSet.new() 35 | bimultimap = BiMultiMap.new(kv_list) 36 | {random_key, random_value} = Enum.random(bimultimap) 37 | 38 | bimultimap_comparison_delete_keys = 39 | kv_list 40 | |> Enum.reject(fn {k, _v} -> k === random_key end) 41 | |> BiMultiMap.new() 42 | 43 | bimultimap_comparison_delete_values = 44 | kv_list 45 | |> Enum.reject(fn {_k, v} -> v === random_value end) 46 | |> BiMultiMap.new() 47 | 48 | deleted_key_bimultimap = BiMultiMap.delete_key(bimultimap, random_key) 49 | deleted_value_bimultimap = BiMultiMap.delete_value(bimultimap, random_value) 50 | 51 | assert BiMultiMap.equal?(deleted_value_bimultimap, bimultimap_comparison_delete_values) 52 | assert BiMultiMap.equal?(deleted_key_bimultimap, bimultimap_comparison_delete_keys) 53 | end 54 | end 55 | 56 | property "it turns bimultimaps into lists" do 57 | check all key_set <- nonempty(uniq_list_of(term())), 58 | value_set <- list_of(term(), length: Enum.count(key_set)) do 59 | kv_list = Enum.zip(key_set, value_set) |> MapSet.new() 60 | bimultimap = BiMultiMap.new(kv_list) 61 | 62 | assert BiMultiMap.to_list(bimultimap) |> MapSet.new() == kv_list 63 | end 64 | end 65 | 66 | property "it puts items into bimultimaps" do 67 | check all key_set <- nonempty(uniq_list_of(term())), 68 | value_set <- list_of(term(), length: Enum.count(key_set)), 69 | random_key <- term(), 70 | random_value <- term() do 71 | kv_list = Enum.zip(key_set, value_set) |> MapSet.new() 72 | bimultimap = BiMultiMap.new(kv_list) 73 | 74 | put_kv_list = MapSet.put(kv_list, {random_key, random_value}) 75 | put_bimultimap = BiMultiMap.put(bimultimap, random_key, random_value) 76 | 77 | assert BiMultiMap.equal?(put_bimultimap, BiMultiMap.new(put_kv_list)) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/bimultimap_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BiMultiMapTest do 2 | use ExUnit.Case, async: true 3 | doctest BiMultiMap 4 | 5 | test "size is correctly computed on put" do 6 | # we can't use new/1 here, because it may compute size on its own 7 | map = 8 | BiMultiMap.new() 9 | |> BiMultiMap.put(:a, 1) 10 | |> BiMultiMap.put(:a, 2) 11 | |> BiMultiMap.put(:b, 2) 12 | 13 | assert BiMultiMap.size(map) == 3 14 | end 15 | 16 | test "size is correclty computed on new/1" do 17 | map = BiMultiMap.new(a: 1, b: 2, c: 2) 18 | assert BiMultiMap.size(map) == 3 19 | end 20 | 21 | test "size is correclty computed on delete/3" do 22 | map = BiMultiMap.new(a: 1, b: 2, c: 2) 23 | assert BiMultiMap.size(map) == 3 24 | map = BiMultiMap.delete(map, :b, 2) 25 | assert BiMultiMap.size(map) == 2 26 | map = BiMultiMap.delete(map, :c, 2) 27 | assert BiMultiMap.size(map) == 1 28 | map = BiMultiMap.delete(map, :a, 1) 29 | assert BiMultiMap.size(map) == 0 30 | end 31 | 32 | test "delete/3 removes empty value sets" do 33 | map = BiMultiMap.new(a: 1, a: 2) 34 | assert BiMultiMap.has_key?(map, :a) 35 | map = BiMultiMap.delete(map, :a, 1) 36 | assert BiMultiMap.has_key?(map, :a) 37 | map = BiMultiMap.delete(map, :a, 2) 38 | refute BiMultiMap.has_key?(map, :a) 39 | end 40 | 41 | test "delete/3 removes empty key sets" do 42 | map = BiMultiMap.new(a: 1, b: 1) 43 | assert BiMultiMap.has_value?(map, 1) 44 | map = BiMultiMap.delete(map, :a, 1) 45 | assert BiMultiMap.has_value?(map, 1) 46 | map = BiMultiMap.delete(map, :b, 1) 47 | refute BiMultiMap.has_value?(map, 1) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------