Appending bitstrings

I am trying to build up a bitstring from an ascii string of 0’s and 1’s.
So the idea is I am receiving an ascii string and then a single character ?1 or ?0.
But no matter how many type annotations I give it, I keep getting an argument error from Kernel.<> I guess because it wants a binary.
What is the proper way to go about this?

defmodule MakeBits do
  @spec char2bit(number) :: bitstring
  def char2bit(48), do: <<0::1>>
  def char2bit(49), do: <<1::1>>

  def append_ascii(ascii_bitstring, ascii_bit) do
    bitstring = Enum.reduce(String.to_charlist(ascii_bitstring), <<>>, fn x, acc -> acc <> char2bit(x) end)
    bitstring # TODO append ascii_bit
  end

  def test() do
    incoming_ascii_string = "10101"
    append_ascii(incoming_ascii_string, '1') == <<1::1, 0::1, 1::1, 0::1, 1::1, 1::1>>
  end
end

iex(2)> MakeBits.test()
** (ArgumentError) argument error
scratch.exs:107: anonymous fn/2 in MakeBits.append_ascii/2
(elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
scratch.exs:113: MakeBits.test/0

1 Like

Is this something that you want?

iex(2)> chars = "10101" |> String.to_charlist()
'10101'
iex(3)> chars ++ '1'
'101011'
iex(4)> chars ++ '0'
'101010'
1 Like

Kernel.<> only works on binaries, not bitstrings. You may want :erlang.list_to_bitstring/1:

iex(1)> a = <<1::1>>
iex(2)> b = <<0::1>>

iex(3)> :erlang.list_to_bitstring([a,b])
<<2::size(2)>>

iex(5)> c = :erlang.list_to_bitstring([a,b])
<<2::size(2)>>

iex(6)> c = :erlang.list_to_bitstring([c,b])
<<4::size(3)>>
2 Likes

You can concatenate bitstrings like this: <<a::bitstring, b::bitstring>>

This works:

defmodule MakeBits do
  @spec char2bit(number) :: bitstring
  def char2bit(48), do: <<0::1>>
  def char2bit(49), do: <<1::1>>

  def append_ascii(ascii_bitstring, ascii_bit) do
    bitstring =
      Enum.reduce(String.to_charlist(ascii_bitstring), <<>>, fn x, acc ->
        <<acc::bitstring, char2bit(x)::bitstring>>
      end)

    # TODO append ascii_bit
  end

  def test() do
    incoming_ascii_string = "10101"
    # Fixed order of match and using = instead of == so that test works
    <<1::1, 0::1, 1::1, 0::1, 1::1, 1::1>> = append_ascii(incoming_ascii_string, ?1)
  end
end

MakeBits.test()
3 Likes

How about this?

def append_ascii(ascii_bitstring, ascii_bit) do
  codepoints = String.to_charlist(ascii_bitstring) ++ ascii_bit
  for cp <- codepoints, do: char2bit(cp), into: <<>>
end

It passes your original test, but I think @Nicd did the right thing by fixing your test to accept a single codepoint as you said, rather than a single-element charlist as you actually wrote in your test.

I also rewrote the reduce into a comprehension with a binary collector.

You could also do it with bitstring generators, but you need to change the char2bit function too:

def char2bit(?0), do: 0
def char2bit(?1), do: 1

def append_ascii(ascii_bitstring, [ascii_bit]) do
  bitstring = for <<cp <- ascii_bitstring>>, do: <<char2bit(cp)::1>>, into: <<>>
  <<bitstring::bitstring, char2bit(ascii_bit)::1>>
end

Thanks to you both, I learned a lot and appreciate your help, this works beautifully now!