Strings are UTF-8 encoded.
Triple-quotes used for heredocs function slightly differently than Python’s triple quotes. Python keeps the initial linebreak unless the first line has
\. Elixir heredocs never keep the initial linebreak.A magīa of sigils.
- Case indicates if the text is to be interpolated (uppercase) or not (lowercase).
- Multiple delimiter choices.
- Some sigils have optional specifiers.
Double quotes declare strings, which are binary lists. Single quotes declare character lists. Vastly different in the Elixir world.
Elixir prints a list of integers as a string if each number in the list is a printable
codepoint, or character. For example:[6]displays as[6]but[8]displays as ‘\b’ (bell).[126]displays as~but[127]displays as[127].
The
next_codepointexample shows how to create a byte list iterator.I became more comfortable with using pattern matching and guard clauses at the function level vs. if/else clauses.
Exercises
StringsAndBinaries-1
Write a function that returns true if a single-quoted string contains only printable ASCII characters (space through tilde).
defmodule Ch11 do
defp is_printable?([], _) do
true
end
defp is_printable?([head | tail], range) do
if head in range do
is_printable?(tail, range)
else
raise "Not printable '#{head}'."
end
end
def printable?(clist) do
is_printable?(clist, 32..127)
end
end
Ch11.printable?('123')
Ch11.printable?('12ô')StringsAndBinaries-2
Write an anagram?(word1, word2) that returns true if its parameters are anagrams.
- I’m assuming this is for character lists, not strings.
- Must have identical collection of characters.
defmodule Ch11 do
def anagram?(word1, word2) do
Enum.sort(word1) == Enum.sort(word2)
end
end
Ch11.anagram?('abc', 'cba')StringsAndBinaries-3
Why does IEx print 'cat' as a string, but 'dog' as individual numbers?
- Because the head is a character list of printable characters and the entire list is not (since it has a sublist).
StringsAndBinaries-4
Write a function that takes a single-quoted string of the form number [+-*/] number and returns the result of the calculation.
The individual numbers do not have leading plus or minus signs.
- Cannot use
String.split()with character lists. - Does not try to identify an invalid equation.
- The reason for
?xnotation makes much more sense after this exercise. - Use an Erlang module to convert character list to integer.
defmodule Ch11 do
defp calc([?+ | tail], agg) do
{ first, _ } = :string.to_integer(Enum.reverse(agg))
{ second, _ } = :string.to_integer(tail)
IO.inspect(tail)
first + second
end
defp calc([?- | tail], agg) do
{ first, _ } = :string.to_integer(Enum.reverse(agg))
{ second, _ } = :string.to_integer(tail)
first - second
end
defp calc([?* | tail], agg) do
{ first, _ } = :string.to_integer(Enum.reverse(agg))
{ second, _ } = :string.to_integer(tail)
first * second
end
defp calc([?/ | tail], agg) do
{ first, _ } = :string.to_integer(Enum.reverse(agg))
{ second, _ } = :string.to_integer(tail)
first / second
end
defp calc([head | tail], agg) do
calc(tail, [head | agg])
end
def calculate(equation) do
calc(equation, [])
end
end
Ch11.calculate('10+3')Compared to Dave’s
- created number and operator parsers that skipped spaces;
- created an anonymous function to perform the math;
- used
divfor integer division.
StringsAndBinaries-5
Write a function that takes a list of double-quoted strings and prints each on a separate line, centered in a column that has the width of the longest string. Make sure it works with UTF characters.
defmodule Ch11 do
defp _longest([], length) do
length
end
defp _longest([head | tail], length) do
if String.length(head) > length do
_longest(tail, String.length(head))
else
_longest(tail, length)
end
end
defp _centered([], _) do
end
defp _centered([head | tail], padding) do
head_length = div(String.length(head), 2)
base = String.duplicate(" ", padding - head_length)
IO.puts(base <> head)
_centered(tail, padding)
end
def center(list) do
longest = _longest(list, 0)
IO.puts("Maximum string length: #{longest}.")
# Want 1/2 of numbers up front.
_centered(list, div(longest, 2))
end
end
Ch11.center(["cat", "zebra", "elephant"])Compared to Dave’s
- used a pipe’d approach
- created a tuple of string and string’s length so as to not calculate length twice.
- my approach is still to Python’ish. Need to start using piping more.
StringsAndBinaries-6
Write a function to capitalize the sentences in a string.
defmodule Ch11 do
defp until_period(<< head :: utf8, tail :: binary >>, agg) when head == ?. do
capitalize(tail, [head | agg])
end
defp until_period(<< head :: utf8, tail :: binary >>, agg) do
until_period(tail, [ head |agg ])
end
defp capitalize(<<>>, agg) do
:string.reverse(agg)
end
defp capitalize(<< head :: utf8, tail :: binary >>, agg) when head == 32 do
capitalize(tail, [:string.to_upper(head) | agg])
end
defp capitalize(<< head :: utf8, tail :: binary >>, agg) do
until_period(tail, [:string.to_upper(head) | agg])
end
def capitalize_sentences(sentences) when is_binary(sentences) do
# First character is start of sentence.
# Ignore leading spaces as it is not in the problem definition.
capitalize(sentences, <<>>)
end
end
Ch11.capitalize_sentences("oh. a dog. woof.")Compared to Dave’s
- I’m writing much more code;
- done again with pipe and using
String.split; - I interpreted problem as working with bytes. Dave uses String module.
StringsAndBinaries-7
Add tax to orders loaded from a file.
defmodule Ch11 do
def calculate_tax(path, tax_rates) do
read_orders(path)
|> Enum.map(fn order -> apply_tax(order, tax_rates) end )
end
def read_orders(path) do
file = File.open!(path)
[header | lines] = Enum.map(IO.stream(file, :line), &String.trim(&1))
header = String.split(header, ",")
header = Enum.map(header, &String.to_atom(&1))
Enum.map(lines, fn line -> parse_order(String.split(line, ","), header) end )
end
def parse_order(line, header) do
line = Enum.zip(header, line)
id = Keyword.get(line, :id)
line = Keyword.replace(line, :id, String.to_integer(id))
net_amount = Keyword.get(line, :net_amount)
line = Keyword.replace(line, :net_amount, String.to_float(net_amount))
ship_to = Keyword.get(line, :ship_to)
Keyword.replace(line, :ship_to, String.to_atom(ship_to))
end
def apply_tax(order, tax_rates) do
net_amount = Keyword.get(order, :net_amount)
ship_to = Keyword.get(order, :ship_to)
tax = Keyword.get(tax_rates, ship_to, 0)
Keyword.put(order, :total_amount, net_amount + (tax * net_amount))
end
end
tax_rates = [ NC: 0.075, TX: 0.08 ]
orders = Ch11.calculate_tax("orders.csv", tax_rates)
IO.inspect(orders)Compared to Dave’s
- didn’t use stream, so not as efficient for large order files
All notes and comments are my own opinion. Follow me at @rgacote@genserver.social