├── .gitignore ├── LICENSE.md ├── README.md ├── bench └── bitmap_bench.exs ├── config └── config.exs ├── lib ├── bitmap.ex └── bitmap │ ├── binary.ex │ ├── integer.ex │ └── utils.ex ├── mix.exs ├── mix.lock └── test ├── bitmap_test.exs ├── doc_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | *.DS_Store 6 | doc 7 | bench/snapshots/* -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 hashd (Kiran Danduprolu) 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bitmap 2 | ====== 3 | 4 | In computing, a bitmap is a mapping from some domain (for example, a range of integers) to bits, that is, values which are zero or one. It is also called a bit array or bitmap index. 5 | 6 | This is an Elixir implementation of a bit array. Two implementations are provided as part of the library, Binary and Integer. Integers are the default due to clear performance superiority based on benchmarks provided below. 7 | 8 | It is a fast space efficient data structure for lookups. 9 | 10 | > Note: Index is zero based in the implementation 11 | 12 | ### Examples 13 | ``` elixir 14 | iex> bitmap = Bitmap.new(5) 15 | <<0::size(5)>> # 00000 16 | iex> Bitmap.set(bitmap, 2) 17 | <<4::size(5)>> # 00100 18 | iex> bitmap |> Bitmap.set(2) |> Bitmap.set(3) 19 | <<6::size(5)>> # 00110 20 | iex> bitmap |> Bitmap.set(2) |> Bitmap.set(3) |> Bitmap.unset(2) 21 | <<2::size(5)>> # 00010 22 | ``` 23 | 24 | Read the latest documentation [here](http://hexdocs.pm/bitmap/overview.html) for elaborate description and more examples on how to use the library. 25 | 26 | ### Benchmark using `Benchfella` 27 | Results are based on sources present inside the `bench/` directory. 28 | 29 | Bitmaps of size 1,000,000 are used to get benchmark on performance of the two 30 | implementations provided in the library - Binary and Integer. 31 | ``` 32 | Benchmark Bitmap.Integer.at 100000000 0.05 µs/op 33 | Benchmark Bitmap.Integer.unset_all 100000000 0.06 µs/op 34 | Benchmark Bitmap.Integer.set? 100000000 0.07 µs/op 35 | Benchmark Bitmap.Integer.new 10000000 0.11 µs/op 36 | Benchmark Bitmap.Integer.set 500000 7.50 µs/op 37 | Benchmark Bitmap.Integer.toggle 500000 7.52 µs/op 38 | Benchmark Bitmap.Integer.toggle_all 100000 21.33 µs/op 39 | Benchmark Bitmap.Integer.unset 100000 22.83 µs/op 40 | Benchmark Bitmap.Binary.unset_all 50000 74.54 µs/op 41 | Benchmark Bitmap.Binary.new 20000 79.34 µs/op 42 | Benchmark Bitmap.Binary.set? 20000 90.18 µs/op 43 | Benchmark Bitmap.Binary.at 20000 99.70 µs/op 44 | Benchmark Bitmap.Binary.toggle 10000 169.97 µs/op 45 | Benchmark Bitmap.Binary.unset 10000 207.38 µs/op 46 | Benchmark Bitmap.Binary.set 10000 208.58 µs/op 47 | Benchmark Bitmap.Integer.set_all 10 111083.80 µs/op 48 | Benchmark Bitmap.Binary.set_all 10 143833.10 µs/op 49 | Benchmark Bitmap.Binary.to_string 1 80530194.00 µs/op 50 | Benchmark Bitmap.Integer.to_string 1 89035291.00 µs/op 51 | Benchmark Bitmap.Binary.toggle_all 1 451542225.00 µs/op 52 | ``` 53 | 54 | ##### License 55 | This project is available under MIT License. 56 | -------------------------------------------------------------------------------- /bench/bitmap_bench.exs: -------------------------------------------------------------------------------- 1 | defmodule BitmapBench do 2 | use Benchfella 3 | 4 | @size 1000000 5 | @bb Bitmap.Binary.new(@size) 6 | @bi Bitmap.Integer.new(@size) 7 | @sbb Bitmap.Binary.new(@size) |> Bitmap.Binary.set_all 8 | @sbi Bitmap.Integer.new(@size) |> Bitmap.Integer.set_all 9 | 10 | bench "Benchmark Bitmap.Binary.new" do 11 | Bitmap.Binary.new(@size) 12 | end 13 | 14 | bench "Benchmark Bitmap.Integer.new" do 15 | Bitmap.Integer.new(@size) 16 | end 17 | 18 | bench "Benchmark Bitmap.Binary.set" do 19 | Bitmap.Binary.set(@bb, div(@size, 2)) 20 | end 21 | 22 | bench "Benchmark Bitmap.Integer.set" do 23 | Bitmap.Integer.set(@bi, div(@size, 2)) 24 | end 25 | 26 | bench "Benchmark Bitmap.Binary.toggle" do 27 | Bitmap.Binary.toggle(@bb, div(@size, 2)) 28 | end 29 | 30 | bench "Benchmark Bitmap.Integer.toggle" do 31 | Bitmap.Integer.toggle(@bi, div(@size, 2)) 32 | end 33 | 34 | bench "Benchmark Bitmap.Binary.unset" do 35 | Bitmap.Binary.unset(@sbb, div(@size, 2)) 36 | end 37 | 38 | bench "Benchmark Bitmap.Integer.unset" do 39 | Bitmap.Integer.unset(@bi, div(@size, 2)) 40 | end 41 | 42 | bench "Benchmark Bitmap.Binary.set_all" do 43 | @bb |> Bitmap.Binary.set_all 44 | end 45 | 46 | bench "Benchmark Bitmap.Integer.set_all" do 47 | @bi |> Bitmap.Integer.set_all 48 | end 49 | 50 | bench "Benchmark Bitmap.Binary.unset_all" do 51 | @sbb |> Bitmap.Binary.unset_all 52 | end 53 | 54 | bench "Benchmark Bitmap.Integer.unset_all" do 55 | @sbi |> Bitmap.Integer.unset_all 56 | end 57 | 58 | bench "Benchmark Bitmap.Binary.toggle_all" do 59 | @sbb |> Bitmap.Binary.toggle_all 60 | @bb |> Bitmap.Binary.toggle_all 61 | end 62 | 63 | bench "Benchmark Bitmap.Integer.toggle_all" do 64 | @sbi |> Bitmap.Integer.toggle_all 65 | @bi |> Bitmap.Integer.toggle_all 66 | end 67 | 68 | bench "Benchmark Bitmap.Binary.at" do 69 | Bitmap.Binary.at(@bb, div(@size, 2)) 70 | end 71 | 72 | bench "Benchmark Bitmap.Integer.at" do 73 | Bitmap.Integer.at(@bi, div(@size, 2)) 74 | end 75 | 76 | bench "Benchmark Bitmap.Binary.set?" do 77 | Bitmap.Binary.set?(@bb, div(@size, 2)) 78 | end 79 | 80 | bench "Benchmark Bitmap.Integer.set?" do 81 | Bitmap.Integer.set?(@bi, div(@size, 2)) 82 | end 83 | 84 | bench "Benchmark Bitmap.Binary.to_string" do 85 | @sbb |> Bitmap.Binary.to_string 86 | @bb |> Bitmap.Binary.to_string 87 | end 88 | 89 | bench "Benchmark Bitmap.Integer.to_string" do 90 | @sbi |> Bitmap.Integer.to_string 91 | @bi |> Bitmap.Integer.to_string 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /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/bitmap.ex: -------------------------------------------------------------------------------- 1 | defmodule Bitmap do 2 | @moduledoc """ 3 | Defines behaviour of a Bitmap, which can be implemented by the user. We 4 | provide implementations using Binary and Integers. 5 | 6 | This behavior has been designed to be pipe-friendly, so pipe 'em up. 7 | 8 | Methods are delegated to the default implementation which is 9 | currently, integer - Bitmap.Integer. 10 | """ 11 | @type bitmap :: binary | Bitmap.Integer.t 12 | @type index :: non_neg_integer 13 | @type bit :: 1 | 0 14 | @type argt :: non_neg_integer | list | Range.t 15 | 16 | @callback new(argt) :: any 17 | @callback at(bitmap, index) :: bit 18 | @callback set?(bitmap, index) :: boolean 19 | @callback set(bitmap, index) :: bitmap 20 | @callback set_all(bitmap) :: bitmap 21 | @callback unset?(bitmap, index) :: boolean 22 | @callback unset(bitmap, index) :: bitmap 23 | @callback unset_all(bitmap) :: bitmap 24 | @callback toggle(bitmap, index) :: bitmap 25 | @callback toggle_all(bitmap) :: bitmap 26 | @callback to_string(bitmap) :: String.t 27 | @callback inspect(bitmap) :: String.t 28 | 29 | defdelegate new(argument), to: Bitmap.Integer 30 | defdelegate at(bitmap, index), to: Bitmap.Integer 31 | defdelegate set?(bitmap, index), to: Bitmap.Integer 32 | defdelegate set(bitmap, index), to: Bitmap.Integer 33 | defdelegate set_all(bitmap), to: Bitmap.Integer 34 | defdelegate unset?(bitmap, index), to: Bitmap.Integer 35 | defdelegate unset(bitmap, index), to: Bitmap.Integer 36 | defdelegate unset_all(bitmap), to: Bitmap.Integer 37 | defdelegate toggle(bitmap, index), to: Bitmap.Integer 38 | defdelegate toggle_all(bitmap), to: Bitmap.Integer 39 | defdelegate to_string(bitmap), to: Bitmap.Integer 40 | defdelegate inspect(bitmap), to: Bitmap.Integer 41 | end 42 | -------------------------------------------------------------------------------- /lib/bitmap/binary.ex: -------------------------------------------------------------------------------- 1 | defmodule Bitmap.Binary do 2 | @moduledoc """ 3 | Contains functions to create and work with a [bitmap](https://en.wikipedia.org/wiki/Bitmap) 4 | 5 | Bitmaps are also known as bit arrays, bit sets and is a fast space efficient 6 | data structure for lookups 7 | 8 | The module has been designed to be pipe-friendly, so pipe 'em up 9 | """ 10 | import Kernel, except: [to_string: 1] 11 | 12 | @behaviour Bitmap 13 | 14 | @type t :: binary 15 | @type argt :: non_neg_integer | [any] | Range.t 16 | @type index :: non_neg_integer 17 | @type bit :: 1 | 0 18 | 19 | @set_bit 1 20 | @unset_bit 0 21 | 22 | @doc """ 23 | Creates and returns a bitmap of size corresponding to the `argument` passed. 24 | 25 | If `argument` is 26 | - integer, size of bitmap is equal to the `argument` 27 | - range, size of bitmap is equal to the length of `argument` 28 | - list, size of bitmap is equal to the length of `argument` 29 | 30 | > Note: All bits are set to 0 by default 31 | 32 | ## Examples 33 | iex> Bitmap.Binary.new(400) 34 | <<0::size(400)>> 35 | iex> Bitmap.Binary.new([1,2,3,4,5]) 36 | <<0::size(5)>> 37 | iex> Bitmap.Binary.new(1..25) 38 | <<0::size(25)>> 39 | """ 40 | @spec new(argt) :: __MODULE__.t 41 | def new(argument) 42 | def new(size) when is_integer(size), do: <<0::size(size)>> 43 | def new(list) when is_list(list), do: new(length(list)) 44 | def new(a..b), do: new(abs(b - a) + 1) 45 | 46 | @doc """ 47 | Returns the bit value at `index` in the bitmap 48 | 49 | ## Examples 50 | iex> bm = Bitmap.Binary.new(5) 51 | iex> Bitmap.Binary.at(bm, 2) 52 | 0 53 | iex> bm = Bitmap.Binary.set(bm, 2) 54 | iex> Bitmap.Binary.at(bm, 2) 55 | 1 56 | """ 57 | @spec at(__MODULE__.t, index) :: bit 58 | def at(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 59 | bitmap |> split_at(index) |> elem(1) 60 | end 61 | 62 | @doc """ 63 | Returns a boolean representing whether the bit at position `index` 64 | is set or not 65 | 66 | ## Examples 67 | iex> bm = Bitmap.Binary.new(5) |> Bitmap.Binary.set(1) |> Bitmap.Binary.set(3) 68 | iex> Bitmap.Binary.set?(bm, 1) 69 | true 70 | iex> Bitmap.Binary.set?(bm, 4) 71 | false 72 | """ 73 | @spec set?(__MODULE__.t, index) :: boolean 74 | def set?(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 75 | at(bitmap, index) == @set_bit 76 | end 77 | 78 | @doc """ 79 | Sets the bit at `index` in the bitmap and returns the new bitmap 80 | 81 | Index can also have a value `:all` in which case all bits 82 | will be set like in set_all 83 | 84 | ## Examples 85 | iex> Bitmap.Binary.set(Bitmap.Binary.new(5), 3) 86 | <<2::size(5)>> 87 | iex> Bitmap.Binary.set(Bitmap.Binary.new(1..10), 2) 88 | <<32, 0::size(2)>> 89 | """ 90 | @spec set(__MODULE__.t, index) :: __MODULE__.t 91 | def set(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 92 | set_bit(bitmap, index, @set_bit) 93 | end 94 | def set(bitmap, :all), do: set_all(bitmap) 95 | 96 | @doc """ 97 | Set all bits in the bitmap and returns a new bitmap 98 | 99 | ## Examples 100 | iex> Bitmap.Binary.set_all(Bitmap.Binary.new(10)) 101 | <<255, 3::size(2)>> 102 | iex> Bitmap.Binary.set_all(Bitmap.Binary.new(100)) 103 | <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15::size(4)>> 104 | """ 105 | @spec set_all(__MODULE__.t) :: __MODULE__.t 106 | def set_all(bitmap) do 107 | import Bitmap.Utils, only: [pow: 2] 108 | 109 | bitmap_size = bit_size(bitmap) 110 | <> 111 | end 112 | 113 | @doc """ 114 | Returns a boolean representing whether the bit at position `index` 115 | is unset or not 116 | 117 | ## Examples 118 | iex> bm = Bitmap.Binary.new(5) |> Bitmap.Binary.set(1) |> Bitmap.Binary.set(3) 119 | iex> Bitmap.Binary.unset?(bm, 1) 120 | false 121 | iex> Bitmap.Binary.unset?(bm, 4) 122 | true 123 | """ 124 | @spec unset?(__MODULE__.t, index) :: boolean 125 | def unset?(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 126 | at(bitmap, index) == @unset_bit 127 | end 128 | 129 | @doc """ 130 | Unsets the bit at `index` in the bitmap and returns the new bitmap 131 | 132 | Index can also have a value `:all` in which case all bits 133 | will be unset like in unset_all 134 | 135 | ## Examples 136 | iex> bm = Bitmap.Binary.new(10) |> Bitmap.Binary.set(4) |> Bitmap.Binary.set(8) 137 | iex> Bitmap.Binary.unset(bm, 4) 138 | <<0, 2::size(2)>> 139 | iex> Bitmap.Binary.unset(bm, 8) 140 | <<8, 0::size(2)>> 141 | """ 142 | @spec unset(__MODULE__.t, index) :: __MODULE__.t 143 | def unset(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 144 | set_bit(bitmap, index, @unset_bit) 145 | end 146 | def unset(bitmap, :all), do: unset_all(bitmap) 147 | 148 | @doc """ 149 | Unsets all bits in the bitmap and returns a new bitmap 150 | 151 | ## Examples 152 | iex> bm = Bitmap.Binary.new(10) |> Bitmap.Binary.set(4) |> Bitmap.Binary.set(8) 153 | iex> Bitmap.Binary.unset_all(bm) 154 | <<0, 0::size(2)>> 155 | """ 156 | @spec unset_all(__MODULE__.t) :: __MODULE__.t 157 | def unset_all(bitmap) do 158 | bitmap_size = bit_size(bitmap) 159 | <<0::size(bitmap_size)>> 160 | end 161 | 162 | @doc """ 163 | Toggles the bit at `index` in the bitmap and returns the new bitmap 164 | i.e. it sets the bit to 1 if it was 0 or sets the bit to 0 if it was 1 165 | 166 | Index can also have a value `:all` in which case all bits will be toggled 167 | like in toggle_all 168 | 169 | ## Examples 170 | iex> bm = Bitmap.Binary.new(10) |> Bitmap.Binary.set(4) |> Bitmap.Binary.set(8) 171 | iex> Bitmap.Binary.toggle(bm, 3) 172 | <<24, 2::size(2)>> 173 | iex> Bitmap.Binary.toggle(bm, 6) 174 | <<10, 2::size(2)>> 175 | """ 176 | @spec toggle(__MODULE__.t, index) :: __MODULE__.t 177 | def toggle(bitmap, index) when index >= 0 and index < bit_size(bitmap) do 178 | {prefix, bit, rest} = split_at(bitmap, index) 179 | case bit do 180 | 1 -> <> 181 | 0 -> <> 182 | end 183 | end 184 | def toggle(bitmap, :all), do: toggle_all(bitmap) 185 | 186 | @doc """ 187 | Toggles all bits in the bitmap and returns a new bitmap 188 | 189 | ## Examples 190 | iex> bm = Bitmap.Binary.new(10) |> Bitmap.Binary.set(4) |> Bitmap.Binary.set(8) 191 | iex> Bitmap.Binary.toggle_all(bm) 192 | <<247, 1::size(2)>> 193 | """ 194 | @spec toggle_all(__MODULE__.t) :: __MODULE__.t 195 | def toggle_all(bitmap) do 196 | toggle_binary(bitmap, bit_size(bitmap), <<>>) 197 | end 198 | 199 | @doc """ 200 | Returns the string representation of the bitmap 201 | 202 | Note: This can be very long for huge bitmaps. 203 | """ 204 | @spec to_string(__MODULE__.t) :: String.t 205 | def to_string(bitmap) do 206 | to_string(bitmap, <<>>) 207 | end 208 | 209 | @doc """ 210 | Inspects the bitmap and returns the string representation of the bitmap 211 | 212 | Note: This can be very long for huge bitmaps. 213 | """ 214 | @spec inspect(__MODULE__.t) :: String.t 215 | def inspect(bitmap) do 216 | bitmap |> to_string |> IO.inspect 217 | end 218 | 219 | defp to_string(<<>>, acc), do: String.reverse(acc) 220 | defp to_string(<>, acc) do 221 | case bit do 222 | 1 -> to_string(rest, "#{@set_bit}" <> acc) 223 | 0 -> to_string(rest, "#{@unset_bit}" <> acc) 224 | end 225 | end 226 | 227 | defp set_bit(bitmap, index, bit) do 228 | {prefix, o_bit, rest} = split_at(bitmap, index) 229 | cond do 230 | o_bit == bit -> bitmap 231 | true -> <> 232 | end 233 | end 234 | 235 | defp split_at(bitmap, index) do 236 | <> = bitmap 237 | {prefix, bit, rest} 238 | end 239 | 240 | defp toggle_binary(_bitmap, 0, acc), do: reverse_binary(acc) 241 | defp toggle_binary(<>, size, acc) do 242 | case bit do 243 | 1 -> toggle_binary(rest, size-1, <<@unset_bit::size(1), acc::bitstring>>) 244 | 0 -> toggle_binary(rest, size-1, <<@set_bit::size(1), acc::bitstring>>) 245 | end 246 | end 247 | 248 | defp reverse_binary(binary), do: reverse_binary(binary, bit_size(binary), <<>>) 249 | 250 | defp reverse_binary(_binary, 0, acc), do: acc 251 | defp reverse_binary(<>, size, acc) do 252 | reverse_binary(rest, size-1, <>) 253 | end 254 | end -------------------------------------------------------------------------------- /lib/bitmap/integer.ex: -------------------------------------------------------------------------------- 1 | defmodule Bitmap.Integer do 2 | @moduledoc """ 3 | Bitmap behaviour implementation using arbitrarily sized integers. 4 | """ 5 | use Bitwise 6 | 7 | import Kernel, except: [to_string: 1] 8 | 9 | @behaviour Bitmap 10 | 11 | @typedoc """ 12 | A typed map which holds the integer bitmap as defined by the module struct 13 | """ 14 | @type t :: %__MODULE__{} 15 | @type argt :: non_neg_integer | [any] | Range.t 16 | @type index :: non_neg_integer 17 | @type bit :: 1 | 0 18 | 19 | @set_bit 1 20 | @unset_bit 0 21 | 22 | defstruct size: 0, data: 0 23 | 24 | @doc """ 25 | Creates and returns a bitmap of size corresponding to the `argument` passed. 26 | 27 | If `argument` is 28 | - integer, size of bitmap is equal to the `argument` 29 | - range, size of bitmap is equal to the length of `argument` 30 | - list, size of bitmap is equal to the length of `argument` 31 | 32 | > Note: All bits are set to 0 by default 33 | 34 | ## Examples 35 | iex> Bitmap.Integer.new(400) 36 | %Bitmap.Integer{data: 0, size: 400} 37 | iex> Bitmap.Integer.new([1,2,3,4,5]) 38 | %Bitmap.Integer{data: 0, size: 5} 39 | iex> Bitmap.Integer.new(1..25) 40 | %Bitmap.Integer{data: 0, size: 25} 41 | """ 42 | @spec new(argt) :: __MODULE__.t 43 | def new(argument) 44 | def new(size) when is_integer(size) and size >= 0, do: %__MODULE__{size: size} 45 | def new(list) when is_list(list), do: new(length(list)) 46 | def new(a..b), do: new(abs(b - a) + 1) 47 | 48 | @doc """ 49 | Returns the bit value at `index` in the bitmap 50 | 51 | ## Examples 52 | iex> bm = Bitmap.Integer.new(5) 53 | iex> Bitmap.Integer.at(bm, 2) 54 | 0 55 | iex> bm = Bitmap.Integer.set(bm, 2) 56 | iex> Bitmap.Integer.at(bm, 2) 57 | 1 58 | """ 59 | @spec at(__MODULE__.t, index) :: bit 60 | def at(bitmap, index) do 61 | (bitmap.data >>> index) &&& 1 62 | end 63 | 64 | @doc """ 65 | Returns a boolean representing whether the bit at position `index` 66 | is set or not 67 | 68 | ## Examples 69 | iex> bm = Bitmap.Integer.new(5) |> Bitmap.Integer.set(1) |> Bitmap.Integer.set(3) 70 | iex> Bitmap.Integer.set?(bm, 1) 71 | true 72 | iex> Bitmap.Integer.set?(bm, 4) 73 | false 74 | """ 75 | @spec set?(__MODULE__.t, index) :: boolean 76 | def set?(bitmap, index) do 77 | at(bitmap, index) == @set_bit 78 | end 79 | 80 | @doc """ 81 | Sets the bit at `index` in the bitmap and returns the new bitmap 82 | 83 | Index can also have a value `:all` in which case all bits 84 | will be set like in set_all 85 | 86 | ## Examples 87 | iex> Bitmap.Integer.set(Bitmap.Integer.new(5), 3) 88 | %Bitmap.Integer{data: 8, size: 5} 89 | iex> Bitmap.Integer.set(Bitmap.Integer.new(1..10), 2) 90 | %Bitmap.Integer{data: 4, size: 10} 91 | """ 92 | @spec set(__MODULE__.t, index) :: __MODULE__.t 93 | def set(%__MODULE__{size: size} = bitmap, index) when index >= 0 and index < size do 94 | %__MODULE__{bitmap | data: (bitmap.data ||| (@set_bit <<< index))} 95 | end 96 | def set(bitmap, :all), do: set_all(bitmap) 97 | 98 | @doc """ 99 | Set all bits in the bitmap and returns a new bitmap 100 | 101 | ## Examples 102 | iex> Bitmap.Integer.set_all(Bitmap.Integer.new(10)) 103 | %Bitmap.Integer{data: 1023, size: 10} 104 | iex> Bitmap.Integer.set_all(Bitmap.Integer.new(100)) 105 | %Bitmap.Integer{data: 1267650600228229401496703205375, size: 100} 106 | """ 107 | @spec set_all(__MODULE__.t) :: __MODULE__.t 108 | def set_all(bitmap) do 109 | import Bitmap.Utils, only: [pow: 2] 110 | %__MODULE__{bitmap | data: pow(2, bitmap.size) - 1} 111 | end 112 | 113 | @doc """ 114 | Returns a boolean representing whether the bit at position `index` 115 | is unset or not 116 | 117 | ## Examples 118 | iex> bm = Bitmap.Integer.new(5) |> Bitmap.Integer.set(1) |> Bitmap.Integer.set(3) 119 | iex> Bitmap.Integer.unset?(bm, 1) 120 | false 121 | iex> Bitmap.Integer.unset?(bm, 4) 122 | true 123 | """ 124 | @spec unset?(__MODULE__.t, index) :: boolean 125 | def unset?(bitmap, index) do 126 | at(bitmap, index) == @unset_bit 127 | end 128 | 129 | @doc """ 130 | Unsets the bit at `index` in the bitmap and returns the new bitmap 131 | 132 | Index can also have a value `:all` in which case all bits 133 | will be unset like in unset_all 134 | 135 | ## Examples 136 | iex> bm = Bitmap.Integer.new(10) |> Bitmap.Integer.set(4) |> Bitmap.Integer.set(8) 137 | iex> Bitmap.Integer.unset(bm, 4) 138 | %Bitmap.Integer{data: 256, size: 10} 139 | iex> Bitmap.Integer.unset(bm, 8) 140 | %Bitmap.Integer{data: 16, size: 10} 141 | """ 142 | @spec unset(__MODULE__.t, index) :: __MODULE__.t 143 | def unset(%__MODULE__{size: size} = bitmap, index) when index >=0 and index < size do 144 | %__MODULE__{bitmap | data: (bitmap.data &&& ~~~(@set_bit <<< index))} 145 | end 146 | def unset(bitmap, :all), do: unset_all(bitmap) 147 | 148 | @doc """ 149 | Unsets all bits in the bitmap and returns a new bitmap 150 | 151 | ## Examples 152 | iex> bm = Bitmap.Integer.new(10) |> Bitmap.Integer.set(4) |> Bitmap.Integer.set(8) 153 | iex> Bitmap.Integer.unset_all(bm) 154 | %Bitmap.Integer{data: 0, size: 10} 155 | """ 156 | @spec unset_all(__MODULE__.t) :: __MODULE__.t 157 | def unset_all(bitmap) do 158 | %__MODULE__{bitmap | data: 0} 159 | end 160 | 161 | @doc """ 162 | Toggles the bit at `index` in the bitmap and returns the new bitmap 163 | i.e. it sets the bit to 1 if it was 0 or sets the bit to 0 if it was 1 164 | 165 | Index can also have a value `:all` in which case all bits will be toggled 166 | like in toggle_all 167 | 168 | ## Examples 169 | iex> bm = Bitmap.Integer.new(10) |> Bitmap.Integer.set(4) |> Bitmap.Integer.set(8) 170 | iex> Bitmap.Integer.toggle(bm, 3) 171 | %Bitmap.Integer{data: 280, size: 10} 172 | iex> Bitmap.Integer.toggle(bm, 6) 173 | %Bitmap.Integer{data: 336, size: 10} 174 | """ 175 | @spec toggle(__MODULE__.t, index) :: __MODULE__.t 176 | def toggle(bitmap, index) do 177 | %__MODULE__{bitmap | data: (bitmap.data ^^^ (@set_bit <<< index))} 178 | end 179 | 180 | @doc """ 181 | Toggles all bits in the bitmap and returns a new bitmap 182 | 183 | ## Examples 184 | iex> bm = Bitmap.Integer.new(10) |> Bitmap.Integer.set(4) |> Bitmap.Integer.set(8) 185 | iex> Bitmap.Integer.toggle_all(bm) 186 | %Bitmap.Integer{data: -273, size: 10} 187 | """ 188 | @spec toggle_all(__MODULE__.t) :: __MODULE__.t 189 | def toggle_all(bitmap) do 190 | %__MODULE__{bitmap | data: ~~~bitmap.data} 191 | end 192 | 193 | @doc """ 194 | Returns the string representation of the bitmap 195 | 196 | Note: This can be very long for huge bitmaps. 197 | """ 198 | @spec to_string(__MODULE__.t) :: String.t 199 | def to_string(bitmap) do 200 | to_string(bitmap.data, bitmap.size, <<>>) 201 | end 202 | 203 | @doc """ 204 | Inspects the bitmap and returns the string representation of the bitmap 205 | 206 | Note: This can be very long for huge bitmaps. 207 | """ 208 | @spec inspect(__MODULE__.t) :: __MODULE__.t 209 | def inspect(bitmap) do 210 | bitmap |> to_string |> IO.inspect 211 | end 212 | 213 | defp to_string(_data, 0, acc), do: acc 214 | defp to_string(data, size, acc) do 215 | case data &&& 1 do 216 | 1 -> to_string(data >>> 1, size - 1, "1" <> acc) 217 | 0 -> to_string(data >>> 1, size - 1, "0" <> acc) 218 | end 219 | end 220 | end -------------------------------------------------------------------------------- /lib/bitmap/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule Bitmap.Utils do 2 | 3 | @doc """ 4 | Binary exponentiation to support large integers which :math.pow can't since 5 | it returns floats 6 | 7 | ## Examples 8 | iex> Bitmap.Utils.pow(2, 10) 9 | 1024 10 | iex> Bitmap.Utils.pow(2, 9) 11 | 512 12 | """ 13 | def pow(x, n) when is_integer(x) and is_integer(n) and n >= 0, do: pow(x, n, 1) 14 | 15 | defp pow(_x, 0, acc), do: acc 16 | defp pow(x, 1, acc), do: x * acc 17 | defp pow(x, n, acc) when rem(n, 2) == 0, do: pow(x * x, div(n, 2), acc) 18 | defp pow(x, n, acc) when rem(n, 2) == 1, do: pow(x * x, div(n - 1, 2), acc * x) 19 | end -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Bitmap.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :bitmap, 6 | version: "1.0.1", 7 | elixir: "~> 1.0", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | source_url: "https://github.com/hashd/bitmap-elixir", 11 | homepage_url: "https://github.com/hashd/bitmap-elixir", 12 | description: description(), 13 | package: package(), 14 | deps: deps()] 15 | end 16 | 17 | # Configuration for the OTP application 18 | # 19 | # Type `mix help compile.app` for more information 20 | def application do 21 | [applications: [:logger]] 22 | end 23 | 24 | # Dependencies can be Hex packages: 25 | # 26 | # {:mydep, "~> 0.3.0"} 27 | # 28 | # Or git/path repositories: 29 | # 30 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 31 | # 32 | # Type `mix help deps` for more examples and options 33 | defp deps do 34 | [ 35 | {:earmark, "~> 1.2", only: :dev}, 36 | {:ex_doc, "~> 0.15", only: :dev}, 37 | {:benchfella, "~> 0.3.0", only: :dev} 38 | ] 39 | end 40 | 41 | defp description do 42 | """ 43 | Package to help you create and work with bitmaps (https://en.wikipedia.org/wiki/Bitmap) 44 | """ 45 | end 46 | 47 | defp package do 48 | [ 49 | files: ["lib", "mix.exs", "README*", "LICENSE*"], 50 | contributors: ["Kiran Danduprolu", "parroty", "freandre"], 51 | licenses: ["MIT"], 52 | links: %{ 53 | "GitHub" => "https://github.com/hashd/bitmap-elixir" 54 | } 55 | ] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"benchfella": {:hex, :benchfella, "0.3.4", "41d2c017b361ece5225b5ba2e3b30ae53578c57c6ebc434417b4f1c2c94cf4f3", [:mix], []}, 2 | "earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []}, 3 | "ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}} 4 | -------------------------------------------------------------------------------- /test/bitmap_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BitmapTest do 2 | use ExUnit.Case 3 | 4 | test "create bitmaps with size" do 5 | assert Bitmap.Binary.new(0) == <<>> 6 | assert Bitmap.Binary.new(25) == <<0, 0, 0, 0::size(1)>> 7 | assert Bitmap.Binary.new(32) == <<0, 0, 0, 0>> 8 | assert Bitmap.Binary.new(120) == <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> 9 | end 10 | 11 | test "create bitmaps with range" do 12 | assert Bitmap.Binary.new(0..0) == <<0::size(1)>> 13 | assert Bitmap.Binary.new(0..10) == <<0, 0::size(3)>> 14 | assert Bitmap.Binary.new(512..591) == <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> 15 | assert Bitmap.Binary.new(-1..1) == <<0::size(3)>> 16 | assert Bitmap.Binary.new(-3..-1) == <<0::size(3)>> 17 | assert Bitmap.Binary.new(-1..-3) == <<0::size(3)>> 18 | end 19 | 20 | test "create bitmaps with list" do 21 | small_list = List.duplicate(5, 25) 22 | big_list = List.duplicate(10, 160) 23 | 24 | assert Bitmap.Binary.new([]) == <<>> 25 | assert Bitmap.Binary.new(small_list) == <<0, 0, 0, 0::size(1)>> 26 | assert Bitmap.Binary.new(big_list) == <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0>> 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/doc_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DocTest do 2 | use ExUnit.Case 3 | 4 | doctest Bitmap.Binary 5 | doctest Bitmap.Integer 6 | doctest Bitmap.Utils 7 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------