Currently, I’m attempting (*without success—logic errors remain) to use/abuse `Stream.scan`

for this:

```
defmodule HilbertCurve do
use Bitwise
def point(d, order, ndims \\ 2) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L115-L149
# side_length = 2 ** order
# volume = side_length ** ndims
d
|> transpose(order, ndims)
|> gray_decode()
|> undo_excess_work(order)
end
def dist(x, order) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L201-L241
x
|> inverse_undo_excess_work(order)
|> gray_encode()
|> untranspose(order)
end
def gray_encode(x) do
# https://en.wikipedia.org/wiki/Gray_code?oldid=1077948578#Constructing_an_n-bit_Gray_code
Enum.map(Stream.zip(x, [0 | x]), fn {a, b} -> Bitwise.bxor(a, b) end)
end
def gray_decode(x) do
# https://en.wikipedia.org/wiki/Gray_code?oldid=1077948578#Constructing_an_n-bit_Gray_code
Enum.scan(x, 0, &Bitwise.bxor/2)
end
def transpose(i, order, ndims) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L85-L97
#TODO: figure out what this function does
i
|> to_bits(order * ndims)
|> Stream.chunk_every(ndims)
|> Stream.zip()
|> Stream.map(&Tuple.to_list/1)
|> Enum.map(&from_bits/1)
end
def untranspose(x, order) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L100-L112
#TODO: figure out what this function does
x
|> Stream.map(fn i -> to_bits(i, order) end)
|> Stream.zip()
|> Enum.map(&Tuple.to_list/1)
|> List.flatten()
|> from_bits()
end
def undo_excess_work(x, order) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L134-L147
# ndims = length(x)
{_, x} = scan_last(
((order - 2)..0),
{hd(x), x},
fn cur, acc ->
# The first field of `acc` is the x_0 accumulator
# The second field of `acc` is the current value of `x`
r = cur
{x_0, x} = acc
{list__x_0, reversed_list__x_i} = scan_unzip(
Enum.reverse(x),
{x_0, nil},
fn cur, acc ->
# The first field of `acc` is the x_0 accumulator
# The second field of `acc` is the updated value of `x[i]`
x_i = cur
{x_0, _} = acc
uew_op(r, x_i, x_0)
end
)
x_0 = List.last(list__x_0)
x = Enum.reverse(reversed_list__x_i)
{x_0, x} # return
end
)
x # return
end
def uew_op(r, x_i, x_0) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L139-L146
# Returns updated values of {x_0, x_i}
bit_r = 1 <<< r
low_bits = bit_r - 1
if (x_i &&& bit_r) == 0 do
swap_bits({x_0, x_i}, low_bits)
else
{invert_bits(x_0, low_bits), x_i}
end
end
def invert_bits(i, mask \\ -1) do
Bitwise.bxor(i, mask)
end
def swap_bits({a, b}, mask \\ -1) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L143-L146
t = Bitwise.bxor(a, b) &&& mask
a = Bitwise.bxor(a, t)
b = Bitwise.bxor(b, t)
{a, b}
end
def inverse_undo_excess_work(_x, _order) do
# https://github.com/galtay/hilbertcurve/blob/v2.0.5/hilbertcurve/hilbertcurve.py#L215-L226
:TODO
end
# def _to_bits(i, width) do
# <<i::size(width)>>
# end
def scan_last(iterable, initial_acc, f) do
Stream.scan(iterable, initial_acc, f)
|> Enum.take(-1)
|> hd()
end
def scan_unzip(iterable, initial_acc, f) do
Stream.scan(iterable, initial_acc, f)
|> Enum.unzip()
end
def to_bits(i, width) do
i
|> to_bitstring(width)
|> String.to_charlist()
|> Enum.map(&(&1 - hd '0'))
end
def to_bitstring(i, width \\ 0) do
Integer.to_string(i, 2)
|> String.pad_leading(width, "0")
end
# def from_bits(bs) when is_bitstring(bs) do
# padding_size = 7 - rem(:erlang.bit_size(bs) + 7, 8)
# padding = <<0::size(padding_size)>>
# :binary.decode_unsigned(<< padding, bs :: bitstring >>)
# end
def from_bits(l) when is_list(l) do
Enum.map(l, &(&1 + hd '0'))
|> List.to_string()
|> from_bitstring()
end
def from_bitstring(s) do
Integer.parse(s, 2)
|> (fn {i, ""} -> i end).()
end
end
#IO.puts("The following should be 1234567:")
#IO.inspect(HilbertCurve.dist([6, 1, 6, 5, 6, 3, 6], 4))
IO.puts("The following should be [1, 2, 3, 4, 5, 6, 7]:")
IO.inspect(HilbertCurve.point(2000640, 4, 7))
```

My main worry is that this isn’t “in the spirit of” how functional programming is supposed to be done—I feel like I’m just trying to blindly imitate the `for`

loops rather than getting at what’s *actually* happening at the math level—and that my wrong perspective in the translation is what’s ultimately leading to my difficulties in getting the logic errors out.

*(edit: I do intend to mark the internal functions private once it’s all working, but marking them all as public is extremely handy for debugging)*