Skip to content

Instantly share code, notes, and snippets.

@kipcole9
Created July 5, 2022 00:32
Show Gist options
  • Save kipcole9/894ffbcaff77a7e12275499fed114aeb to your computer and use it in GitHub Desktop.
Save kipcole9/894ffbcaff77a7e12275499fed114aeb to your computer and use it in GitHub Desktop.
Index a binary at newlines and get a line using the index map
defmodule StringIndex do
@newline 10
@doc """
Returns a map of line numbers to
a 2-tuple that is the index at which
the line starts in `string` and the
length of the line.
### Example
iex> string = "1234567\n12345678\n1234567890"
"1234567\n12345678\n1234567890"
iex> StringIndex.index string
%{1 => {0, 7}, 2 => {8, 8}, 3 => {17, 10}}
"""
def index_map(string) do
{indices, _} =
for <<char::utf8 <- string>>, reduce: {[0], 0} do
{acc, index} ->
if char == @newline do
{[index + 1 | acc], index + 1}
else
{acc, index + 1}
end
end
indices
|> Enum.reverse()
|> with_line_and_length(byte_size(string), 1, %{})
end
@doc """
Gets a line from a string according to a
map produced by `index_map/1`.
### Example
iex> StringIndex.get_line string, 3, map
"1234567890"
"""
def get_line(string, line, index_map) do
case Map.fetch(index_map, line) do
{:ok, {index, size}} ->
:binary.part(string, {index, size})
other ->
other
end
end
defp with_line_and_length([index], size, line, map) do
Map.put(map, line, {index, size - index})
end
defp with_line_and_length([a, b | rest], size, line, map) do
map = Map.put(map, line, {a, b - a - 1})
with_line_and_length([b | rest], size, line + 1, map)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment