nickcoxdotme
nickcoxdotme

Reputation: 6707

Convert list of codepoints (or binary) to string

I've been working on the exercises from Dave Thomas's Programming in Elixir. I came across one that says to write a function (called caesar for some reason) that takes a charlist and an integer to add to each element in the charlist, cycling back around to 'a' if it goes past 'z', so you should be able to call it like

MyList.caesar('ryvke', 13)

and it should return a string.

I have a function that maps over the list and performs the addition, but it returns a charlist, and I can't figure out how to convert it to a string:

defmodule MyList do
  def caesar(list, n) do
    Enum.map list, &(perform_addition(&1, n))
    |> to_charlist
    |> to_string
  end

  defp perform_addition(char_val, n) when char_val < 122 do
    char_val + n
  end

  defp perform_addition(_, n) do
    97 + n
  end
end

I've tried:

The attempt based on the last bullet led to:

MyList.caesar('ryvke', 13)
# => <<127, 194, 134, 194, 131, 120, 114>>

Upvotes: 12

Views: 18370

Answers (3)

Stiakov
Stiakov

Reputation: 89

Some char values that are being returned from your algorithm are outbound of printable characters, that's the reason because you're getting a bitstring with UTF8 encoded binaries instead of a charlist.

My solution only contemplates the subtraction when the char's codepoint exceeds the ?z codepoint or addition when it is below the ?z codepoint value.

defmodule Caesar do
  def decoder(list \\ [], plus \\ 13)
  def decoder([], _plus), do: ''
  def decoder([head | tail], plus) when head + plus > ?z do
    [ head - plus | decoder(tail, plus)]
  end
  def decoder([head | tail], plus) when head + plus < ?z do
    [ head + plus | decoder(tail, plus)]
  end
end

Upvotes: 0

prater
prater

Reputation: 2530

The question as stated in the book is:

An Elixir single-quoted string is actually a list of individual character codes. Write a caesar(list, n) function that adds n to each list element, wrapping if the addition results in a character greater than z.

The answer above is almost surely the way you would do this in "real life", but the point of the exercise in the book is to do it with recursion, since it's in the chapter on recursion.

defmodule MyList do
  def caesar([], _n), do: []

  def caesar([head | tail], n) when head + n > ?z do
    [head + n - ?z + (?a - 1) | caesar(tail, n)]
  end

  def caesar([head | tail], n) do
    [head + n | caesar(tail, n)]
  end
end

This goes through the list and uses pattern matching to either add 13 to the characters ord value, or to wrap it back around within the lowercase ASCII area.

Admittedly, the question is not that clear about what "wrap" means (like wrap to 0 or what?) so you kinda have to intuit what the expected answer is before you know exactly what to do. (If you wrap to 0 you wind up with ^E being the first character, which is a pretty big hint.)

Upvotes: 1

Dogbert
Dogbert

Reputation: 222428

To answer the question in the title: you're looking for List.to_string/1:

iex(1)> List.to_string([97, 98, 99])
"abc"

The reason you're not getting a readable string back for those arguments is that your logic to rotate the value is incorrect. Here's how you can shift a lower case letter and rotate it back to a if it crosses z while not touching non lower case letters:

# ?a == 97, ?z == 122
defp add(ch, n) when ch in ?a..?z do
  rem((ch - ?a + n), 26) + ?a
end
defp add(ch, n) when ch in ?A..?Z do
  rem(ch - ?A + shift, 26) + ?A
end
defp add(ch, _), do: ch

With this, you just need to map the function over the input charlist and then call List.to_string/1:

def caesar(list, n) do
  list |> Enum.map(&add(&1, n)) |> List.to_string
end
iex(1)> MyList.caesar('ryvke', 13)
"elixr"

(called caesar for some reason)

This algorithm is known as the Caesar Cipher.

Upvotes: 35

Related Questions