├── .formatter.exs ├── .gitignore ├── LICENSE ├── README.md ├── bench └── bit_field_set_bench.exs ├── lib └── bit_field_set.ex ├── mix.exs ├── mix.lock └── test ├── bit_field_set_eqc.exs ├── bit_field_set_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | .eqc-info 7 | current_counterexample.eqc 8 | /bench/snapshots 9 | /bench/graphs 10 | /doc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Martin Gausby 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bit Field Set 2 | 3 | [![Hex.pm](https://img.shields.io/hexpm/l/bit_field_set.svg "Apache 2.0 Licensed")](https://github.com/gausby/bit_field_set/blob/master/LICENSE) 4 | [![Hex version](https://img.shields.io/hexpm/v/bit_field_set.svg "Hex version")](https://hex.pm/packages/bit_field_set) 5 | 6 | Store and manipulate a set of bit flags, mostly used for syncing the state over the wire between peers in a peer to peer network, such as BitTorrent. 7 | 8 | 9 | ## Usage 10 | 11 | ```elixir 12 | # Create a new bit field set with the new command. 13 | # (initial content, and size in bits) 14 | bitfield = BitFieldSet.new!(<<0b00110001>>, 8) 15 | # => #BitFieldSet<[2, 3, 7]> 16 | 17 | # set the first bit 18 | bitfield = BitFieldSet.put(bitfield, 0) 19 | # => #BitFieldSet<[0, 2, 3, 7]> 20 | 21 | bitfield = BitFieldSet.delete(bitfield, 3) 22 | # => #BitFieldSet<[0, 2, 7]> 23 | 24 | BitFieldSet.to_binary(bitfield) 25 | # => <<161>> 26 | ``` 27 | 28 | 29 | ## Installation 30 | 31 | Bit Field Set is [available in Hex](https://hex.pm/packages/bit_field_set), the package can be installed by adding bit_field_set to your list of dependencies in `mix.exs`: 32 | 33 | ``` elixir 34 | def deps do 35 | [{:bit_field_set, "~> 1.2.0"}] 36 | end 37 | ``` 38 | 39 | This module does not need to be started as an application, just use it as is. 40 | 41 | 42 | ## Development 43 | 44 | Fork the project and fetch the dependencies. 45 | 46 | * The project uses [QuickCheck for Elixir](https://github.com/Quviq/eqc_ex/) from [Quviq](http://quviq.com/) to test its behavior. Please download an follow the install instructions for QuickCheck Mini to run the property tests. 47 | 48 | * Benchmarks are performed by [Benchfella](https://github.com/alco/benchfella), a project by [Alexei Sholik](https://github.com/alco). Nothing special is needed besides fetching the mix dependencies. 49 | 50 | 51 | ## License 52 | 53 | Copyright 2016 Martin Gausby 54 | 55 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 60 | -------------------------------------------------------------------------------- /bench/bit_field_set_bench.exs: -------------------------------------------------------------------------------- 1 | defmodule BitFieldSetBench do 2 | use Benchfella 3 | 4 | @empty <<0::size(8000)>> 5 | @half_full IO.iodata_to_binary(for _ <- 1..1000, do: 170) 6 | @full IO.iodata_to_binary(for _ <- 1..1000, do: 255) 7 | 8 | @half_full_set BitFieldSet.new!(@half_full, 8000) 9 | 10 | # all bits set to 0 11 | bench "empty-bitfield" do 12 | BitFieldSet.new!(@empty, 8000) 13 | end 14 | 15 | # every other bit set to 1 16 | bench "half-full-bitfield" do 17 | BitFieldSet.new!(@full, 8000) 18 | end 19 | 20 | # every bit set to 1 21 | bench "full-bitfield" do 22 | BitFieldSet.new!(@full, 8000) 23 | end 24 | 25 | bench "count set bits" do 26 | BitFieldSet.size(@half_full_set) == 4000 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/bit_field_set.ex: -------------------------------------------------------------------------------- 1 | defmodule BitFieldSet do 2 | @moduledoc false 3 | 4 | use Bitwise 5 | 6 | @type piece_index :: non_neg_integer 7 | @type size :: non_neg_integer 8 | @type errors :: :out_of_bounds | :bit_field_size_too_small 9 | 10 | @opaque t :: %__MODULE__{size: size, pieces: non_neg_integer} 11 | defstruct size: 0, pieces: 0 12 | 13 | @doc """ 14 | Create a new bit field set given a `size` (an integer denoting the 15 | bit size of the bit field) *and* some optional initialization 16 | `content` (a binary, the size of it should not exceed the size of 17 | the bit field). 18 | 19 | iex> BitFieldSet.new!(16) |> BitFieldSet.to_binary() 20 | <<0, 0>> 21 | 22 | The target size should be specified when a bit field is initialized 23 | with data. 24 | 25 | iex> BitFieldSet.new!(<<128, 1>>, 16) 26 | #BitFieldSet<[0, 15]> 27 | 28 | """ 29 | @spec new(binary, size) :: {:ok, t} | {:error, errors} 30 | def new(data \\ <<>>, size) 31 | def new(<<>>, size), do: {:ok, %__MODULE__{size: size, pieces: 0}} 32 | def new(_, size) when size <= 0, do: {:error, :bit_field_size_too_small} 33 | 34 | def new(data, size) when is_binary(data) and bit_size(data) - size < 8 do 35 | actual_size = bitfield_size(size) 36 | <> = data 37 | 38 | with bitfield = %__MODULE__{size: size, pieces: pieces}, 39 | {:ok, bitfield} <- validate_trailing_bits(bitfield), 40 | {:ok, bitfield} <- drop_tailing_bits(bitfield) do 41 | {:ok, bitfield} 42 | end 43 | end 44 | 45 | def new(data, size) when is_bitstring(data) and bit_size(data) - size < 8 do 46 | data_size = bit_size(data) 47 | <> = data 48 | bitfield = %__MODULE__{size: size, pieces: pieces} 49 | {:ok, bitfield} 50 | end 51 | 52 | def new(_content, _size), do: {:error, :out_of_bounds} 53 | 54 | # Trailing bits should never be set. They can occur if the bit 55 | # field set it not divisible by eight. If they are set we should 56 | # throw an error. 57 | defp validate_trailing_bits(%__MODULE__{size: size} = bitfield) 58 | when rem(size, 8) == 0 do 59 | {:ok, bitfield} 60 | end 61 | 62 | defp validate_trailing_bits(%__MODULE__{size: size, pieces: pieces} = bitfield) do 63 | tailing_bits = bitfield_size(bitfield) - size 64 | tailing_bit_mask = (1 <<< tailing_bits) - 1 65 | 66 | if band(pieces, tailing_bit_mask) == 0 do 67 | {:ok, bitfield} 68 | else 69 | {:error, :out_of_bounds} 70 | end 71 | end 72 | 73 | # We don't use the tailing bits for the internal representation 74 | defp drop_tailing_bits(%__MODULE__{size: size} = bitfield) 75 | when rem(size, 8) == 0 do 76 | {:ok, bitfield} 77 | end 78 | 79 | defp drop_tailing_bits(%__MODULE__{size: size, pieces: pieces} = bitfield) do 80 | tailing_bits = bitfield_size(bitfield) - size 81 | {:ok, %{bitfield | pieces: pieces >>> tailing_bits}} 82 | end 83 | 84 | @doc """ 85 | Like `new/2` but will throw an error on initialization failure 86 | """ 87 | @spec new!(binary, size) :: t 88 | def new!(content \\ <<>>, size) do 89 | {:ok, set} = new(content, size) 90 | set 91 | end 92 | 93 | @doc """ 94 | Takes two bit field sets of the same size, and return `true` if both 95 | sets contain exactly the same pieces; and `false` otherwise. 96 | 97 | iex> a = BitFieldSet.new!(<<0b10100110>>, 8) 98 | iex> b = BitFieldSet.new!(<<0b10100110>>, 8) 99 | iex> BitFieldSet.equal?(a, b) 100 | true 101 | iex> c = BitFieldSet.new!(<<0b11011011>>, 8) 102 | iex> BitFieldSet.equal?(a, c) 103 | false 104 | 105 | """ 106 | @spec equal?(t, t) :: boolean 107 | def equal?( 108 | %__MODULE__{size: size, pieces: pieces}, 109 | %__MODULE__{size: size, pieces: pieces} 110 | ), 111 | do: true 112 | 113 | def equal?(_, _), do: false 114 | 115 | @doc """ 116 | Takes a bit field set and a piece number and return `true` if the 117 | given piece number is present in the set; `false` otherwise. 118 | 119 | iex> set = BitFieldSet.new!(<<0b10000001>>, 8) 120 | iex> BitFieldSet.member?(set, 7) 121 | true 122 | iex> BitFieldSet.member?(set, 2) 123 | false 124 | 125 | """ 126 | @spec member?(t, piece_index) :: boolean 127 | def member?(%__MODULE__{pieces: pieces} = bitfield, piece_index) do 128 | piece = get_piece_index(bitfield, piece_index) 129 | band(pieces, piece) != 0 130 | end 131 | 132 | @doc """ 133 | Take a bit field set and an piece index and add it to the bit 134 | field. The updated piece set will get returned: 135 | 136 | iex> a = BitFieldSet.new!(<<0b10101000>>, 8) 137 | iex> BitFieldSet.put(a, 6) 138 | #BitFieldSet<[0, 2, 4, 6]> 139 | 140 | """ 141 | @spec put(t, piece_index) :: t 142 | def put(%__MODULE__{size: size, pieces: pieces} = bitfield, piece_index) 143 | when piece_index < size do 144 | piece = get_piece_index(bitfield, piece_index) 145 | %{bitfield | pieces: bor(pieces, piece)} 146 | end 147 | 148 | @doc """ 149 | Take a bit field set and an index. The given index will get removed 150 | from the bit field set and the updated bit field set will get 151 | returned: 152 | 153 | iex> set = BitFieldSet.new!(<<0b10101000>>, 8) 154 | iex> BitFieldSet.delete(set, 2) 155 | #BitFieldSet<[0, 4]> 156 | 157 | """ 158 | @spec delete(t, piece_index) :: t 159 | def delete(%__MODULE__{pieces: pieces} = bitfield, piece_index) do 160 | piece = get_piece_index(bitfield, piece_index) 161 | %{bitfield | pieces: band(pieces, bnot(piece))} 162 | end 163 | 164 | @doc """ 165 | Set all the bits to on in the bit field set. 166 | 167 | iex> set = BitFieldSet.new!(<<0b10100110>>, 8) 168 | iex> BitFieldSet.fill(set) 169 | #BitFieldSet<[0, 1, 2, 3, 4, 5, 6, 7]> 170 | 171 | """ 172 | @spec fill(t) :: t 173 | def fill(%__MODULE__{size: size} = bitfield) do 174 | %{bitfield | pieces: (1 <<< size) - 1} 175 | end 176 | 177 | @doc """ 178 | Take a bit field set and return `true` if the set contains all the 179 | pieces, and `false` otherwise. 180 | 181 | iex> BitFieldSet.new!(<<0b10011010>>, 8) |> BitFieldSet.full?() 182 | false 183 | iex> BitFieldSet.new!(<<0b11111111>>, 8) |> BitFieldSet.full?() 184 | true 185 | 186 | """ 187 | @spec full?(t) :: boolean 188 | def full?(%__MODULE__{pieces: pieces, size: size}) do 189 | pieces == (1 <<< size) - 1 190 | end 191 | 192 | @doc """ 193 | Take a bit field set and return `true` if the set contains no 194 | pieces, and `false` otherwise. 195 | 196 | iex> BitFieldSet.new!(<<0b11111111>>, 8) |> BitFieldSet.empty?() 197 | false 198 | iex> BitFieldSet.new!(<<0b00000000>>, 8) |> BitFieldSet.empty?() 199 | true 200 | 201 | """ 202 | @spec empty?(t) :: boolean 203 | def empty?(%__MODULE__{pieces: 0}), do: true 204 | def empty?(%__MODULE__{}), do: false 205 | 206 | @doc """ 207 | Takes two bit field sets of the same size and return a set 208 | containing the pieces that belong to both sets. 209 | 210 | iex> a = BitFieldSet.new!(<<0b00101010>>, 8) 211 | iex> b = BitFieldSet.new!(<<0b10110011>>, 8) 212 | iex> BitFieldSet.intersection(a, b) 213 | #BitFieldSet<[2, 6]> 214 | 215 | """ 216 | @spec intersection(t, t) :: t 217 | def intersection( 218 | %__MODULE__{size: size, pieces: a} = bitfield, 219 | %__MODULE__{size: size, pieces: b} 220 | ) do 221 | %{bitfield | pieces: band(b, a)} 222 | end 223 | 224 | @doc """ 225 | Takes two bit field sets, a and b, who both of the same size, and 226 | returns a set containing the pieces in *a* without the pieces in 227 | *b*. 228 | 229 | iex> a = BitFieldSet.new!(<<170>>, 8) 230 | iex> b = BitFieldSet.new!(<<85>>, 8) 231 | iex> BitFieldSet.difference(a, b) 232 | #BitFieldSet<[0, 2, 4, 6]> 233 | iex> BitFieldSet.difference(b, a) 234 | #BitFieldSet<[1, 3, 5, 7]> 235 | 236 | """ 237 | @spec difference(t, t) :: t 238 | def difference( 239 | %__MODULE__{size: size, pieces: a} = bitfield, 240 | %__MODULE__{size: size, pieces: b} 241 | ) do 242 | %{bitfield | pieces: band(a, bnot(b))} 243 | end 244 | 245 | @doc """ 246 | Takes two bit field sets of the same size and returns a set 247 | containing all members of both sets. 248 | 249 | iex> a = BitFieldSet.new!(<<0b00101010>>, 8) 250 | iex> b = BitFieldSet.new!(<<0b10000000>>, 8) 251 | iex> BitFieldSet.union(a, b) 252 | #BitFieldSet<[0, 2, 4, 6]> 253 | 254 | """ 255 | @spec union(t, t) :: t 256 | def union( 257 | %__MODULE__{size: size, pieces: a} = bitfield, 258 | %__MODULE__{size: size, pieces: b} 259 | ) do 260 | %{bitfield | pieces: bor(a, b)} 261 | end 262 | 263 | @doc """ 264 | Takes two bit field sets, a and b, who has the same size, and return 265 | `true` if all the members of set a are also members of set b; 266 | `false` otherwise. 267 | 268 | iex> a = BitFieldSet.new!(<<0b00000110>>, 8) 269 | iex> b = BitFieldSet.new!(<<0b00101110>>, 8) 270 | iex> BitFieldSet.subset?(a, b) 271 | true 272 | iex> BitFieldSet.subset?(b, a) 273 | false 274 | 275 | """ 276 | @spec subset?(t, t) :: boolean 277 | def subset?( 278 | %__MODULE__{size: size, pieces: a}, 279 | %__MODULE__{size: size, pieces: b} 280 | ) do 281 | band(b, a) == a 282 | end 283 | 284 | @doc """ 285 | Takes two bit field sets and return `true` if the two bit fields 286 | does not share any members, otherwise `false` will get returned. 287 | 288 | iex> a = BitFieldSet.new!(<<0b00101110>>, 8) 289 | iex> b = BitFieldSet.new!(<<0b11010001>>, 8) 290 | iex> c = BitFieldSet.new!(<<0b11101000>>, 8) 291 | iex> BitFieldSet.disjoint?(a, b) 292 | true 293 | iex> BitFieldSet.disjoint?(a, c) 294 | false 295 | 296 | """ 297 | @spec disjoint?(t, t) :: boolean 298 | def disjoint?( 299 | %__MODULE__{pieces: a, size: size}, 300 | %__MODULE__{pieces: b, size: size} 301 | ) do 302 | band(b, a) == 0 303 | end 304 | 305 | @doc """ 306 | Take a bit field set and return the number of its available pieces. 307 | 308 | iex> BitFieldSet.new!(<<0b10101010>>, 8) |> BitFieldSet.size() 309 | 4 310 | 311 | """ 312 | @spec size(t) :: non_neg_integer 313 | def size(%__MODULE__{pieces: pieces}) do 314 | count_enabled_bits(pieces, 0) 315 | end 316 | 317 | @doc """ 318 | Takes a bit field set and returns a binary representation of the set. 319 | 320 | iex> a = BitFieldSet.new!(<<0b10011010, 0b10000000>>, 16) 321 | iex> BitFieldSet.to_binary(a) 322 | <<154, 128>> 323 | 324 | """ 325 | @spec to_binary(t) :: binary 326 | def to_binary(%__MODULE__{pieces: pieces, size: size}) do 327 | byte_size = bitfield_size(size) 328 | tailing_bits = byte_size - size 329 | bitfield = pieces <<< tailing_bits 330 | <> 331 | end 332 | 333 | @doc """ 334 | Take a bit field set and returns the available pieces as a list. 335 | 336 | iex> BitFieldSet.new!(<<0b10011010>>, 8) |> BitFieldSet.to_list() 337 | [0, 3, 4, 6] 338 | 339 | """ 340 | @spec to_list(t) :: [piece_index] 341 | def to_list(%__MODULE__{pieces: 0, size: _}), do: [] 342 | 343 | def to_list(%__MODULE__{pieces: pieces, size: size}) do 344 | # `:math.log2/1` does not support numbers bigger than 1024 bits; 345 | # so we need to split the number up if the number is bigger 346 | chunk(<>, 1024) 347 | |> List.flatten() 348 | end 349 | 350 | defp chunk(data, step_size, offset \\ 0) do 351 | case data do 352 | <<>> -> 353 | [] 354 | 355 | <> -> 356 | offset = offset + step_size 357 | # note; body recursive because we will hit other limitations 358 | # before blowing the call stack 359 | [do_chunk_to_list(head, offset, []) | chunk(remaining, step_size, offset)] 360 | 361 | <> -> 362 | remainder_size = bit_size(remainder) 363 | offset = offset + remainder_size 364 | <> = remainder 365 | [do_chunk_to_list(remainder, offset, [])] 366 | end 367 | end 368 | 369 | defp do_chunk_to_list(0, _, acc), do: acc 370 | 371 | defp do_chunk_to_list(n, offset, acc) do 372 | next = band(n, n - 1) 373 | position_of_least_significant = :erlang.trunc(:math.log2(band(n, -n)) + 1) 374 | do_chunk_to_list(next, offset, [offset - position_of_least_significant | acc]) 375 | end 376 | 377 | # helpers ============================================================ 378 | defp get_piece_index(%__MODULE__{size: size}, piece_index) do 379 | 1 <<< (size - (piece_index + 1)) 380 | end 381 | 382 | # calculate the size of the bit field in bytes (divisible by 8) 383 | defp bitfield_size(%__MODULE__{size: size}), do: bitfield_size(size) 384 | 385 | defp bitfield_size(size) when is_integer(size) do 386 | tail = if rem(size, 8) != 0, do: 1, else: 0 387 | (div(size, 8) + tail) * 8 388 | end 389 | 390 | # We use Brian Kernighan's Algorithm to count the 'on' bits in the 391 | # bit field. It works by subtracting one from the current integer 392 | # value and returning the BINARY AND of that and the current value. 393 | # This will effectively remove the current least significant bit, so 394 | # it is just a matter of counting the times we can do that before 395 | # reaching zero 396 | defp count_enabled_bits(0, acc), do: acc 397 | 398 | defp count_enabled_bits(n, acc) do 399 | count_enabled_bits(band(n, n - 1), acc + 1) 400 | end 401 | 402 | # protocols ========================================================== 403 | defimpl Enumerable do 404 | def reduce(source, acc, fun) do 405 | Enumerable.List.reduce(BitFieldSet.to_list(source), acc, fun) 406 | end 407 | 408 | def member?(source, value) do 409 | {:ok, BitFieldSet.member?(source, value)} 410 | end 411 | 412 | def count(source) do 413 | {:ok, BitFieldSet.size(source)} 414 | end 415 | end 416 | 417 | defimpl Collectable do 418 | def into(original) do 419 | {original, 420 | fn 421 | acc, {:cont, value} -> 422 | BitFieldSet.put(acc, value) 423 | 424 | acc, :done -> 425 | acc 426 | 427 | _, :halt -> 428 | :ok 429 | end} 430 | end 431 | end 432 | 433 | defimpl Inspect do 434 | import Inspect.Algebra 435 | 436 | def inspect(source, opts) do 437 | opts = %Inspect.Opts{opts | charlists: :as_lists} 438 | concat(["#BitFieldSet<", Inspect.List.inspect(BitFieldSet.to_list(source), opts), ">"]) 439 | end 440 | end 441 | end 442 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule BitFieldSet.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :bit_field_set, 7 | version: "1.2.3", 8 | elixir: "~> 1.2", 9 | test_pattern: "*_{test,eqc}.exs", 10 | build_embedded: Mix.env() == :prod, 11 | start_permanent: Mix.env() == :prod, 12 | description: description(), 13 | package: package(), 14 | deps: deps() 15 | ] 16 | end 17 | 18 | def application() do 19 | [applications: [:logger]] 20 | end 21 | 22 | defp description() do 23 | """ 24 | Store and manipulate a set of bit flags, mostly used for syncing the state 25 | between peers in a peer to peer network, such as BitTorrent. 26 | """ 27 | end 28 | 29 | def package() do 30 | [ 31 | files: ["lib", "mix.exs", "README*", "LICENSE"], 32 | maintainers: ["Martin Gausby"], 33 | licenses: ["Apache 2.0"], 34 | links: %{ 35 | "GitHub" => "https://github.com/gausby/bit_field_set", 36 | "Issues" => "https://github.com/gausby/bit_field_set/issues" 37 | } 38 | ] 39 | end 40 | 41 | defp deps() do 42 | [ 43 | {:ex_doc, "~> 0.20.0", only: :dev}, 44 | {:eqc_ex, "~> 1.4.2", only: [:test, :dev]}, 45 | {:benchfella, "~> 0.3.4", only: :dev} 46 | ] 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchfella": {:hex, :benchfella, "0.3.4", "41d2c017b361ece5225b5ba2e3b30ae53578c57c6ebc434417b4f1c2c94cf4f3", [:mix], []}, 3 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, 4 | "eqc_ex": {:hex, :eqc_ex, "1.4.2", "c89322cf8fbd4f9ddcb18141fb162a871afd357c55c8c0198441ce95ffe2e105", [:mix], []}, 5 | "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, 9 | } 10 | -------------------------------------------------------------------------------- /test/bit_field_set_eqc.exs: -------------------------------------------------------------------------------- 1 | defmodule BitFieldSetEqc do 2 | use ExUnit.Case 3 | use EQC.ExUnit 4 | use Bitwise 5 | 6 | describe "initializing bit fields" do 7 | property "converting data to a bit field set and back should yield the same result" do 8 | forall input <- binary() do 9 | size = bit_size(input) 10 | result = input |> BitFieldSet.new!(size) |> BitFieldSet.to_binary() 11 | ensure(result == input) 12 | end 13 | end 14 | 15 | property "initializing bit fields with varying sizes" do 16 | forall {size, a} <- gen_random_set() do 17 | result = a |> BitFieldSet.new!(size) |> BitFieldSet.to_list() 18 | expected = a |> convert_to_map_set(size) |> MapSet.to_list() 19 | ensure(Enum.sort(expected) == result) 20 | end 21 | end 22 | 23 | property "collectable protocol" do 24 | forall {size, a} <- gen_random_set() do 25 | bit_field = BitFieldSet.new!(a, size) 26 | 27 | result = 28 | bit_field 29 | |> BitFieldSet.to_list() 30 | |> Enum.into(%BitFieldSet{size: size}) 31 | 32 | ensure(result == bit_field) 33 | end 34 | end 35 | end 36 | 37 | property "put/2" do 38 | forall {size, numbers} <- gen_random_set_of_numbers() do 39 | bit_field = BitFieldSet.new!(size + 1) 40 | 41 | result = 42 | numbers 43 | |> Enum.reduce(bit_field, &BitFieldSet.put(&2, &1)) 44 | |> BitFieldSet.to_list() 45 | 46 | ensure(Enum.dedup(numbers) == result) 47 | end 48 | end 49 | 50 | defp gen_random_set_of_numbers() do 51 | sized size do 52 | {size, orderedlist(choose(0, size))} 53 | end 54 | end 55 | 56 | property "delete/2" do 57 | forall {add, remove} <- {orderedlist(choose(0, 15)), orderedlist(choose(0, 15))} do 58 | expected = 59 | remove 60 | |> Enum.reduce( 61 | add, 62 | &Enum.reject(&2, fn 63 | ^&1 -> true 64 | _ -> false 65 | end) 66 | ) 67 | |> Enum.dedup() 68 | 69 | bit_field = Enum.into(add, %BitFieldSet{size: 16}) 70 | 71 | result = 72 | remove 73 | |> Enum.reduce(bit_field, &BitFieldSet.delete(&2, &1)) 74 | |> BitFieldSet.to_list() 75 | 76 | ensure(expected == result) 77 | end 78 | end 79 | 80 | # todo, make sure some tests are guaranteed disjoined 81 | property "disjoint?/2" do 82 | forall {size, a, b} <- gen_two_random_sets_of_same_size() do 83 | bit_field_a = BitFieldSet.new!(a, size) 84 | bit_field_b = BitFieldSet.new!(b, size) 85 | 86 | map_set_a = convert_to_map_set(a, size) 87 | map_set_b = convert_to_map_set(b, size) 88 | 89 | expected = MapSet.disjoint?(map_set_a, map_set_b) 90 | result = BitFieldSet.disjoint?(bit_field_a, bit_field_b) 91 | 92 | ensure(expected == result) 93 | end 94 | end 95 | 96 | property "difference/2" do 97 | forall {size, a, b} <- gen_two_random_sets_of_same_size() do 98 | bit_field_a = BitFieldSet.new!(a, size) 99 | bit_field_b = BitFieldSet.new!(b, size) 100 | 101 | map_set_a = convert_to_map_set(a, size) 102 | map_set_b = convert_to_map_set(b, size) 103 | 104 | expected = 105 | MapSet.difference(map_set_a, map_set_b) 106 | |> MapSet.to_list() 107 | |> Enum.sort() 108 | 109 | result = 110 | BitFieldSet.difference(bit_field_a, bit_field_b) 111 | |> BitFieldSet.to_list() 112 | 113 | ensure(expected == result) 114 | end 115 | end 116 | 117 | property "intersection/2" do 118 | forall {size, a, b} <- gen_two_random_sets_of_same_size() do 119 | bit_field_a = BitFieldSet.new!(a, size) 120 | bit_field_b = BitFieldSet.new!(b, size) 121 | 122 | map_set_a = convert_to_map_set(a, size) 123 | map_set_b = convert_to_map_set(b, size) 124 | 125 | expected = 126 | MapSet.intersection(map_set_a, map_set_b) 127 | |> MapSet.to_list() 128 | |> Enum.sort() 129 | 130 | result = 131 | BitFieldSet.intersection(bit_field_a, bit_field_b) 132 | |> BitFieldSet.to_list() 133 | 134 | ensure(expected == result) 135 | end 136 | end 137 | 138 | property "union/2" do 139 | forall {size, a, b} <- gen_two_random_sets_of_same_size() do 140 | bit_field_a = BitFieldSet.new!(a, size) 141 | bit_field_b = BitFieldSet.new!(b, size) 142 | 143 | map_set_a = convert_to_map_set(a, size) 144 | map_set_b = convert_to_map_set(b, size) 145 | 146 | expected = 147 | MapSet.union(map_set_a, map_set_b) 148 | |> MapSet.to_list() 149 | |> Enum.sort() 150 | 151 | result = 152 | BitFieldSet.union(bit_field_a, bit_field_b) 153 | |> BitFieldSet.to_list() 154 | 155 | ensure(expected == result) 156 | end 157 | end 158 | 159 | property "equal?/2" do 160 | # todo, should generate two sets that are equal once in a while 161 | forall {size, a, b} <- gen_two_random_sets_of_same_size() do 162 | bit_field_a = BitFieldSet.new!(a, size) 163 | bit_field_b = BitFieldSet.new!(b, size) 164 | 165 | map_set_a = convert_to_map_set(a, size) 166 | map_set_b = convert_to_map_set(b, size) 167 | 168 | expected = MapSet.equal?(map_set_a, map_set_b) 169 | result = BitFieldSet.equal?(bit_field_a, bit_field_b) 170 | 171 | ensure(expected == result) 172 | end 173 | end 174 | 175 | property "subset?/2" do 176 | forall {size, a, b} <- gen_maybe_subset() do 177 | bit_field_a = BitFieldSet.new!(a, size) 178 | bit_field_b = BitFieldSet.new!(b, size) 179 | 180 | map_set_a = convert_to_map_set(a, size) 181 | map_set_b = convert_to_map_set(b, size) 182 | 183 | expected = MapSet.subset?(map_set_a, map_set_b) 184 | result = BitFieldSet.subset?(bit_field_a, bit_field_b) 185 | 186 | ensure(expected == result) 187 | end 188 | end 189 | 190 | # todo figure out how to pick random bits from a bitstring 191 | defp gen_maybe_subset() do 192 | sized size do 193 | let set <- bitstring(size) do 194 | {size, set, bitstring(size)} 195 | end 196 | end 197 | end 198 | 199 | property "size/2" do 200 | forall {size, a} <- gen_random_set() do 201 | expected = a |> convert_to_map_set(size) |> MapSet.size() 202 | result = a |> BitFieldSet.new!(size) |> BitFieldSet.size() 203 | ensure(expected == result) 204 | end 205 | end 206 | 207 | property "member?/2" do 208 | forall {size, haystack, needle} <- gen_haystack_and_needle() do 209 | implies needle >= 0 do 210 | expected = 211 | haystack 212 | |> convert_to_map_set(size) 213 | |> MapSet.member?(needle) 214 | 215 | result = 216 | haystack 217 | |> BitFieldSet.new!(size) 218 | |> BitFieldSet.member?(needle) 219 | 220 | ensure(expected == result) 221 | end 222 | end 223 | end 224 | 225 | defp gen_haystack_and_needle() do 226 | sized size do 227 | let haystack <- bitstring(size) do 228 | let needle <- nat() do 229 | {size, haystack, needle} 230 | end 231 | end 232 | end 233 | end 234 | 235 | # =Generators ========================================================= 236 | defp gen_random_set() do 237 | sized size do 238 | {size, bitstring(size)} 239 | end 240 | end 241 | 242 | defp gen_two_random_sets_of_same_size() do 243 | sized size do 244 | {size, bitstring(size), bitstring(size)} 245 | end 246 | end 247 | 248 | # =Helpers ============================================================ 249 | defp convert_to_map_set(data, set_size) when is_bitstring(data) do 250 | data_size = bit_size(data) 251 | <> = data 252 | convert_to_map_set(bit_field, set_size) 253 | end 254 | 255 | defp convert_to_map_set(data, bit_size) when is_number(data) do 256 | {_counter, acc} = 257 | data 258 | |> Integer.digits(2) 259 | |> Enum.reverse() 260 | |> Enum.reduce({bit_size - 1, []}, fn 261 | 0, {counter, acc} -> 262 | {counter - 1, acc} 263 | 264 | 1, {counter, acc} -> 265 | {counter - 1, [counter | acc]} 266 | end) 267 | 268 | Enum.into(acc, MapSet.new()) 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /test/bit_field_set_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BitFieldSetTest do 2 | use ExUnit.Case 3 | doctest BitFieldSet 4 | 5 | test "creating a new empty bitfield" do 6 | assert {:ok, %BitFieldSet{size: 32, pieces: 0}} = BitFieldSet.new(32) 7 | end 8 | 9 | test "creating a new bitfield with data" do 10 | bitfield = %BitFieldSet{size: 32, pieces: 0b10000000010000000010000000110000} 11 | assert {:ok, ^bitfield} = BitFieldSet.new(<<128, 64, 32, 48>>, 32) 12 | 13 | assert %BitFieldSet{size: 8, pieces: 0b00000100} = BitFieldSet.new!(<<4>>, 8) 14 | end 15 | 16 | test "bit fields should be able to hold sets of arbitrary sizes" do 17 | bitfield = %BitFieldSet{size: 33, pieces: 0b100000000100000000100000001100001} 18 | assert {:ok, ^bitfield} = BitFieldSet.new(<<128, 64, 32, 48, 128>>, 33) 19 | assert %BitFieldSet{size: 15, pieces: 0b100000000000000} = BitFieldSet.new!(<<128, 0>>, 15) 20 | end 21 | 22 | test "tmp" do 23 | bitfield = %BitFieldSet{size: 33, pieces: 0b100000000100000000100000001100001} 24 | assert {:ok, ^bitfield} = BitFieldSet.new(<<128, 64, 32, 48, 128>>, 33) 25 | assert [0] = BitFieldSet.to_list(BitFieldSet.new!(<<128, 0>>, 15)) 26 | end 27 | 28 | test "bit fields should throw an error if bits are out of bounds" do 29 | assert {:error, :out_of_bounds} = BitFieldSet.new(<<128, 64, 32, 48, 129>>, 33) 30 | assert {:error, :out_of_bounds} = BitFieldSet.new(<<0::size(120)>>, 33) 31 | end 32 | 33 | test "turning a bitfield into a binary" do 34 | cases = [<<74, 0, 0>>, <<0, 74, 0>>, <<0, 0, 74>>, <<1, 255, 74>>] 35 | 36 | for bin <- cases do 37 | size = bit_size(bin) 38 | <> = bin 39 | assert ^bin = BitFieldSet.to_binary(%BitFieldSet{size: size, pieces: pieces}) 40 | end 41 | 42 | assert <<0b10000000, 0, 0>> = 43 | BitFieldSet.to_binary(%BitFieldSet{size: 22, pieces: 0b1000000000000000000000}) 44 | 45 | assert <<0b10000000, 0, 0>> = 46 | BitFieldSet.to_binary(%BitFieldSet{size: 24, pieces: 0b100000000000000000000000}) 47 | 48 | assert <<255, 255, 252>> = 49 | BitFieldSet.to_binary(%BitFieldSet{size: 23, pieces: 0b111111111111111111111110}) 50 | 51 | assert <<128, 0>> = BitFieldSet.to_binary(%BitFieldSet{size: 12, pieces: 0b100000000000}) 52 | end 53 | 54 | test "getting bits" do 55 | bitfield = %BitFieldSet{size: 32, pieces: 0b10000000100000011111111100000001} 56 | 57 | assert BitFieldSet.member?(bitfield, 0) 58 | refute BitFieldSet.member?(bitfield, 1) 59 | assert BitFieldSet.member?(bitfield, 8) 60 | refute BitFieldSet.member?(bitfield, 14) 61 | assert BitFieldSet.member?(bitfield, 15) 62 | assert BitFieldSet.member?(bitfield, 16) 63 | 64 | refute BitFieldSet.member?(bitfield, 30) 65 | assert BitFieldSet.member?(bitfield, 31) 66 | end 67 | 68 | test "setting bits" do 69 | result = 70 | %BitFieldSet{size: 16, pieces: 0} 71 | |> BitFieldSet.put(2) 72 | |> BitFieldSet.put(4) 73 | |> BitFieldSet.put(6) 74 | |> BitFieldSet.put(8) 75 | |> BitFieldSet.put(15) 76 | 77 | assert result == %BitFieldSet{size: 16, pieces: 0b0010101010000001} 78 | end 79 | 80 | test "setting bits in an odd numbered set" do 81 | result = 82 | %BitFieldSet{size: 18, pieces: 0} 83 | |> BitFieldSet.put(0) 84 | |> BitFieldSet.put(4) 85 | |> BitFieldSet.put(6) 86 | |> BitFieldSet.put(8) 87 | |> BitFieldSet.put(17) 88 | 89 | assert result == %BitFieldSet{size: 18, pieces: 0b100010101000000001} 90 | 91 | bitfield = %BitFieldSet{size: 18, pieces: 0b111111110000000000} 92 | assert %BitFieldSet{size: 18, pieces: 0b111111111000000000} = BitFieldSet.put(bitfield, 8) 93 | end 94 | 95 | test "setting all bits using fill/1" do 96 | assert %BitFieldSet{size: 8, pieces: 0b11111111} = 97 | BitFieldSet.fill(%BitFieldSet{size: 8, pieces: 0}) 98 | 99 | assert %BitFieldSet{size: 8, pieces: 0b11111111} = 100 | BitFieldSet.fill(%BitFieldSet{size: 8, pieces: 0b11100100}) 101 | 102 | <> = 103 | <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> 104 | 105 | assert %BitFieldSet{size: 120, pieces: ^pieces} = 106 | BitFieldSet.fill(%BitFieldSet{size: 120, pieces: 0}) 107 | 108 | assert %BitFieldSet{size: 1, pieces: 0b00000001} = 109 | BitFieldSet.fill(%BitFieldSet{size: 1, pieces: 0}) 110 | 111 | assert %BitFieldSet{size: 17, pieces: 0b11111111111111111} = 112 | BitFieldSet.fill(%BitFieldSet{size: 17, pieces: 0}) 113 | end 114 | 115 | test "removing bits" do 116 | assert %BitFieldSet{size: 8, pieces: 0} = 117 | BitFieldSet.delete(%BitFieldSet{size: 8, pieces: 0b00100000}, 2) 118 | 119 | assert %BitFieldSet{size: 8, pieces: 0b101010} = 120 | BitFieldSet.delete(%BitFieldSet{size: 8, pieces: 0b10101010}, 0) 121 | 122 | assert %BitFieldSet{size: 8, pieces: 0b10100010} = 123 | BitFieldSet.delete(%BitFieldSet{size: 8, pieces: 0b10101010}, 4) 124 | end 125 | 126 | test "counting the number of available pieces in a bitfield" do 127 | assert 8 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b11111111}) 128 | assert 4 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b10101001}) 129 | assert 3 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b101001}) 130 | assert 2 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b1010}) 131 | assert 1 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b1000}) 132 | assert 0 = BitFieldSet.size(%BitFieldSet{size: 8, pieces: 0b00000000}) 133 | 134 | assert 3 = BitFieldSet.size(%BitFieldSet{size: 24, pieces: 0b100000001000000010000000}) 135 | assert 6 = BitFieldSet.size(%BitFieldSet{size: 24, pieces: 0b000010100000101000001010}) 136 | assert 12 = BitFieldSet.size(%BitFieldSet{size: 24, pieces: 0b101010101010101010101010}) 137 | 138 | assert 12 = BitFieldSet.size(%BitFieldSet{size: 23, pieces: 0b10101010101010101010101}) 139 | end 140 | 141 | test "is full?" do 142 | assert BitFieldSet.full?(%BitFieldSet{size: 8, pieces: 0b11111111}) 143 | refute BitFieldSet.full?(%BitFieldSet{size: 8, pieces: 0b11111110}) 144 | refute BitFieldSet.full?(%BitFieldSet{size: 16, pieces: 0b1111111100000001}) 145 | assert BitFieldSet.full?(%BitFieldSet{size: 16, pieces: 0b1111111111111111}) 146 | end 147 | 148 | test "is empty?" do 149 | assert BitFieldSet.empty?(%BitFieldSet{size: 8, pieces: 0}) 150 | refute BitFieldSet.empty?(%BitFieldSet{size: 8, pieces: 0b11111111}) 151 | refute BitFieldSet.empty?(%BitFieldSet{size: 16, pieces: 0b1111111100000001}) 152 | assert BitFieldSet.empty?(%BitFieldSet{size: 16, pieces: 0}) 153 | end 154 | 155 | test "get available pieces for a bit-field as a list" do 156 | assert [1, 8] = BitFieldSet.to_list(%BitFieldSet{size: 16, pieces: 0b0100000010000000}) 157 | assert [0, 8] = BitFieldSet.to_list(%BitFieldSet{size: 16, pieces: 0b1000000010000000}) 158 | assert [8] = BitFieldSet.to_list(%BitFieldSet{size: 16, pieces: 0b0000000010000000}) 159 | result = Enum.to_list(0..15) 160 | assert ^result = BitFieldSet.to_list(%BitFieldSet{size: 16, pieces: 0b1111111111111111}) 161 | 162 | assert [15] = BitFieldSet.to_list(%BitFieldSet{size: 16, pieces: 0b00000000000000001}) 163 | 164 | assert [0] = BitFieldSet.to_list(%BitFieldSet{size: 8, pieces: 0b10000000}) 165 | assert [1] = BitFieldSet.to_list(%BitFieldSet{size: 8, pieces: 0b01000000}) 166 | assert [5] = BitFieldSet.to_list(%BitFieldSet{size: 8, pieces: 0b00000100}) 167 | assert [5, 7] = BitFieldSet.to_list(%BitFieldSet{size: 8, pieces: 0b00000101}) 168 | 169 | assert [3, 5] = BitFieldSet.to_list(%BitFieldSet{size: 7, pieces: 0b0001010}) 170 | 171 | assert [0] = BitFieldSet.to_list(%BitFieldSet{size: 15, pieces: 0b100000000000000}) 172 | end 173 | 174 | test "intersection" do 175 | # present in both sets 176 | bitfield1 = %BitFieldSet{size: 16, pieces: 0b1011111001101010} 177 | bitfield2 = %BitFieldSet{size: 16, pieces: 0b0110101010111110} 178 | 179 | assert %BitFieldSet{size: 16, pieces: 0b0010101000101010} = 180 | BitFieldSet.intersection(bitfield1, bitfield2) 181 | 182 | assert %BitFieldSet{size: 16, pieces: 0b0010101000101010} = 183 | BitFieldSet.intersection(bitfield2, bitfield1) 184 | end 185 | 186 | test "difference" do 187 | empty = %BitFieldSet{size: 8, pieces: 0} 188 | bitfield1 = %BitFieldSet{size: 8, pieces: 0b11111111} 189 | bitfield2 = %BitFieldSet{size: 8, pieces: 0} 190 | bitfield3 = %BitFieldSet{size: 8, pieces: 0b10101010} 191 | bitfield4 = %BitFieldSet{size: 8, pieces: 0b01010101} 192 | 193 | assert ^bitfield1 = BitFieldSet.difference(bitfield1, bitfield2) 194 | assert ^empty = BitFieldSet.difference(empty, bitfield2) 195 | assert ^empty = BitFieldSet.difference(bitfield3, bitfield3) 196 | assert ^bitfield3 = BitFieldSet.difference(bitfield3, bitfield4) 197 | assert ^bitfield4 = BitFieldSet.difference(bitfield4, bitfield3) 198 | 199 | bitfield_a = %BitFieldSet{size: 8, pieces: 0b01100000} 200 | bitfield_b = %BitFieldSet{size: 8, pieces: 0b00111000} 201 | bitfield_c = %BitFieldSet{size: 8, pieces: 0b01000000} 202 | assert ^bitfield_c = BitFieldSet.difference(bitfield_a, bitfield_b) 203 | end 204 | 205 | test "disjoint?" do 206 | bitfield1 = %BitFieldSet{size: 16, pieces: 0b0000000011111111} 207 | bitfield2 = %BitFieldSet{size: 16, pieces: 0b1111111100000000} 208 | bitfield3 = %BitFieldSet{size: 16, pieces: 0b1000000010000000} 209 | 210 | assert BitFieldSet.disjoint?(bitfield1, bitfield2) 211 | refute BitFieldSet.disjoint?(bitfield1, bitfield3) 212 | end 213 | 214 | test "subset" do 215 | bitfield1 = %BitFieldSet{size: 8, pieces: 0b10000000} 216 | bitfield2 = %BitFieldSet{size: 8, pieces: 0b11111111} 217 | 218 | assert BitFieldSet.subset?(bitfield1, bitfield2) 219 | refute BitFieldSet.subset?(bitfield2, bitfield1) 220 | end 221 | 222 | test "equal" do 223 | bitfield1 = %BitFieldSet{size: 16, pieces: 0b0000000011111111} 224 | bitfield2 = %BitFieldSet{size: 16, pieces: 0b1111111100000000} 225 | 226 | refute BitFieldSet.equal?(bitfield1, bitfield2) 227 | assert BitFieldSet.equal?(bitfield1, bitfield1) 228 | end 229 | 230 | test "union" do 231 | bitfield_full = %BitFieldSet{size: 16, pieces: 0b1111111111111111} 232 | bitfield1 = %BitFieldSet{size: 16, pieces: 0b0000000011111111} 233 | bitfield2 = %BitFieldSet{size: 16, pieces: 0b1111111100000000} 234 | bitfield4 = %BitFieldSet{size: 16, pieces: 0b1010101000000000} 235 | bitfield5 = %BitFieldSet{size: 16, pieces: 0b0101010100000000} 236 | 237 | assert ^bitfield_full = BitFieldSet.union(bitfield1, bitfield2) 238 | assert ^bitfield1 = BitFieldSet.union(bitfield1, bitfield1) 239 | assert ^bitfield2 = BitFieldSet.union(bitfield2, bitfield2) 240 | assert ^bitfield2 = BitFieldSet.union(bitfield4, bitfield5) 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------