Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert list of codepoints (or binary) to string

Tags:

elixir

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:

  • Reading the docs on binaries, strings, and charlists
  • Reading about charlists on ElixirSchool
  • Trying to convert it from a charlist to a binary and then to a string after following this SO question

The attempt based on the last bullet led to:

MyList.caesar('ryvke', 13)
# => <<127, 194, 134, 194, 131, 120, 114>>
like image 527
nickcoxdotme Avatar asked Sep 24 '17 02:09

nickcoxdotme


2 Answers

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.

like image 62
Dogbert Avatar answered Sep 27 '22 18:09

Dogbert


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.)

like image 23
prater Avatar answered Sep 27 '22 17:09

prater