Starting Elixir
Mix.install([
{:req, "~> 0.4.3"},
{:csv, "~> 3.2"},
{:statistex, "~> 1.0"}
])
Overview
Conceptos básicos en elixir
Tutorial Complementario en: https://elixir-lang.org/getting-started/introduction.html
Basic Pattern Match
x = 1
x
1 = x
2 = x
1 = whatever
{a, b, c} = {:hello, true, 1}
IO.inspect(a)
IO.inspect(b)
IO.inspect(c)
{a, b, c} = {:hello, "world"}
Matching Lists
Lists have some special matching cases
a = [1, 2, 3]
[head | tail] = a
IO.inspect(head)
IO.inspect(tail)
:ok
a = [1]
[head | tail] = a
IO.inspect(head)
IO.inspect(tail)
:ok
a = []
[head | tail] = a
head
# You may want to ignore parts of a match, that work for "_" underscore character
a = [1, 2, 3]
[head | _] = a
head
The Pin Operator
x = 1
^x = 1
x = 1
^x = 2
x = 1
a = [1, 2, 3]
[^x, b, c] = a
{b, c}
x = 2
a = [1, 2, 3]
[^x, b, c] = a
A bit of maps
user = %{"username" => "foo", "role" => "admin"}
user["username"]
# Atoms and access to them
user = %{username: "foo", role: "admin"}
{user.username, user.role}
A bit of data inmutability
a = 1
if a == 1 do
a = 2
IO.puts(a)
end
IO.puts(a)
map_a = %{"foo" => "bar"}
IO.inspect(map_a)
map_b = Map.put(map_a, "baz", "foo")
IO.inspect(map_b)
map_c = Map.delete(map_b, "baz")
IO.inspect(map_c)
# A common pattern to modify certain data structures
# Is to return the new variable
{val, map_d} = Map.pop(map_b, "baz")
IO.inspect({val, map_d})
true
A Common Pattern Match case
A common example is to use {:ok, something}
and {:error, something
}
{:ok, response} = Req.get("https://api.github.com/repos/wojtekmach/req")
response.body["description"]
# https://postman-echo.com/test return 404 always
case Req.get("https://postman-echo.com/test") do
{:ok, %{status: status, body: body}} when 200 <= status and status < 300 ->
body
{:ok, %{status: status}} ->
"non ok response with code #{status}"
{:error, error} ->
"an error happened #{inspect(error)}"
end
The Pipe Operator
a_map = %{"remove_me" => "wololooo"}
# Last output will be first argument of next operator
new_map = a_map |> Map.put("foo", "bar") |> Map.put("baz", "asdf") |> Map.delete("remove_me")
new_map
Functions
# Function always goes in modules
defmodule MyMod do
def sum(a, b) do
a + b
end
end
MyMod.sum(1, 2)
# default arguments
defmodule SayHi do
def say_hi(msg \\ "Hi") do
msg
end
end
hi1 = SayHi.say_hi()
hi2 = SayHi.say_hi("Hello")
{hi1, hi2}
defmodule MyTypedMod do
@spec sum(number, integer) :: number
def sum(a, b) do
a + b
end
end
MyTypedMod.sum(1.1, 2)
# @specs are useful for Editors and code checkers (elixir-ls, credo, dyalizer, ...)
MyTypedMod.sum(1, 2)
Functions and Pattern Match
defmodule Calculator do
def calc("+", a, b) do
a + b
end
def calc("-", a, b) do
a - b
end
def calc("*", a, b) do
a * b
end
end
IO.puts(Calculator.calc("+", 1, 2))
IO.puts(Calculator.calc("-", 1, 2))
IO.puts(Calculator.calc("*", 1, 2))
true
# a Match error
Calculator.calc("/", 1, 2)
The first Recursion
defmodule Recursion1 do
def repeat(msg, times \\ 3) do
if times > 0 do
IO.puts(msg)
repeat(msg, times - 1)
end
end
end
Recursion1.repeat("Hello")
Recursion with pattern match
defmodule Recursion2 do
def repeat(msg, times \\ 3)
def repeat(_msg, 0), do: true
def repeat(msg, times) do
IO.puts(msg)
repeat(msg, times - 1)
end
end
Recursion2.repeat("Hello")
# Lists and Recursion
defmodule ListsRecursion do
def sum([]), do: 0
def sum([head | tail]) do
head + sum(tail)
end
end
ListsRecursion.sum([1, 2, 3])
# Or You can Just use built in libraries
Enum.sum([1, 2, 3])
A bit of Structs
defmodule User do
defstruct [:email, name: "John", age: 27]
end
%User{}
%User{email: "foo@example.com"}
The Enum Module
The Enum module allows us to easily iterate data
a = [1, 2, 3]
# multiply by 2 each value with map
Enum.map(a, fn val -> val * 2 end)
require Enum
require Integer
a = 1..3
IO.inspect(Range.to_list(a), label: "variable a")
# Sum values that are odd, using Reduce
Enum.reduce(a, fn val, accumulator ->
IO.puts("value: #{val}, accumulator: #{accumulator}")
if Integer.is_odd(val) do
accumulator + val
else
accumulator
end
end)
a = 1..3
IO.inspect(Range.to_list(a), label: "variable a")
# Sum values that are odd, using Reduce
Enum.reduce(a, fn val, accumulator ->
IO.puts("value: #{val}, accumulator: #{accumulator}")
if Integer.is_even(val) do
accumulator + val
else
accumulator
end
end)
a = 1..3
start_value = 0
IO.inspect(Range.to_list(a), label: "variable a")
# Sum values that are odd, using Reduce
Enum.reduce(a, start_value, fn val, accumulator ->
IO.puts("value: #{val}, accumulator: #{accumulator}")
if Integer.is_even(val) do
accumulator + val
else
accumulator
end
end)
# Lets take numbers until a specific condition
a = 1..100
Enum.reduce_while(a, 0, fn val, acc ->
newval = val + acc
cond do
newval == 60 ->
{:halt, newval}
Integer.mod(val, 10) == 0 ->
{:cont, newval}
true ->
{:cont, acc}
end
end)
Parte 2: Leer Datos de mi CSV
stream =
File.stream!(
"topclickeds_07_2w.csv.gz",
[:compressed, read_ahead: 100_000],
1000
)
|> CSV.decode(headers: true, separator: ?\t)
# a lazy object to read data
csv_data = stream |> Enum.take(100_000) |> Enum.map(fn {:ok, item} -> item end)
Enum.take(csv_data, 3)
# Get top 5 queries
queries_count =
Enum.reduce(csv_data, %{}, fn %{"query" => query}, count_map ->
Map.update(count_map, query, 1, fn current -> current + 1 end)
end)
IO.inspect(queries_count)
queries_count
|> Enum.sort(fn {_query1, count1}, {_query2, count2} -> count1 > count2 end)
|> Enum.take(5)
# Mean of clicks by search query
# queries_sum = queries_count |> Map.values() |> Enum.sum()
queries_sum = 100_000
queries_count_values = Map.values(queries_count)
# [1528, 1148, ...]
num_queries = length(Map.keys(queries_count))
%{
"min" => Enum.min(queries_count_values),
"max" => Enum.max(queries_count_values),
"mean" => queries_sum / num_queries
}