├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config └── config.exs ├── lib └── sorted_set.ex ├── mix.exs ├── mix.lock └── test ├── sorted_set_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | /doc 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | otp_release: 3 | - 17.4 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Maddox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SortedSet 2 | [![Hex.pm](https://img.shields.io/hexpm/v/sorted_set.svg)](https://hex.pm/packages/sorted_set) [![Travis](https://img.shields.io/travis/SenecaSystems/sorted_set.svg)](https://travis-ci.org/SenecaSystems/sorted_set) 3 | 4 | 5 | A sorted set library for Elixir. Implements the 6 | [Set](http://elixir-lang.org/docs/v1.0/elixir/Set.html) protocol. 7 | 8 | ## Installation 9 | 10 | Add the following to `deps` section of your `mix.exs`: 11 | `{:sorted_set, "~> 1.0"}` 12 | 13 | and then `mix deps.get`. That's it! 14 | 15 | Generate the documentation with `mix docs`. 16 | 17 | ## About 18 | 19 | Sorted sets are backed by a [red-black tree](http://en.wikipedia.org/wiki/Red%E2%80%93black_tree), providing lookup in O(log(n)). Size is tracked automatically, resulting in O(1) 20 | performance. 21 | 22 | 23 | ## Basic Usage 24 | 25 | `SortedSet` implements the `Set` behaviour, `Enumerable`, and `Collectable`. 26 | 27 | ```elixir 28 | SortedSet.new() 29 | |> Set.put(5) 30 | |> Set.put(1) 31 | |> Set.put(3) 32 | |> Enum.reduce([], fn (element, acc) -> [element*2|acc] end) 33 | |> Enum.reverse 34 | # => [2, 6, 10] 35 | ``` 36 | 37 | ## Custom Comparison 38 | 39 | Sorted Set can also take a custom `:comparator` function to determine ordering. The 40 | function should accept two terms and 41 | 42 | - return `0` if they are considered equal 43 | - return `-1` if the first is considered less than or before the second 44 | - return `1` if the first is considered greater than or after the second 45 | 46 | This function is passed on to the underlying [red-black tree implementation](https://github.com/SenecaSystems/red_black_tree) implemetation. Otherwise, the 47 | default Erlang term comparison is used (with an extra bit to handle edgecases — see note in [RedBlackTree](https://github.com/SenecaSystems/red_black_tree) 48 | README.) 49 | 50 | ```elixir 51 | SortedSet.new([:a, :b, :c], comparator: fn (term1, term2) -> 52 | RedBlackTree.compare_terms(term1, term2) * -1 53 | end) 54 | # => #SortedSet<[:c, :b, :a]> 55 | ``` 56 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /lib/sorted_set.ex: -------------------------------------------------------------------------------- 1 | defmodule SortedSet do 2 | alias RedBlackTree 3 | @moduledoc """ 4 | A Set implementation that always remains sorted. 5 | 6 | SortedSet guarantees that no element appears more than once and that 7 | enumerating over members happens in their sorted order. 8 | """ 9 | 10 | @behaviour Set 11 | 12 | @default_comparator &RedBlackTree.compare_terms/2 13 | 14 | # Define the type as opaque 15 | 16 | @opaque t :: %__MODULE__{members: RedBlackTree, size: non_neg_integer} 17 | @doc false 18 | defstruct members: RedBlackTree.new, size: 0 19 | 20 | @doc ~S""" 21 | Returns a new `SortedSet`, initialized with the unique, sorted values of 22 | `members`. 23 | 24 | ## Options 25 | - `:comparator` function taking two terms and deciding their order. Passed 26 | on to the underlying data structure, in this case a Red-Black tree. The 27 | default is to compare based on standard Erlang term comparison. To learn 28 | more about this option, see the examples given for 29 | [RedBlackTree](https://github.com/SenecaSystems/red_black_tree) 30 | 31 | ## Examples 32 | 33 | iex> SortedSet.new() 34 | #SortedSet<[]> 35 | 36 | iex> SortedSet.new([1,3,5]) 37 | #SortedSet<[1, 3, 5]> 38 | 39 | iex> SortedSet.new([:a, :b, :c], comparator: fn (term1, term2) -> 40 | ...> RedBlackTree.compare_terms(term1, term2) * -1 41 | ...> end) 42 | #SortedSet<[:c, :b, :a]> 43 | """ 44 | def new(members \\ [], options \\ []) 45 | def new(members, options) do 46 | comparator = :proplists.get_value(:comparator, options, @default_comparator) 47 | new_set = %SortedSet{ 48 | members: RedBlackTree.new([], comparator: comparator) 49 | } 50 | 51 | Enum.reduce(members, new_set, fn(member, set) -> 52 | put(set, member) 53 | end) 54 | end 55 | 56 | @doc ~S""" 57 | Returns the number of elements in a `SortedSet`. 58 | 59 | ## Examples 60 | 61 | iex> SortedSet.size SortedSet.new([1,3,5]) 62 | 3 63 | """ 64 | def size(%SortedSet{size: size}) do 65 | size 66 | end 67 | 68 | @doc ~S""" 69 | Returns a `List` with all of the members of `set`. 70 | 71 | ## Examples 72 | 73 | iex> SortedSet.to_list SortedSet.new([1,3,5]) 74 | [1,3,5] 75 | """ 76 | def to_list(%SortedSet{members: members}) do 77 | Enum.reduce(members, [], fn ({key, _value}, acc) -> 78 | [key | acc] 79 | end) |> Enum.reverse 80 | end 81 | 82 | @doc ~S""" 83 | Returns a `SortedSet` with all of the members of `set` plus `element`. 84 | 85 | ## Examples 86 | 87 | iex> set = SortedSet.new([1,3,5]) 88 | iex> SortedSet.to_list SortedSet.put(set, 1) 89 | [1,3,5] 90 | 91 | iex> set = SortedSet.new([1,3,5]) 92 | iex> SortedSet.to_list SortedSet.put(set, 2) 93 | [1,2,3,5] 94 | """ 95 | def put(%SortedSet{members: members}, element) do 96 | new_tree = RedBlackTree.insert members, element, element 97 | %SortedSet{members: new_tree, size: new_tree.size} 98 | end 99 | 100 | @doc ~S""" 101 | Returns a `SortedSet` with all of the members of `sortedset` except for `element`. 102 | 103 | ## Examples 104 | 105 | iex> set = SortedSet.new([1,3,5]) 106 | iex> SortedSet.to_list SortedSet.delete(set, 1) 107 | [3,5] 108 | 109 | iex> set = SortedSet.new([1,3,5]) 110 | iex> SortedSet.to_list SortedSet.delete(set, 2) 111 | [1,3,5] 112 | 113 | iex> set = SortedSet.new([]) 114 | iex> SortedSet.to_list SortedSet.delete(set, 2) 115 | [] 116 | """ 117 | def delete(%SortedSet{members: members}, element) do 118 | new_tree = RedBlackTree.delete members, element 119 | %SortedSet{members: new_tree, size: new_tree.size} 120 | end 121 | 122 | ## SortedSet predicate methods 123 | 124 | @doc ~S""" 125 | Returns `true` if `set` contains `element` 126 | 127 | ## Examples 128 | 129 | iex> set = SortedSet.new([1,3,5]) 130 | iex> SortedSet.member?(set, 1) 131 | true 132 | 133 | iex> set = SortedSet.new([1,3,5]) 134 | iex> SortedSet.member?(set, 0) 135 | false 136 | """ 137 | def member?(%SortedSet{members: tree}, element) do 138 | RedBlackTree.has_key? tree, element 139 | end 140 | 141 | # If the sizes are not equal, no need to check members 142 | def equal?(%SortedSet{size: size1}, %SortedSet{size: size2}) when size1 != size2 do 143 | false 144 | end 145 | 146 | @doc ~S""" 147 | Returns `true` if all elements in `set1` are in `set2` and all elements in 148 | `set2` are in `set1` 149 | 150 | ## Examples 151 | 152 | iex> set1 = SortedSet.new([1,3,5]) 153 | iex> set2 = SortedSet.new([1,3,5]) 154 | iex> SortedSet.equal?(set1, set2) 155 | true 156 | 157 | iex> set1 = SortedSet.new([1,3,5]) 158 | iex> set2 = SortedSet.new([1,2,3,4,5]) 159 | iex> SortedSet.equal?(set1, set2) 160 | false 161 | """ 162 | def equal?(%SortedSet{}=set1, %SortedSet{}=set2) do 163 | Enum.all?(to_list(set1), fn(set1_member) -> 164 | member? set2, set1_member 165 | end) 166 | end 167 | 168 | def subset?(%SortedSet{size: size1}, %SortedSet{size: size2}) when size1 > size2 do 169 | false 170 | end 171 | 172 | @doc ~S""" 173 | Returns `true` if all elements in `set1` are in `set2` 174 | 175 | ## Examples 176 | 177 | iex> set1 = SortedSet.new([1,3,5]) 178 | iex> set2 = SortedSet.new([1,2,3,4,5]) 179 | iex> SortedSet.subset?(set1, set2) 180 | true 181 | 182 | iex> set1 = SortedSet.new([1,2,3,4,5]) 183 | iex> set2 = SortedSet.new([1,3,5]) 184 | iex> SortedSet.subset?(set1, set2) 185 | false 186 | """ 187 | # If set1 is larger than set2, it cannot be a subset of it 188 | def subset?(%SortedSet{}=set1, %SortedSet{}=set2) do 189 | Enum.all?(to_list(set1), fn(set1_member) -> 190 | member? set2, set1_member 191 | end) 192 | end 193 | 194 | @doc ~S""" 195 | Returns `true` if no member of `set1` is in `set2`. Otherwise returns 196 | `false`. 197 | 198 | ## Examples 199 | 200 | iex> set1 = SortedSet.new([1,2,3,4]) 201 | iex> set2 = SortedSet.new([5,6,7,8]) 202 | iex> SortedSet.disjoint?(set1, set2) 203 | true 204 | 205 | iex> set1 = SortedSet.new([1,2,3,4]) 206 | iex> set2 = SortedSet.new([4,5,6,7]) 207 | iex> SortedSet.disjoint?(set1, set2) 208 | false 209 | """ 210 | def disjoint?(%SortedSet{size: size1}=set1, %SortedSet{size: size2}=set2) when size1 <= size2 do 211 | not Enum.any?(to_list(set1), fn(set1_member) -> 212 | member?(set2, set1_member) 213 | end) 214 | end 215 | 216 | def disjoint?(%SortedSet{}=set1, %SortedSet{}=set2) do 217 | disjoint?(set2, set1) 218 | end 219 | 220 | ## SortedSet Operations 221 | 222 | @doc ~S""" 223 | Returns a `SortedSet` containing the items of both `set1` and `set2`. 224 | 225 | ## Examples 226 | 227 | iex> set1 = SortedSet.new([1,3,5,7]) 228 | iex> set2 = SortedSet.new([0,2,3,4,5]) 229 | iex> SortedSet.to_list SortedSet.union(set1, set2) 230 | [0,1,2,3,4,5,7] 231 | """ 232 | def union(%SortedSet{size: size1}=set1, %SortedSet{size: size2}=set2) when size1 <= size2 do 233 | Enum.reduce(to_list(set1), set2, fn(member, new_set) -> 234 | put(new_set, member) 235 | end) 236 | end 237 | 238 | def union(%SortedSet{}=set1, %SortedSet{}=set2) do 239 | union(set2, set1) 240 | end 241 | 242 | # If either set is empty, the intersection is the empty set 243 | def intersection(%SortedSet{size: 0}=set1, _) do 244 | set1 245 | end 246 | 247 | # If either set is empty, the intersection is the empty set 248 | def intersection(_, %SortedSet{size: 0}=set2) do 249 | set2 250 | end 251 | 252 | @doc ~S""" 253 | Returns a `SortedSet` containing the items contained in both `set1` and 254 | `set2`. 255 | 256 | ## Examples 257 | 258 | iex> set1 = SortedSet.new([1,3,5,7]) 259 | iex> set2 = SortedSet.new([0,2,3,4,5]) 260 | iex> SortedSet.to_list SortedSet.intersection(set1, set2) 261 | [3,5] 262 | """ 263 | def intersection(%SortedSet{size: size1}=set1, %SortedSet{size: size2}=set2) when size1 <= size2 do 264 | Enum.reduce(to_list(set1), SortedSet.new, fn(set1_member, new_set) -> 265 | if SortedSet.member?(set2, set1_member) do 266 | SortedSet.put(new_set, set1_member) 267 | else 268 | new_set 269 | end 270 | end) 271 | end 272 | 273 | def intersection(%SortedSet{}=set1, %SortedSet{}=set2) do 274 | intersection(set2, set1) 275 | end 276 | 277 | @doc ~S""" 278 | Returns a `SortedSet` containing the items in `set1` that are not in `set2`. 279 | 280 | ## Examples 281 | 282 | iex> set1 = SortedSet.new([1,2,3,4]) 283 | iex> set2 = SortedSet.new([2,4,6,8]) 284 | iex> SortedSet.to_list SortedSet.difference(set1, set2) 285 | [1,3] 286 | """ 287 | def difference(%SortedSet{size: size1}=set1, %SortedSet{size: size2}=set2) when size1 > 0 and size2 > 0 do 288 | Enum.reduce(to_list(set1), set1, fn(set1_member, new_set) -> 289 | if SortedSet.member?(set2, set1_member) do 290 | delete(new_set, set1_member) 291 | else 292 | new_set 293 | end 294 | end) 295 | end 296 | 297 | # When the first set is empty, the difference is the empty set 298 | def difference(%SortedSet{size: 0}=empty_set, _) do 299 | empty_set 300 | end 301 | 302 | # When the other set is empty, the difference is the first set 303 | def difference(%SortedSet{}=set1, %SortedSet{size: 0}) do 304 | set1 305 | end 306 | end 307 | 308 | defimpl Enumerable, for: SortedSet do 309 | def count(%SortedSet{size: size}), do: {:ok, size} 310 | def member?(%SortedSet{}=set, element), do: {:ok, SortedSet.member?(set, element)} 311 | def reduce(%SortedSet{}=set, acc, fun) do 312 | SortedSet.to_list(set) 313 | |> Enumerable.List.reduce(acc, fun) 314 | end 315 | end 316 | 317 | defimpl Collectable, for: SortedSet do 318 | def into(original) do 319 | {original, fn 320 | set, {:cont, new_member} -> SortedSet.put(set, new_member) 321 | set, :done -> set 322 | _, :halt -> :ok 323 | end} 324 | end 325 | end 326 | 327 | # We want our own inspect so that it will hide the underlying :members and :size 328 | # fields. Otherwise users may try to play with them directly. 329 | defimpl Inspect, for: SortedSet do 330 | import Inspect.Algebra 331 | 332 | def inspect(set, opts) do 333 | concat ["#SortedSet<", Inspect.List.inspect(SortedSet.to_list(set), opts), ">"] 334 | end 335 | end 336 | 337 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule SortedSet.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :sorted_set, 6 | version: "1.1.0", 7 | source_url: "https://github.com/SenecaSystems/sorted_set", 8 | elixir: "~> 1.0", 9 | description: "SortedSet implementation for Elixir", 10 | licenses: ["MIT"], 11 | deps: deps, 12 | package: package 13 | ] 14 | end 15 | 16 | def application do 17 | [applications: [:logger]] 18 | end 19 | 20 | defp deps do 21 | [ 22 | {:red_black_tree, "~> 1.2"}, 23 | {:earmark, "~> 0.1", only: :dev}, 24 | {:ex_doc, "~> 0.7", only: :dev} 25 | ] 26 | end 27 | 28 | defp package do 29 | [ 30 | files: ["lib", "mix.exs", "README.md", "LICENSE"], 31 | contributors: ["Seneca Systems"], 32 | licenses: ["MIT"], 33 | links: %{"GitHub" => "https://github.com/SenecaSystems/sorted_set"} 34 | ] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "0.1.16"}, 2 | "ex_doc": {:hex, :ex_doc, "0.7.2"}, 3 | "red_black_tree": {:hex, :red_black_tree, "1.2.0"}} 4 | -------------------------------------------------------------------------------- /test/sorted_set_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SortedSetTest do 2 | use ExUnit.Case, async: true 3 | doctest SortedSet 4 | 5 | 6 | test "it creates an empty set with size 0" do 7 | assert 0 == SortedSet.size SortedSet.new 8 | end 9 | 10 | test "it sorts an existing list on creation" do 11 | assert [1,3,5] == SortedSet.to_list SortedSet.new [1,5,3] 12 | end 13 | 14 | test "it can put an element into the set" do 15 | new_set = SortedSet.put SortedSet.new, 1 16 | assert [1] == SortedSet.to_list new_set 17 | assert 1 == SortedSet.size new_set 18 | end 19 | 20 | test "it puts elements in sorted order" do 21 | set = SortedSet.put(SortedSet.new(), 2) 22 | |> SortedSet.put(3) 23 | |> SortedSet.put(1) 24 | assert [1,2,3] == SortedSet.to_list(set) 25 | assert 3 == SortedSet.size set 26 | end 27 | 28 | test "it can delete from the set" do 29 | set = SortedSet.new([1,2,3,4,5]) 30 | new_set = SortedSet.delete(set, 3) 31 | assert [1,2,4,5] == SortedSet.to_list new_set 32 | assert 4 == SortedSet.size new_set 33 | 34 | assert [] == SortedSet.to_list SortedSet.delete(SortedSet.new(), 1) 35 | end 36 | 37 | test "it can perform a union on two sorted sets" do 38 | set1 = SortedSet.new([1,2,3,4,5]) 39 | set2 = SortedSet.new([1,3,5,7,9]) 40 | union = SortedSet.union(set1, set2) 41 | assert [1,2,3,4,5,7,9] == SortedSet.to_list union 42 | assert 7 == SortedSet.size union 43 | end 44 | 45 | test "it can tell if a set contains an item" do 46 | assert SortedSet.member?(SortedSet.new([1,2,3]), 1) 47 | assert not SortedSet.member?(SortedSet.new([1,2,3]), 4) 48 | assert not SortedSet.member?(SortedSet.new([]), 4) 49 | end 50 | 51 | test "it can tell if two sets are equal" do 52 | assert SortedSet.equal?(SortedSet.new, SortedSet.new) 53 | assert SortedSet.equal?(SortedSet.new([1,2,3,4]), SortedSet.new([1,2,3,4])) 54 | assert not SortedSet.equal?(SortedSet.new([1,2,3]), SortedSet.new([1,2,4])) 55 | 56 | # Ensure it isn't confused by subsets 57 | assert not SortedSet.equal?(SortedSet.new([1,2,3]), SortedSet.new([1,2])) 58 | # Or supersets 59 | assert not SortedSet.equal?(SortedSet.new([1,2]), SortedSet.new([1,2,3])) 60 | 61 | end 62 | 63 | test "it can tell if one set is the subset of another" do 64 | assert SortedSet.subset?(SortedSet.new, SortedSet.new) 65 | 66 | assert SortedSet.subset?(SortedSet.new([1,2,3]), SortedSet.new([1,2,3,4])) 67 | assert not SortedSet.subset?(SortedSet.new([1,2,3,4]), SortedSet.new([1,2,3])) 68 | end 69 | 70 | test "it can return the intersection of two sets" do 71 | intersection = SortedSet.intersection(SortedSet.new([1,2,3]), SortedSet.new([1,3,5,7,9])) 72 | assert [1,3] == SortedSet.to_list(intersection) 73 | 74 | intersection = SortedSet.intersection(SortedSet.new([1,3,5]), SortedSet.new([2,4,6])) 75 | assert [] == SortedSet.to_list(intersection) 76 | 77 | intersection = SortedSet.intersection(SortedSet.new, SortedSet.new([1,2,3])) 78 | assert [] == SortedSet.to_list(intersection) 79 | end 80 | 81 | test "it can find the difference between two sets" do 82 | # With members in common 83 | difference = SortedSet.difference(SortedSet.new([1,2,3]), SortedSet.new([1,3,5,7,9])) 84 | assert [2] == SortedSet.to_list(difference) 85 | 86 | # With no members in common 87 | difference = SortedSet.difference(SortedSet.new([1,2,3]), SortedSet.new([5,7,9])) 88 | assert [1,2,3] == SortedSet.to_list(difference) 89 | 90 | # When one set is empty 91 | difference = SortedSet.difference(SortedSet.new([]), SortedSet.new([5,7,9])) 92 | assert [] == SortedSet.to_list(difference) 93 | 94 | # When the other set is empty 95 | difference = SortedSet.difference(SortedSet.new([1,2,3]), SortedSet.new([])) 96 | assert [1,2,3] == SortedSet.to_list(difference) 97 | end 98 | 99 | test "it can tell if two sets are disjointed" do 100 | assert SortedSet.disjoint?(SortedSet.new([1,2,3]), SortedSet.new([4,5,6])) 101 | assert SortedSet.disjoint?(SortedSet.new(), SortedSet.new([4,5,6])) 102 | assert SortedSet.disjoint?(SortedSet.new([4,5,6]), SortedSet.new()) 103 | assert not SortedSet.disjoint?(SortedSet.new([1,2,3]), SortedSet.new([3,4,5])) 104 | end 105 | 106 | test "it adheres to the Enumerable protocol" do 107 | assert Enum.member?(SortedSet.new([1,2]), 1) 108 | assert not Enum.member?(SortedSet.new(), 1) 109 | 110 | assert 3 == Enum.count(SortedSet.new([1,2,3])) 111 | assert 0 == Enum.count(SortedSet.new()) 112 | 113 | assert 24 == Enum.reduce(SortedSet.new([1,2,3,4]), 1, fn (n, acc) -> n * acc end) 114 | end 115 | 116 | test "it adheres to the Collectable prototcol" do 117 | assert [1,2,3,4] == SortedSet.to_list(Enum.into([1,3,4,2,3,4], %SortedSet{})) 118 | end 119 | 120 | test "it implements the Inspect protocol" do 121 | assert "#SortedSet<[0, 1, 2, 5, 6]>" == inspect SortedSet.new [1,0,5,2,5,6,2] 122 | assert "#SortedSet<[]>" == inspect SortedSet.new 123 | end 124 | 125 | test "adding a custom comparator" do 126 | reverse_set = SortedSet.new([0,1,2,3,4], comparator: fn (term1, term2) -> 127 | RedBlackTree.compare_terms(term1, term2) * -1 128 | end) 129 | 130 | assert [4,3,2,1,0] == SortedSet.to_list(reverse_set) 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------