Repository: lambdaclass/erlings Branch: main Commit: 3109c02dd6c3 Files: 198 Total size: 107.2 KB Directory structure: gitextract_kx806ov1/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── check-progress ├── concurrent/ │ ├── calculator/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── rebar.config │ │ ├── solution/ │ │ │ ├── calculator.app.src │ │ │ └── calculator.erl │ │ ├── src/ │ │ │ ├── calculator.app.src │ │ │ └── calculator.erl │ │ └── test/ │ │ ├── calculator_SUITE.erl │ │ └── calculator_test.erl │ ├── parallel_map/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── rebar.config │ │ ├── solution/ │ │ │ ├── parallel_map.app.src │ │ │ └── parallel_map.erl │ │ ├── src/ │ │ │ ├── parallel_map.app.src │ │ │ └── parallel_map.erl │ │ └── test/ │ │ ├── parallel_map_SUITE.erl │ │ └── parallel_map_test.erl │ ├── priority/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README.md │ │ ├── rebar.config │ │ ├── solution/ │ │ │ ├── priority.app.src │ │ │ └── priority.erl │ │ ├── src/ │ │ │ ├── priority.app.src │ │ │ └── priority.erl │ │ └── test/ │ │ ├── .keep │ │ ├── priority_SUITE.erl │ │ └── priority_test.erl │ └── ring_benchmark/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── ring.erl │ │ └── ring_benchmark.app.src │ ├── src/ │ │ ├── ring.erl │ │ └── ring_benchmark.app.src │ └── test/ │ ├── ring_benchmark_SUITE.erl │ └── ring_test.erl ├── distributed/ │ └── remote_fun/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── src/ │ │ ├── remote_fun.app.src │ │ ├── remote_fun_client.erl │ │ └── remote_fun_server.erl │ └── test/ │ └── .keep ├── libraries/ │ └── shortly/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── rebar.config │ ├── rebar3 │ ├── solution/ │ │ ├── config/ │ │ │ ├── node_1.config │ │ │ └── node_2.config │ │ ├── shortly.app.src │ │ ├── shortly_app.erl │ │ ├── shortly_db.erl │ │ ├── shortly_link_handler.erl │ │ ├── shortly_notification_algorithm.erl │ │ ├── shortly_notification_ets.erl │ │ ├── shortly_notification_pg2.erl │ │ ├── shortly_shortener.erl │ │ ├── shortly_sup.erl │ │ ├── shortly_syn.erl │ │ └── shortly_ws_handler.erl │ ├── src/ │ │ ├── shortly.app.src │ │ └── shortly_app.erl │ └── test/ │ ├── shortly_shortener_SUITE.erl │ └── shortly_shortener_test.erl ├── otp/ │ ├── pool/ │ │ ├── README.md │ │ ├── rebar.config │ │ ├── solution/ │ │ │ ├── poolie.app.src │ │ │ ├── poolie_app.erl │ │ │ ├── poolie_server.erl │ │ │ ├── poolie_sup.erl │ │ │ ├── poolie_worker.erl │ │ │ └── poolie_worker_sup.erl │ │ ├── src/ │ │ │ ├── poolie.app.src │ │ │ ├── poolie_app.erl │ │ │ ├── poolie_server.erl │ │ │ ├── poolie_sup.erl │ │ │ ├── poolie_worker.erl │ │ │ └── poolie_worker_sup.erl │ │ └── test/ │ │ ├── poolie_SUITE.erl │ │ └── poolie_test.erl │ └── shopping_cart/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── shopping_cart.app.src │ │ └── shopping_cart.erl │ ├── src/ │ │ ├── shopping_cart.app.src │ │ └── shopping_cart.erl │ └── test/ │ ├── shopping_cart_SUITE.erl │ └── shopping_cart_test.erl └── sequential/ ├── bank_accounts/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── bank_account.app.src │ │ └── bank_account.erl │ ├── src/ │ │ ├── bank_account.app.src │ │ └── bank_account.erl │ └── test/ │ ├── bank_account_SUITE.erl │ └── bank_account_test.erl ├── calculate_bmi/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── calculate_bmi.app.src │ │ ├── calculate_bmi.erl │ │ └── person_record.hrl │ ├── src/ │ │ ├── calculate_bmi.app.src │ │ ├── calculate_bmi.erl │ │ └── person_record.hrl │ └── test/ │ ├── calculate_bmi_SUITE.erl │ └── calculate_bmi_test.erl ├── filter_fibonacci_numbers/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── filter_fibonacci_numbers.app.src │ │ └── filter_fibonacci_numbers.erl │ ├── src/ │ │ ├── filter_fibonacci_numbers.app.src │ │ └── filter_fibonacci_numbers.erl │ └── test/ │ ├── filter_fibonacci_numbers_SUITE.erl │ └── filter_fibonacci_numbers_test.erl ├── filter_numbers/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── filter_numbers.app.src │ │ └── filter_numbers.erl │ ├── src/ │ │ ├── filter_numbers.app.src │ │ └── filter_numbers.erl │ └── test/ │ ├── filter_numbers_SUITE.erl │ └── filter_numbers_test.erl ├── hello/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── hello.app.src │ │ └── hello.erl │ ├── src/ │ │ ├── hello.app.src │ │ └── hello.erl │ └── test/ │ ├── hello_SUITE.erl │ └── hello_test.erl ├── hello_pattern/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── hello_pattern.app.src │ │ └── hello_pattern.erl │ ├── src/ │ │ ├── hello_pattern.app.src │ │ └── hello_pattern.erl │ └── test/ │ ├── hello_pattern_SUITE.erl │ └── hello_pattern_test.erl ├── insert_element_at/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── insert_element_at.app.src │ │ └── insert_element_at.erl │ ├── src/ │ │ ├── insert_element_at.app.src │ │ └── insert_element_at.erl │ └── test/ │ ├── insert_element_at_SUITE.erl │ └── insert_element_at_test.erl ├── installing/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── flake.nix │ ├── src/ │ │ ├── installing.app.src │ │ └── installing.erl │ └── test/ │ ├── installing_SUITE.erl │ └── installing_test.erl ├── lists/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── lists_exercises.app.src │ │ └── lists_exercises.erl │ ├── src/ │ │ ├── lists_exercises.app.src │ │ └── lists_exercises.erl │ └── test/ │ ├── lists_exercises_SUITE.erl │ └── lists_exercises_test.erl ├── maps/ │ ├── .gitignore │ ├── README.md │ ├── rebar.config │ ├── solution/ │ │ ├── maps_exercises.app.src │ │ └── maps_exercises.erl │ ├── src/ │ │ ├── maps_exercises.app.src │ │ └── maps_exercises.erl │ └── test/ │ ├── maps_exercises_SUITE.erl │ └── maps_exercises_test.erl └── regex/ ├── .gitignore ├── README.md ├── rebar.config ├── solution/ │ ├── regex.app.src │ └── regex.erl ├── src/ │ ├── regex.app.src │ └── regex.erl └── test/ ├── regex_SUITE.erl └── regex_test.erl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/test.yml ================================================ on: push: branches: [main] pull_request: types: [opened, repoened, synchronize] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.0.0 - uses: cachix/install-nix-action@v27 with: nix_path: nixpkgs=channel:nixos-unstable - name: Setup erlang run: cd sequential/installing && nix develop - name: Re-use nix flake to check erlang tests run: cd sequential/installing && nix develop --command bash -c "cd ../../ && make test PROFILE=ci" ================================================ FILE: .gitignore ================================================ .eunit deps *.o *.beam *.plt erl_crash.dump ebin/*.beam rel/example_project .concrete/DEV_MODE .rebar _build/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 LambdaClass Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ DIRS = $(filter-out _build/, $(dir $(wildcard */))) EXERCISES = $(patsubst %/, %, $(filter-out $(DIRS), $(dir $(wildcard */*/)))) CATEGORIES = $(subst /, , $(DIRS)) PROFILE ?= test .PHONY: test $(CATEGORIES) $(EXERCISES) check-progress check-progress: ./check-progress test: $(CATEGORIES) .SECONDEXPANSION: $(CATEGORIES): $$(filter $$@%, $(EXERCISES)) $(EXERCISES): cd $@ && rebar3 as $(PROFILE) ct ================================================ FILE: README.md ================================================ ![Build Status](https://github.com/lambdaclass/erlings/actions/workflows/test.yml/badge.svg) # Erlings: Small exercises to get you used to reading and writing Erlang code source: http://www.erlang.org/ > Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance. One of the most important parts of any learning process it the resolution of practical problems by one self, but is hard when you don't have anything to guide you or something to compare with. ## Must Read - Read [Learn you some Erlang for great good](http://learnyousomeerlang.com/) - [Erlang and code style](https://medium.com/@jlouis666/erlang-and-code-style-b5936dceb5e4) - Read our [guidelines](https://github.com/lambdaclass/guidelines) ## Exercises The exercises are divided in 5 sections. They are meant to be done as you read [Learn you some Erlang for great good](http://learnyousomeerlang.com/), but feel free to do them as you wish. Start by clicking one of the exercises below or just run: ``` $ make ``` ### A. Sequential Programming 1. [Installing](sequential/installing/) 1. [Filter numbers](sequential/filter_numbers/) 1. [Filter in](sequential/filter_numbers#filter-in) 1. [Filter out](sequential/filter_numbers#filter-out) 1. [Filter in values](sequential/filter_numbers#filter-in-values) 1. [Hello world](sequential/hello/) 1. [Hello pattern](sequential/hello_pattern/) 1. [Lists](sequential/lists/) 1. [Reverse](sequential/lists#reverse) 1. [Remove Consecutive](sequential/lists#remove-consecutive) 1. [Even Fibonacci Numbers](sequential/lists#even-fibonacci-numbers) 1. [Reduce](sequential/lists#reduce) 1. [Rotate Lists](sequential/lists#rotate-lists) 1. [Run-length Encoding of a List](sequential/lists#run-length-encoding-of-a-list) 1. [Any](sequential/lists#any) 1. [Anagram](sequential/lists#anagram) 1. [First Letter Last Letter](sequential/lists#first-letter-last-letter-game) 1. [Bank Accounts](sequential/bank_accounts/) 1. [Calculate BMI](sequential/calculate_bmi/) 1. [Insert element at position](sequential/insert_element_at/) 1. [Filter Fibonacci Numbers](sequential/filter_fibonacci_numbers/) 1. [Maps](sequential/maps/) 1. [Sum of Values](sequential/maps#sum-of-values) 1. [Min Value](sequential/maps#min-value) 1. [Sort keys](sequential/maps/#sort-keys) 1. [Return values](sequential/maps/#return-values) 1. [Mapping a Map](sequential/maps#mapping-a-map) 1. [Merge Map](sequential/maps#merge-map) 1. [List to Map](sequential/maps#list-to-map) 1. [Records to Maps](sequential/maps#records-to-maps) 1. [Maps to Records](sequential/maps#maps-to-records) 1. [Proplist to Map](sequential/maps#proplist-to-map) 1. [Implement a simple regex engine](sequential/regex/) ### B. Concurrent Programming 1. [Parallel Map](concurrent/parallel_map) 1. [Calculator](concurrent/calculator) 1. [Priority](concurrent/priority) 1. [Ring Benchmark](concurrent/ring_benchmark) ### C. OTP 1. [Shopping Cart](otp/shopping_cart/) 1. [Worker pool](/otp/pool) ### D. Distributed Programming 1. [Remote Function](distributed/remote_fun/) ### E. Libraries 1. [Shortly](libraries/shortly/) ## Down the Rabbit Hole ### Advanced Erlang - [How to build stable systems](https://medium.com/@jlouis666/how-to-build-stable-systems-6fe9dcf32fc4) - [Stacking Theory for Systems Design](https://medium.com@jlouis666/stacking-theory-for-systems-design-2450e6300689) - [A Ramble Through Erlang IO Lists](http://prog21.dadgum.com/70.html) - [Erlang String Handling](https://medium.com/@jlouis666/erlang-string-handling-7588daad8f05) - [How Erlang does scheduling](http://jlouisramblings.blogspot.com.ar/2013/01/how-erlang-does-scheduling.html) - [Red and Green callbacks](https://joearms.github.io/published/2013-04-02-Red-and-Green-Callbacks.html) - [RTB: Where Erlang BLOOMs](https://ferd.ca/rtb-where-erlang-blooms.html) - [On Erlang, State and Crashes](http://jlouisramblings.blogspot.com.ar/2010/11/on-erlang-state-and-crashes.html) - [It's About the Guarantees](https://ferd.ca/it-s-about-the-guarantees.html) - [Don’t Lose Your ETS Tables](http://steve.vinoski.net/blog/2011/03/23/dont-lose-your-ets-tables/) - [The Road we didn't go down ](http://armstrongonsoftware.blogspot.com.ar/2008/05/road-we-didnt-go-down.html) - [Erlang Garbage Collection Details and Why It Matters](https://hamidreza-s.github.io/erlang%20garbage%20collection%20memory%20layout%20soft%20realtime/2015/08/24/erlang-garbage-collection-details-and-why-it-matters.html) - [Sequence and Order in Erlang](https://web.archive.org/web/20160419085030/http://notdennisbyrne.blogspot.com.ar/2008/04/sequence-and-order-in-erlang.html) - [The Erlang shell](https://medium.com/@jlouis666/the-erlang-shell-ab8d8bec3972) - [Queues Don't Fix Overload](https://ferd.ca/queues-don-t-fix-overload.html) - [https://www.erlang-in-anger.com/](https://www.erlang-in-anger.com/) - Whatever you can find in [spawned shelter](http://spawnedshelter.com/) ### Distributed systems and databases - [Distributed Systems: for fun and profit](http://book.mixu.net/distsys/single-page.html) - Designing Data-intensive applications book - [From the Ground Up: Reasoning About Distributed Systems in the Real World](https://bravenewgeek.com/from-the-ground-up-reasoning-about-distributed-systems-in-the-real-world/) - [Jepsen: On the perils of network partitions](https://aphyr.com/posts/281-jepsen-on-the-perils-of-network-partitions) - MapReduce: Simplified Data Processing on Large Clusters - Dynamo: Amazon’s Highly Available Key-value Store - Bigtable: A Distributed Storage System for Structured Data - Time, Clocks and Ordering of Events in a Distributed System - Unreliable failure detectors and reliable distributed systems ### Riak and riak_core - [Riak Core Tutorial](https://github.com/lambdaclass/riak_core_tutorial/) ### Elixir - [Erlang/Elixir Syntax: A Crash Course](https://elixir-lang.org/crash-course.html) - [Elixir: Introduction](https://elixir-lang.org/getting-started/introduction.html) - [Phoenix: Overview](https://hexdocs.pm/phoenix/overview.html) - [Phoenix: Up and Running](https://hexdocs.pm/phoenix/up_and_running.html#content) - [GenState](https://hexdocs.pm/gen_stage/GenStage.html) - [GenStage advanced](https://elixirschool.com/en/lessons/advanced/gen-stage/) ================================================ FILE: check-progress ================================================ #!/usr/bin/env escript %% -*- erlang -*- %%! -smp enable -sname factorial -mnesia debug verbose main(_) -> Folders = exercise_folders(), run_tests(Folders, 1, length(Folders)). run_tests([], _, _) -> io:format("All tests completed."), true; run_tests([Folder | MoreFolders], CurrentCount, TotalCount) -> case run_make_on_folder(Folder) of true -> io:format("~p.......ok~n", [Folder]), run_tests(MoreFolders, CurrentCount + 1, TotalCount); false -> print_failure(Folder, CurrentCount, TotalCount) end. print_failure(Folder, CurrentCount, TotalCount) -> io:format("Exercise `~s` failed.~n", [Folder]), io:format("Review it, you may be closer than you think.~n",[]), io:format("Progress: ~p/~p.~n", [CurrentCount-1, TotalCount]). run_make_on_folder(Folder) -> Output = cmd(Folder, "make"), string:str(Output, "tests passed.") > 0. cmd(Folder, Command) -> Cmd = io_lib:format("~s ~s", [Command, Folder]), CmdString = binary_to_list(iolist_to_binary(Cmd)), os:cmd(CmdString). exercise_folders() -> lists:append( [ in_folder("sequential", ["installing", "hello", "hello_pattern", "lists", "bank_accounts", "calculate_bmi", "insert_element_at", "filter_fibonacci_numbers", "maps", "regex"]), in_folder("concurrent", ["calculator", "parallel_map", "priority", "ring_benchmark"]), in_folder("otp", ["shopping_cart", "pool"]) %in_folder("distributed", ["remote_fun"]) %in_folder("libraries", ["shortly"]) ]). in_folder(Folder, SubFolders) -> [Folder ++ "/" ++ SubFolder || SubFolder <- SubFolders]. ================================================ FILE: concurrent/calculator/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: concurrent/calculator/README.md ================================================ # Calculator ## Reading Material - [The Hitchhiker's Guide to Concurrency](http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency) - [More On Multiprocessing](http://learnyousomeerlang.com/more-on-multiprocessing) ## Exercise You will write a simple calculator (sum, substraction, multiplication, and division) using processes and passing messages. For this you will write a module `calculator` and create the following functions: - `start_calculator/0`: This functions will spawn a `calculator:calculator_server/1`. - `calculator_server/0`: It will receive through messages the operation to execute and its arguments, and send back the result. - `turn_off/1`: Shuts down the calculator server. This is the bare minimum you need to make a working example, but we won't stop there so will use what you learned in ["We Love Messages, but we keep them secret"](http://learnyousomeerlang.com/more-on-multiprocessing#secret-messages) to abstract everything to simple functions the user can use without knowing about underlying protocol. For this create the functions: `add/3, subtract/3, multiply/3, divide/3`. This functions will receive as input PID and a tuple contaning the two numbers to operate on, send the message to the calculator server, and return the result. ```erlang 1> Cal = calculator:start_calculator(). <0.67.0> 2> calculator:add(Cal, 1, 3). 4 3> calculator:subtract(Cal, 1, 3). -2 4> calculator:multiply(Cal, 7, 3). 21 5> calculator:divide(Cal, 7, 3). 2.3333333333333335 6> calculator:divide(Cal, 0, 3). 0.0 7> calculator:divide(Cal, 2, 3). 0.6666666666666666 8> calculator:turn_off(Cal). off ``` Check our proposed [solution](solution/calculator.erl). ================================================ FILE: concurrent/calculator/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: concurrent/calculator/solution/calculator.app.src ================================================ {application, calculator, [{description, "calculator"}, {vsn, "0.1.0"}, {registered, []}, {mod, { calculator_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/calculator/solution/calculator.erl ================================================ -module(calculator). -export([start_calculator/0, calculator_server/0, add/3, subtract/3, multiply/3, divide/3, turn_off/1]). start_calculator() -> spawn(calculator, calculator_server, []). send_operation(Pid, Operation, Numbers) -> Pid ! {self(), Operation, Numbers}, receive Result -> Result end. add(Pid, X, Y) -> send_operation(Pid, add, {X, Y}). subtract(Pid, X, Y) -> send_operation(Pid, subtract, {X, Y}). multiply(Pid, X, Y) -> send_operation(Pid, multiply, {X, Y}). divide(Pid, X, Y) -> send_operation(Pid, divide, {X, Y}). calculator_server() -> receive {From, add, {X, Y}} -> From ! X + Y; {From, subtract, {X, Y}} -> From ! X - Y; {From, multiply, {X, Y}} -> From ! X * Y; {From, divide, {X, Y}} -> From ! X / Y; off -> exit(shutdown) end, calculator_server(). turn_off(Pid) -> Pid ! off. ================================================ FILE: concurrent/calculator/src/calculator.app.src ================================================ {application, calculator, [{description, "calculator"}, {vsn, "0.1.0"}, {registered, []}, {mod, { calculator_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/calculator/src/calculator.erl ================================================ -module(calculator). -export([start_calculator/0, calculator_server/0, turn_off/1]). start_calculator() -> put_your_solution_here. calculator_server() -> put_your_solution_here. turn_off(Pid) -> put_your_solution_here. ================================================ FILE: concurrent/calculator/test/calculator_SUITE.erl ================================================ -module(calculator_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(calculator_test). ================================================ FILE: concurrent/calculator/test/calculator_test.erl ================================================ -module(calculator_test). -include_lib("eunit/include/eunit.hrl"). calculator_add_test() -> Cal = calculator:start_calculator(), ?assertEqual(2+3, calculator:add(Cal, 2, 3)). calculator_subtract_test() -> Cal = calculator:start_calculator(), ?assertEqual(2-3, calculator:subtract(Cal, 2, 3)). calculator_multiply_test() -> Cal = calculator:start_calculator(), ?assertEqual(7*9, calculator:multiply(Cal, 7, 9)). calculator_divide_test() -> Cal = calculator:start_calculator(), ?assertEqual(25/5, calculator:divide(Cal, 25, 5)). ================================================ FILE: concurrent/parallel_map/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: concurrent/parallel_map/README.md ================================================ # Parallel Map ## Reading Material - [The Hitchhiker's Guide to Concurrency](http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency) - [More On Multiprocessing](http://learnyousomeerlang.com/more-on-multiprocessing) ## Exercise You will write `parallel_map:pmap/2` a parallel implementation of [lists:map](http://erlang.org/doc/man/lists.html#map-2). Every element in the input list must me processed by a separate process, this means that a list of 100 elements will spawn 100 processes. ```erlang 1> Fun = fun(X) -> X * 10 end, 2> List = [1, 2, 3, 4, 5], 3> parallel_map:pmap(Fun, List) [10,20,30,40,50] ``` Check our proposed [solution](solution/parallel_map.erl). ================================================ FILE: concurrent/parallel_map/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: concurrent/parallel_map/solution/parallel_map.app.src ================================================ {application, parallel_map, [{description, "parallel_map"}, {vsn, "0.1.0"}, {registered, []}, {mod, { parallel_map_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/parallel_map/solution/parallel_map.erl ================================================ -module(parallel_map). -export([pmap/2, apply/3]). pmap(Fun, List) -> Pids = lists:map(fun(Elem) -> spawn(parallel_map, apply, [self(), Fun, Elem]) end, List), lists:map(fun(Pid) -> receive {Pid, Result} -> Result end end, Pids). apply(From, Fun, Elem) -> From ! {self(), Fun(Elem)}. ================================================ FILE: concurrent/parallel_map/src/parallel_map.app.src ================================================ {application, parallel_map, [{description, "parallel_map"}, {vsn, "0.1.0"}, {registered, []}, {mod, { parallel_map_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/parallel_map/src/parallel_map.erl ================================================ -module(parallel_map). -export([pmap/2]). pmap(Fun, List) -> your_solution_here. ================================================ FILE: concurrent/parallel_map/test/parallel_map_SUITE.erl ================================================ -module(parallel_map_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(parallel_map_test). ================================================ FILE: concurrent/parallel_map/test/parallel_map_test.erl ================================================ -module(parallel_map_test). -include_lib("eunit/include/eunit.hrl"). parallel_map_result_test() -> Fun = fun(X) -> X * 10 end, List = [1, 2, 3, 4, 5], Result = lists:map(Fun, List), ?assertEqual(Result, parallel_map:pmap(Fun, List)). ================================================ FILE: concurrent/priority/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: concurrent/priority/Makefile ================================================ .PHONY: compile, run default: run compile: erl -compile priority run: compile erl -noshell -s priority send_test -s init stop ================================================ FILE: concurrent/priority/README.md ================================================ # Priority ## Reading material - [Learn You Some Erlang: More On Multiprocessing](http://learnyousomeerlang.com/more-on-multiprocessing) - [Erlang Manual: processes](http://erlang.org/doc/reference_manual/processes.html) - [Erlang Manual: receive expression](http://erlang.org/doc/reference_manual/expressions.html#id81776) ## Exercise For this exercise we will process messages according to priorities from the erlang message box without using any data structure. Messages with priority `vip` should be processed _as soon as_ they enter the `priority_loop`. Carefully consider how messages are processed by a `receive` block, and how you can _combine_ more than one block to match clauses in a certain order. Hints on how to solve this exercise found [here](http://learnyousomeerlang.com/more-on-multiprocessing). You will write 3 functions for the `priority` module: - `start/0`: This will create the process that will receive the messages (using `priority_loop/1`) and return its PID. - `get_messages/1`: Given the PID will return all the messages stored in the receive loop state. - `priority_loop/1`: This function will hold a list of messages received and read incoming messages. Remeber, messages need to be read according to their priority. Notes: - Messages are of the form `{Priority, Message}`. - `Priority` is either `vip` or `normal`. - `priority_loop/1` may receive other things to perform certain actions needed. You can check your answer by doing `make concurrent/priority` from the root directory of your [erlings](https://github.com/lambdaclass/erlings) folder. If you wish you can also check our proposed [solution](solution). ================================================ FILE: concurrent/priority/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: concurrent/priority/solution/priority.app.src ================================================ {application, priority, [{description, "priority"}, {vsn, "0.1.0"}, {registered, []}, {mod, { priority_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/priority/solution/priority.erl ================================================ -module(priority). -export([start/0, send_vip/2, send_normal/2, get_messages/1, priority_loop/1]). start() -> spawn_link(priority, priority_loop, [[]]). send_vip(Pid, Msg) -> Pid ! {vip, Msg}. send_normal(Pid, Msg) -> Pid ! {normal, Msg}. get_messages(Pid) -> Pid ! {server, {self(), current_msgs}}, receive Msgs -> Msgs end. priority_loop(State) -> receive {vip, Msg} -> priority_loop([{vip, Msg}] ++ State) after 0 -> receive {server, {From, current_msgs}} -> From ! lists:reverse(State); {Priority, Msg} -> priority_loop([{Priority, Msg}] ++ State) end end. ================================================ FILE: concurrent/priority/src/priority.app.src ================================================ {application, priority, [{description, "priority"}, {vsn, "0.1.0"}, {registered, []}, {mod, { priority_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: concurrent/priority/src/priority.erl ================================================ -module(priority). -export([start/0, get_messages/1, priority_loop/1]). start() -> your_solution_here. get_messages(Pid) -> your_solution_here. priority_loop(State) -> your_solution_here. ================================================ FILE: concurrent/priority/test/.keep ================================================ ================================================ FILE: concurrent/priority/test/priority_SUITE.erl ================================================ -module(priority_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(priority_test). ================================================ FILE: concurrent/priority/test/priority_test.erl ================================================ -module(priority_test). -include_lib("eunit/include/eunit.hrl"). sort_fun({vip, _}, {vip, _}) -> true; sort_fun({normal, _}, {normal, _}) -> true; sort_fun({vip, _}, {normal, _}) -> true; sort_fun({normal, _}, {vip, _}) -> false. priority_test() -> Msgs = [{normal, 1}, {normal, 2}, {vip, 3}, {vip, 4}, {normal, 5}, {normal, 6}, {normal, 7}, {vip, 8}, {vip, 9}], Msgs_ordered = lists:sort(fun sort_fun/2, Msgs), Pid = priority:start(), lists:foreach(fun(Msg) -> Pid ! Msg end, Msgs), ?assertEqual(Msgs_ordered, priority:get_messages(Pid)). ================================================ FILE: concurrent/ring_benchmark/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: concurrent/ring_benchmark/README.md ================================================ # Ring benchmark ## Reading Material ## Exercise Write a function `ring:ring/2` which takes 2 arguments: M and N. This function should create N process in a ring in such a way that sending a message to the first process it get passed around the ring M times so that a total of N * M messages get sent. In case you need any guidance please check our [proposed solution](solution/ring.erl). ================================================ FILE: concurrent/ring_benchmark/rebar.config ================================================ {erl_opts, [debug_info]}. {deps, []}. {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: concurrent/ring_benchmark/solution/ring.erl ================================================ -module(ring). -export([ring/2]). node_loop(Parent) -> receive {msg, []} -> Parent ! done; {msg, [FirstNode | OtherNodes]} -> io:format("Node ~p forwarding to ~p~n", [self(), FirstNode]), FirstNode ! {msg, OtherNodes}, node_loop(Parent) end. % N processes, M messages ring(N, M) -> [FirstNode | Nodes] = create_processes(N, M), BeforeFistMessage = os:timestamp(), FirstNode ! {msg, Nodes}, receive done -> io:format("done received ~n") end, AfterLastMessage = os:timestamp(), ElapsedTime = timer:now_diff(AfterLastMessage, BeforeFistMessage), io:format("Processes: ~p, Messages ~p in ~pms~n", [N, M, ElapsedTime]). create_processes(N, M) -> Parent = self(), Processes = [spawn_link(fun () -> node_loop(Parent) end) || _ <- lists:seq(1, N)], lists:append(lists:duplicate(M, Processes)). ================================================ FILE: concurrent/ring_benchmark/solution/ring_benchmark.app.src ================================================ {application, ring_benchmark, [{description, "An OTP application"}, {vsn, "0.1.0"}, {registered, []}, {mod, { ring_benchmark_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["Apache 2.0"]}, {links, []} ]}. ================================================ FILE: concurrent/ring_benchmark/src/ring.erl ================================================ -module(ring). -export([ring/2]). %% N processes, M messages ring(_N, _M) -> complete_here. ================================================ FILE: concurrent/ring_benchmark/src/ring_benchmark.app.src ================================================ {application, ring_benchmark, [{description, "An OTP application"}, {vsn, "0.1.0"}, {registered, []}, {mod, { ring_benchmark_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["Apache 2.0"]}, {links, []} ]}. ================================================ FILE: concurrent/ring_benchmark/test/ring_benchmark_SUITE.erl ================================================ -module(ring_benchmark_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(ring). ================================================ FILE: concurrent/ring_benchmark/test/ring_test.erl ================================================ -module(ring_test). -include_lib("eunit/include/eunit.hrl"). ring_test() -> ?assertMatch(#{msgs_sent := 4, procs_started := 2}, run_ring(2, 2)), ?assertMatch(#{msgs_sent := 12, procs_started := 4}, run_ring(4, 3)), ?assertMatch(#{msgs_sent := 380, procs_started := 19}, run_ring(19, 20)). run_ring(M, N) -> dbg_start_tracing(M, N), ring:ring(M, N), Result = dbg_wait_for_result(M,N), dbg_stop_tracing(), Result. dbg_start_tracing(M, N) -> dbg:stop_clear(), dbg:tracer(process, {fun dbg_fun_handler/2, dbg_init_state(M, N)}), dbg:p(new, [m,p]). dbg_stop_tracing() -> dbg:stop_clear(). dbg_wait_for_result(M, N) -> WaitTime = M * N * 1000, receive DbgState -> DbgState after WaitTime -> #{} end. dbg_init_state(M, N) -> #{m => M, n => N, ret_pid => self(), procs_started => 0, msgs_sent => 0, informed => false}. dbg_fun_handler(Trace, State) -> TracedState = dbg_capture_trace(Trace, State), dbg_inform_if_necessary(TracedState). dbg_capture_trace(Trace, State = #{procs_started := ProcsStarted, msgs_sent := MsgsSent}) -> case Trace of {_, _, spawned, _, _} -> State#{procs_started := ProcsStarted + 1}; {_, _, send, _, _} -> State#{msgs_sent := MsgsSent + 1}; _ -> State end. dbg_inform_if_necessary(State = #{m := M, n := N, ret_pid := RetPid}) -> MN = M * N, case State of #{msgs_sent := MN, informed := false} -> RetPid ! State#{informed => true}; _ -> State end. ================================================ FILE: distributed/remote_fun/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: distributed/remote_fun/Makefile ================================================ .PHONY: server client default: @echo "usage make [client|server]" client: ./rebar3 shell --sname 'client@localhost' server: ./rebar3 shell --sname 'server@localhost' ================================================ FILE: distributed/remote_fun/README.md ================================================ # Remote Function Server ## Reading material - [Learn you Some Erlang: Distribunomicon](http://learnyousomeerlang.com/distribunomicon) - [My favorite erlang program](https://joearms.github.io/published/2013-11-21-My-favorite-erlang-program.html) ## Exercise ### Problem Create an Erlang cluster with 2 nodes. One node is a server that just receives a function and become it. The other node sends a function the first and execute something remotely. ### Solution The server (`remote_fun_server.erl`) receives a message as normal, and get the function from there. The client (`remote_fun_client.erl`) creates a function that is able to receive and loop, and also to be killed, then it's send to the server. #### Running the solution Run two terminals with ``make server`` and ``make client``. Then connect the two nodes: ~~~ (client@localhost)1> net_kernel:connect_node(server@localhost). true ~~~ ~~~ (server@localhost)1> net_kernel:connect_node(client@localhost). true ~~~ After the connections are fine, you can test the functions with: ~~~ (server@localhost)2> remote_fun_server:function_server(). ~~~ ~~~ (client@localhost)2> remote_fun_client:function_client(server@localhost). Ok! ok ~~~ ================================================ FILE: distributed/remote_fun/src/remote_fun.app.src ================================================ {application, remote_fun, [{description, "Remote function"}, {vsn, "0.1.0"}, {registered, []}, {mod, { remote_fun_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: distributed/remote_fun/src/remote_fun_client.erl ================================================ -module(remote_fun_client). -export([function_client/1]). function_client(RemoteNode) -> FunctionServerPid = {function_server, RemoteNode}, FunctionServerPid ! {be, fun multiply_loop/0}, FunctionServerPid ! {self(), {3, 9}}, receive {product_result, _Result = 27} -> ok end, FunctionServerPid ! kill, io:format("Ok!~n"). multiply_loop() -> receive {From, {Factor1, Factor2}} -> Result = Factor1 * Factor2, From ! {product_result, Result}, multiply_loop(); kill -> kill end. ================================================ FILE: distributed/remote_fun/src/remote_fun_server.erl ================================================ -module(remote_fun_server). -export([function_server/0]). function_server() -> register(function_server, self()), receive {be, Function} -> Function() end. ================================================ FILE: distributed/remote_fun/test/.keep ================================================ ================================================ FILE: libraries/shortly/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: libraries/shortly/Makefile ================================================ .PHONY: default server samples test default: @echo "usage [test|server]" server: ./rebar3 shell --apps shortly node_1: ./rebar3 shell --apps shortly --config src/config/node_1.config --sname node_1 node_2: ./rebar3 shell --apps shortly --config src/config/node_2.config --sname node_2 ================================================ FILE: libraries/shortly/README.md ================================================ # Shortly: Link Shortener ## Reading material - [Rebar3 build tool](https://github.com/erlang/rebar3) - [Rebar3 hex](https://hex.pm/docs/rebar3_usage) - [Cowboy http server](https://github.com/ninenines/cowboy) - [Cowboy user-guide](https://ninenines.eu/docs/en/cowboy/2.2/guide/) ## Exercise This exercise will be divided in multiple parts: 1. [Shortly application](#shortly-application) 1. [Create two nodes](#create-two-nodes) 1. [Mnesia](#mnesia) 1. [Syn](#syn) ### Shortly application Create an ``OTP`` application using ``rebar3`` and [cowboy](https://github.com/ninenines/cowboy) that is capable of receiving long links and returning shorts ones: - Receive a ``HTTP POST`` at `http://localhost:8080/` returning a shortened link. - Receive a ``HTTP GET`` at `http://localhost:8080/` returning the original long link. - Accept websocket connections at `http://localhost:8080/news` and notify every time a new link is shortened. **BONUS:** Create similar endpoints (`GET` and `POST`), but using `cowboy_rest` handler. ### Create two nodes Now that you have a working application for a single node we need to make it distributed. For this you will need to do the following: - Receive cowboy's port as either a configuration variable or an environment variable. - Set the node name when starting it. ### Mnesia So we can have two nodes of our application running, but what is the point if they don't actually comunicate. To fix this we will make use of [Mnesia](http://erlang.org/doc/man/mnesia.html), which will let us have a distributed database across our nodes. To achieve this do the following: - Create the Mnesia schema. - Create the `shortly_urls` table. - Add the Mnesia application as a dependency (no more manual starts). - Change code to use Mnesia and abstract its usage to its own module `shortly_db`. If everything went smoothly you should be able to create a short URL in one node, ask the other node for the long URL, and get your answer. ### Syn For the final touch we need to let our users connect (websocket) to any node and still receive the notifications of link creations in all other nodes. For this task we will use [Syn](https://github.com/ostinelli/syn) and do the following: - Add the Syn application as a dependency. - Change code to use Syn and abstract it to a `shortly_presence` module. Now no matter to what node your users are connected whenever a URL is shortened by their node or any other they should receive the notification. ## Solution - ``src/bitly_short_link_handler.erl`` handles ``POST`` and ``GET`` requests. - ``src/bitly_shortener`` is a ``gen_server`` that uses ``ets`` for storing the links in memory. Being a separated process from the normal ``cowboy`` request handler is very important because the ``ets`` data is lost if the process which created it dies, and the ``cowboy`` handlers processes are created and destroyed in every request. To run the solution first star the server with ``make server`` and then ``make test`` for running simple requests. ================================================ FILE: libraries/shortly/rebar.config ================================================ {erl_opts, [debug_info]}. {deps, [cowboy, jsx, gun, syn]}. {plugins,[rebar3_hex]}. {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: libraries/shortly/solution/config/node_1.config ================================================ [ {mnesia, [{dir, "/tmp/mnesia/node_8010"}]}, {shortly, [{port, 8010}]} ]. ================================================ FILE: libraries/shortly/solution/config/node_2.config ================================================ [ {mnesia, [{dir, "/tmp/mnesia/node_8020"}]}, {shortly, [{port, 8020}]} ]. ================================================ FILE: libraries/shortly/solution/shortly.app.src ================================================ {application, shortly, [{description, "Link shortener exercise"}, {vsn, "0.1.0"}, {registered, []}, {mod, { shortly_app, []}}, {applications, [kernel, stdlib, mnesia, syn, cowboy ]}, {env,[{port, 8080}]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: libraries/shortly/solution/shortly_app.erl ================================================ -module(shortly_app). -behaviour(application). -export([start/2, stop/1, install/1]). -record(shortly_urls, {hash, url}). start(_StartType, _StartArgs) -> {ok, Port} = application:get_env(shortly, port), Dispatch = cowboy_router:compile( [ {'_', [ {"/news", shortly_ws_handler, []}, {"/:url", shortly_link_handler, []} ]} ]), {ok, _} = cowboy:start_clear(http, [{port, Port}], #{ env => #{dispatch => Dispatch} }), shortly_sup:start_link(). stop(_State) -> cowboy:stop_listener(http), ok. install(Nodes) -> mnesia:stop(), rpc:multicall(Nodes, application, stop, [mnesia]), ok = mnesia:create_schema([node() | Nodes]), mnesia:start(), rpc:multicall(Nodes, application, start, [mnesia]), mnesia:create_table(shortly_urls, [{attributes, record_info(fields, shortly_urls)}, {type, set}, {ram_copies, Nodes}]), mnesia:stop(). ================================================ FILE: libraries/shortly/solution/shortly_db.erl ================================================ -module(shortly_db). -export([store_url/1, save_url/2]). -record(shortly_urls, {hash, url}). store_url(ShortUrl) -> Result = mnesia:activity(transaction, fun() -> mnesia:read(shortly_urls, ShortUrl) end), case Result of [] -> []; [#shortly_urls{hash=ShortUrl, url=LongUrl}] -> [{ShortUrl, LongUrl}] end. save_url(ShortUrl, LongUrl) -> mnesia:activity(transaction, fun() -> mnesia:write(#shortly_urls{hash=ShortUrl, url=LongUrl}) end). ================================================ FILE: libraries/shortly/solution/shortly_link_handler.erl ================================================ -module(shortly_link_handler). -export([init/2]). init(Req, State) -> Url = get_request_url(Req), ReqMethod = cowboy_req:method(Req), {Status, ShortUrl} = handle_request(ReqMethod, Url), Body = get_body_response(Status, ShortUrl), Header = get_headers(Status, ShortUrl), Resp = cowboy_req:reply(Status, Header, Body, Req), {ok, Resp, State}. handle_request(<<"POST">>, Url) -> {CreationStatus, ShortUrl} = shortly_shortener:short(Url), HttpStatus = case CreationStatus of old -> 200; %ok new -> 201 %created end, {HttpStatus, ShortUrl}; handle_request(<<"GET">>, Url) -> case shortly_shortener:get(Url) of not_found -> {404, ""}; ShortUrl -> {302, ShortUrl} end. get_request_url(Req) -> <<"/", Url/binary>> = cowboy_req:path(Req), Url. get_body_response(404, _) -> <<>>; get_body_response(_, Url) -> jsx:encode(#{url => Url }). get_headers(Status, _) when Status =/= 302 -> common_headers(); get_headers(302, RedirectUrl) -> CommonHeaders = common_headers(), CommonHeaders#{<<"location">> => RedirectUrl }. common_headers() -> #{<<"content-type">> => <<"application/json">>}. ================================================ FILE: libraries/shortly/solution/shortly_notification_algorithm.erl ================================================ -module(shortly_notification_algorithm). -export([behaviour_info/1]). behaviour_info(callbacks) -> [{init,0}, {subscribe, 1}, {unsubscribe, 1}, {notify, 1}]. ================================================ FILE: libraries/shortly/solution/shortly_notification_ets.erl ================================================ -module(shortly_notification_ets). -behaviour(shortly_notification_algorithm). -export([init/0, subscribe/1, unsubscribe/1, notify/1]). -define(ETS_NAME, shortly_notification_ets_table). init() -> ets:new(?ETS_NAME, [set, public, named_table]). subscribe(Pid) -> ets:insert_new(?ETS_NAME, {Pid}). unsubscribe(Pid) -> ets:delete(?ETS_NAME, Pid). notify(Msg)-> ets:foldl( fun({Pid}, _) -> Pid ! Msg end,ignored, ?ETS_NAME). ================================================ FILE: libraries/shortly/solution/shortly_notification_pg2.erl ================================================ -module(shortly_notification_pg2). -behaviour(shortly_notification_algorithm). -export([init/0, subscribe/1, unsubscribe/1, notify/1]). -define (PG2_NAME, shortly_notification_pg2_name). init() -> pg2:create(?PG2_NAME). subscribe(Pid) -> pg2:join(?PG2_NAME, Pid). unsubscribe(Pid) -> pg2:leave(?PG2_NAME, Pid). notify(Msg) -> Subs = pg2:get_members(?PG2_NAME), lists:foreach( fun(Pid) -> Pid ! Msg end, Subs). ================================================ FILE: libraries/shortly/solution/shortly_shortener.erl ================================================ -module(shortly_shortener). -export([init/0, short/1, get/1, subscribe/1, unsubscribe/1]). init() -> notify_init(). short(LongUrl) -> ShortUrl = shortening_algorithm(LongUrl), EntryType = store_url(LongUrl, ShortUrl), notify_subscribers(EntryType, LongUrl, ShortUrl), {EntryType, ShortUrl}. get(ShortUrl) -> search_long_url(ShortUrl). subscribe(Pid) -> notify_subscribe(Pid). unsubscribe(Pid) -> notify_unsubscribe(Pid). %% Internal functions shortening_algorithm(Url) -> Hash = crypto:hash(md4, Url), Base64 = base64:encode(Hash), Sub = binary:part(Base64,1,5), WithoutSlashes = re:replace(Sub, "/", "_", [global, {return, list}]), list_to_binary(WithoutSlashes). store_url(LongUrl, ShortUrl) -> case shortly_db:store_url(ShortUrl) of [] -> shortly_db:save_url(ShortUrl, LongUrl), new; [{ShortUrl, LongUrl}] -> old end. search_long_url(ShortUrl) -> case shortly_db:store_url(ShortUrl) of [] -> not_found; [{ShortUrl, LongUrlEntry}] -> LongUrlEntry end. notify_technique_call(F,A) -> apply(shortly_syn,F,A). notify_init() -> notify_technique_call(init,[]). notify_subscribe(Pid) -> notify_technique_call(subscribe,[Pid]). notify_unsubscribe(Pid) -> notify_technique_call(unsubscribe,[Pid]). notify_notify(Msg) -> notify_technique_call(notify,[Msg]). notify_subscribers(old,_,_) -> nothing; notify_subscribers(new, LongUrl, ShortUrl) -> notify_notify(#{long_url => LongUrl, short_url => ShortUrl}). ================================================ FILE: libraries/shortly/solution/shortly_sup.erl ================================================ -module(shortly_sup). -behaviour(supervisor). -export([start_link/0, init/1]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> shortly_shortener:init(), {ok, { {one_for_all, 0, 1}, []} }. ================================================ FILE: libraries/shortly/solution/shortly_syn.erl ================================================ -module(shortly_syn). -behaviour(shortly_notification_algorithm). -export([init/0, subscribe/1, unsubscribe/1, notify/1]). -define(SYN_NAME, syn_ws_connections). init() -> syn:init(). subscribe(Pid) -> syn:join(?SYN_NAME, Pid). unsubscribe(Pid) -> pg2:leave(?SYN_NAME, Pid). notify(Msg) -> syn:publish(?SYN_NAME, Msg). ================================================ FILE: libraries/shortly/solution/shortly_ws_handler.erl ================================================ -module(shortly_ws_handler). -export([init/2, websocket_init/1, websocket_handle/2, websocket_info/2, terminate/3]). init(Req, Opts) -> {cowboy_websocket,Req,Opts}. websocket_init(State) -> shortly_shortener:subscribe(self()), {ok, State}. websocket_handle(_Msg, State) -> {ok, State}. websocket_info(#{long_url := LongUrl, short_url := ShortUrl}, State) -> Response = get_ws_json_response(LongUrl, ShortUrl), {reply, {text, Response}, State}; websocket_info(_Info, State) -> {ok, State}. terminate(_Msg, _Req, _State) -> shortly_shortener:unsubscribe(self()), ok. get_ws_json_response(LongUrl, ShortUrl) -> Data = #{ <<"long_url">> => LongUrl, <<"short_url">> => ShortUrl}, jsx:encode(Data). ================================================ FILE: libraries/shortly/src/shortly.app.src ================================================ {application, shortly, [{description, "Link shortener exercise"}, {vsn, "0.1.0"}, {registered, []}, {mod, { shortly_app, []}}, {applications, [kernel, stdlib, mnesia, syn, cowboy ]}, {env,[{port, 8080}]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: libraries/shortly/src/shortly_app.erl ================================================ -module(shortly_app). -behaviour(application). -export([]). ================================================ FILE: libraries/shortly/test/shortly_shortener_SUITE.erl ================================================ -module(shortly_shortener_SUITE). -include_lib("common_test/include/ct.hrl"). -compile(export_all). -compile(nowarn_export_all). -record(shortly_urls, {hash, url}). all() -> [test_notfound, test_created, test_ok, test_redirect, test_ws, run_eunit]. init_per_suite(Config) -> mnesia:start(), mnesia:create_table(shortly_urls, [{attributes, record_info(fields, shortly_urls)}, {type, set}, {ram_copies, [node()]}]), application:ensure_all_started(shortly), application:ensure_all_started(gun), Config. end_per_suite(Config) -> mnesia:clear_table(shortly_urls), application:stop(shortly), application:stop(gun), Config. init_per_testcase(_,Config) -> Config. end_per_testcase(_, Config) -> Config. test_notfound(_) -> NewUrl = url("http://not.com/existent"), {404, _, #{}} = do_get_request(NewUrl). test_created(_) -> NewUrl = url("http://new.created.com/"), {201, _, #{<<"url">> := _}} = do_post_request(NewUrl). test_ok(_) -> LongUrl = url("htts://random.org"), {201, _, #{<<"url">> := ShortUrl}} = do_post_request(LongUrl), {200, _, #{<<"url">> := ShortUrl}} = do_post_request(LongUrl). test_redirect(_) -> LongUrl = url("http://example.com/testing_redirect"), {201, _, #{<<"url">> := ShortUrl}} = do_post_request(LongUrl), {302, Headers, #{<<"url">> := LongUrl}} = do_get_request(ShortUrl), RedirectionHeader = list_to_binary(proplists:get_value("location", Headers)), LongUrl = RedirectionHeader. test_ws(_) -> WsConn = ws_connect("/news"), LongUrl = url("http://random.com/long"), {_, _, #{<<"url">> := ShortUrl}} = do_post_request(LongUrl), JsonResponse = ws_get(WsConn), Response = json_to_map(JsonResponse), #{<<"long_url">> := LongUrl} = Response, #{<<"short_url">> := ShortUrl} = Response, ws_terminate(WsConn). get_request_url(Url) -> BinaryReqUrl = iolist_to_binary([<<"http://localhost:8080/">>, Url]), UrlStr = binary_to_list(BinaryReqUrl), UrlStr. json_to_map(In) -> InBinary = case is_list(In) of true -> list_to_binary(In); _ -> In end, case jsx:is_json(InBinary) of true -> jsx:decode(InBinary, [return_maps]); _ -> #{} end. do_post_request(Url) -> ReqUrl = get_request_url(Url), {ok, {{_, StatusCode, _}, Headers, Body}} = httpc:request(post, {ReqUrl, [], [], []}, [], []), {StatusCode, Headers, json_to_map(Body)}. do_get_request(Url) -> ReqUrl = get_request_url(Url), {ok, {{_, StatusCode, _}, Headers, Body}} = httpc:request(get, {ReqUrl, []},[{autoredirect,false}],[]), {StatusCode, Headers, json_to_map(Body)}. url(Url) -> StringUrl = http_uri:encode(Url), list_to_binary(StringUrl). ws_connect(Path) -> {ok, Pid} = gun:open("127.0.0.1", 8080, #{retry=>0}), {ok, http} = gun:await_up(Pid), Ref = monitor(process, Pid), gun:ws_upgrade(Pid, Path, [], #{compress => true}), receive {gun_ws_upgrade, Pid, ok, _} -> ok; _ -> error(failed) end, {Pid, Ref}. ws_get({Pid,_}) -> receive {gun_ws, Pid, {text, Text}} -> Text; _ -> error(failed) after 5000 -> error(timout) end. ws_terminate({Pid, Ref}) -> demonitor(Ref), gun:close(Pid). run_eunit(_Config) -> ok = eunit:test(shortly_shortener_test). ================================================ FILE: libraries/shortly/test/shortly_shortener_test.erl ================================================ -module(shortly_shortener_test). -include_lib("eunit/include/eunit.hrl"). idempotence_test() -> shortly_shortener:init(), LongUrl = <<"long_url">>, {_, ShortUrl} = shortly_shortener:short(LongUrl), LongUrl = shortly_shortener:get(ShortUrl). ================================================ FILE: otp/pool/README.md ================================================ # Worker Pool ## Reading material - [Learn You Some Erlang: Who Supervises The Supervisors?](https://learnyousomeerlang.com/supervisors) - [Learn You Some Erlang: Building an Application With OTP](https://learnyousomeerlang.com/building-applications-with-otp) - [Erlang OTP Design Principles: Supervisor Behaviour](http://erlang.org/doc/design_principles/sup_princ.html) ## Exercise For this exercise create a pool of workers to compute a standard `{M, F, A}` or `{F, A}`. You will write a `gen_server` that controls the pool of workers. The supervision tree will look like this: ![Supervision tree](suptree.png) In `poolie_sup` and `poolie_worker_sup` you will define appropriate supervision strategies and child specs. `poolie_server` implements the following api: - `run/3`: Takes a module, a function and a list of args and dispatches the computation to an idle worker. If all workers are busy, asks user to try again later. - `run/2`: Same as `run/3`, but only takes a function and a list of args. - `pool_info/0`: Displays the number of idle and busy workers in the pool. Your task is to implement the `gen_server` callbacks in `poolie_server`, `poolie_worker_sup` and `poolie_worker` to handle the work requests. ### Example ```console 1> poolie_server:run(fun(X) -> X + 1 end, [5]). Request is being processed ok Got results for {#Fun,[5]} Result: 6 2> poolie_server:run(lists, max, [[1,2,3,4,5]]). Request is being processed ok Got results for {lists,max,[[1,2,3,4,5]]} Result: 5 ``` ### Notes - Think about what supervisor strategies you should use. - Should you use `gen_server:call` or `gen_server:cast` to send work to your workers? ================================================ FILE: otp/pool/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. {shell, [ {apps, [poolie]} ]}. ================================================ FILE: otp/pool/solution/poolie.app.src ================================================ {application, poolie, [{description, "An OTP application"}, {vsn, "0.1.0"}, {registered, []}, {mod, { poolie_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: otp/pool/solution/poolie_app.erl ================================================ %%%------------------------------------------------------------------- %% @doc poolie public API %% @end %%%------------------------------------------------------------------- -module(poolie_app). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %%==================================================================== %% API %%==================================================================== start(_StartType, _StartArgs) -> poolie_sup:start_link(). %%-------------------------------------------------------------------- stop(_State) -> ok. %%==================================================================== %% Internal functions %%==================================================================== ================================================ FILE: otp/pool/solution/poolie_server.erl ================================================ -module(poolie_server). -behaviour(gen_server). -export([start_link/0, stop/0, run/3, run/2, pool_info/0]). -export([init/1, handle_call/3, handle_cast/2]). -record(state, {limit, idle}). -define(N, 10). %% Number of workers %% API run(M, F, A) when is_list(A) -> Msg = gen_server:call(?MODULE, {work, {M, F, A}}), io:format(Msg). run(F, A) when is_function(F), is_list(A) -> Msg = gen_server:call(?MODULE, {work, {F, A}}), io:format(Msg). pool_info() -> {PoolSize, Idle, Busy} = gen_server:call(?MODULE, info), io:format("Pool has ~p workers.~nThere are ~p idle and ~p busy workers.~n", [PoolSize, Idle, Busy]). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> gen_server:call({local, ?MODULE}, stop). %% gen_server callbacks init(_Args) -> Workers = poolie_worker_sup:add_workers(?N), {ok, #state{limit=?N, idle=Workers}}. handle_call({work, MFA}, _From, S = #state{idle=[Worker | Rest]}) -> Msg = "Request is being processed~n", gen_server:cast(Worker, {work, MFA}), {reply, Msg, S#state{idle=Rest}}; handle_call({work, _MFA}, _From, State) -> Msg = "No idle workers at the moment, please try again later~n", {reply, Msg, State}; handle_call(info, _From, S = #state{limit=Limit, idle=Idle}) -> IdleWorkers = length(Idle), {reply, {Limit, IdleWorkers, Limit - IdleWorkers}, S}; handle_call(stop, _From, State) -> {stop, normal, stopped, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast({result, {Worker, MFA, Result}}, S = #state{idle=Idle}) -> io:format("Got results for ~p~nResult: ~p~n", [MFA, Result]), {noreply, S#state{idle=[Worker | Idle]}}; handle_cast(_Msg, State) -> {noreply, State}. ================================================ FILE: otp/pool/solution/poolie_sup.erl ================================================ %%%------------------------------------------------------------------- %% @doc poolie top level supervisor. %% @end %%%------------------------------------------------------------------- -module(poolie_sup). -behaviour(supervisor). %% API -export([start_link/0]). %% Supervisor callbacks -export([init/1]). %%==================================================================== %% API functions %%==================================================================== start_link() -> supervisor:start_link(?MODULE, []). %%==================================================================== %% Supervisor callbacks %%==================================================================== %% Child :: #{id => Id, start => {M, F, A}} %% Optional keys are restart, shutdown, type, modules. %% Before OTP 18 tuples must be used to specify a child. e.g. %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} init([]) -> WorkerSup = #{id => poolie_worker_sup, start => {poolie_worker_sup, start_link, []}, restart => permanent, shutdown => 10000, type => supervisor, modules => [poolie_worker_sup]}, Server = #{id => poolie_server, start => {poolie_server, start_link, []}, restart => permanent, shutdown => 10000, type => worker, modules => [poolie_server]}, {ok, {{one_for_all, 5, 150}, [WorkerSup, Server]}}. %%==================================================================== %% Internal functions %%==================================================================== ================================================ FILE: otp/pool/solution/poolie_worker.erl ================================================ -module(poolie_worker). -behaviour(gen_server). %% API -export([start/0]). -export([init/1, handle_call/3, handle_cast/2]). -define(SERVER, poolie_server). %% API start() -> gen_server:start(?MODULE, [], []). %% gen_server callbacks init(_Args) -> {ok, []}. handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast({work, MFA = {M, F, A}}, State) -> Result = erlang:apply(M, F, A), gen_server:cast(?SERVER, {result, {self(), MFA, Result}}), {noreply, State}; handle_cast({work, FA = {F, A}}, State) -> Result = erlang:apply(F, A), gen_server:cast(?SERVER, {result, {self(), FA, Result}}), {noreply, State}; handle_cast(_Msg, State) -> {noreply, State}. ================================================ FILE: otp/pool/solution/poolie_worker_sup.erl ================================================ -module(poolie_worker_sup). -behaviour(supervisor). -export([start_link/0, add_workers/1, init/1]). %% API start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). add_workers(N) when N >= 0 -> add_workers(N, []). add_workers(0, Workers) -> Workers; add_workers(N, Workers) -> {ok, Pid} = supervisor:start_child(?MODULE, []), add_workers(N-1, [Pid | Workers]). %% supervisor callbacks init(_Args) -> {ok, {{simple_one_for_one, 5, 500}, [{poolie_worker, {poolie_worker, start, []}, permanent, 5000, worker, [poolie_worker]}]}}. ================================================ FILE: otp/pool/src/poolie.app.src ================================================ {application, poolie, [{description, "An OTP application"}, {vsn, "0.1.0"}, {registered, []}, {mod, { poolie_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: otp/pool/src/poolie_app.erl ================================================ %%%------------------------------------------------------------------- %% @doc poolie public API %% @end %%%------------------------------------------------------------------- -module(poolie_app). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %%==================================================================== %% API %%==================================================================== start(_StartType, _StartArgs) -> poolie_sup:start_link(). %%-------------------------------------------------------------------- stop(_State) -> ok. %%==================================================================== %% Internal functions %%==================================================================== ================================================ FILE: otp/pool/src/poolie_server.erl ================================================ -module(poolie_server). -behaviour(gen_server). -export([start_link/0, stop/0, run/3, run/2, pool_info/0]). -export([init/1, handle_call/3, handle_cast/2]). -record(state, {limit, idle}). -define(N, 10). %% Number of workers %% API run(M, F, A) when is_list(A) -> Msg = gen_server:call(?MODULE, {work, {M, F, A}}), io:format(Msg). run(F, A) when is_function(F), is_list(A) -> Msg = gen_server:call(?MODULE, {work, {F, A}}), io:format(Msg). pool_info() -> {PoolSize, Idle, Busy} = gen_server:call(?MODULE, info), io:format("Pool has ~p workers.~nThere are ~p idle and ~p busy workers.~n", [PoolSize, Idle, Busy]). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> gen_server:call({local, ?MODULE}, stop). %% gen_server callbacks init(_Args) -> Workers = poolie_worker_sup:add_workers(?N), {ok, #state{limit=?N, idle=Workers}}. handle____({result, {Worker, MFA, Result}}, State) -> put_your_solution_here. handle_call({work, MFA}, _From, State) -> put_your_solution_here; handle_call(info, _From, S = #state{limit=Limit, idle=Idle}) -> IdleWorkers = length(Idle), {reply, {Limit, IdleWorkers, Limit - IdleWorkers}, S}; handle_call(stop, _From, State) -> {stop, normal, stopped, State}; handle_call(_Request, _From, State) -> {reply, ok, State}. handle_cast(_Msg, State) -> {noreply, State}. ================================================ FILE: otp/pool/src/poolie_sup.erl ================================================ %%%------------------------------------------------------------------- %% @doc poolie top level supervisor. %% @end %%%------------------------------------------------------------------- -module(poolie_sup). -behaviour(supervisor). %% API -export([start_link/0]). %% Supervisor callbacks -export([init/1]). %%==================================================================== %% API functions %%==================================================================== start_link() -> supervisor:start_link(?MODULE, []). %%==================================================================== %% Supervisor callbacks %%==================================================================== %% Child :: #{id => Id, start => {M, F, A}} %% Optional keys are restart, shutdown, type, modules. %% Before OTP 18 tuples must be used to specify a child. e.g. %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} init([]) -> WorkerSup = worker_supervisor_spec, Server = pool_server_spec, {ok, {{supervisor_strategy}, [WorkerSup, Server]}}. %%==================================================================== %% Internal functions %%==================================================================== ================================================ FILE: otp/pool/src/poolie_worker.erl ================================================ -module(poolie_worker). -behaviour(gen_server). %% API -export([start/0]). -export([init/1, handle_call/3, handle_cast/2]). -define(SERVER, poolie_server). %% API start() -> gen_server:start(?MODULE, [], []). %% gen_server callbacks init(_Args) -> {ok, []}. handle____({work, MFA = {M, F, A}}, State) -> put_your_solution_here; handle____({work, FA = {F, A}}, State) -> put_your_solution_here. handle_cast(_Msg, State) -> {noreply, State}. handle_call(_Request, _From, State) -> {reply, ok, State}. ================================================ FILE: otp/pool/src/poolie_worker_sup.erl ================================================ -module(poolie_worker_sup). -behaviour(supervisor). -export([start_link/0, add_workers/1, init/1]). %% API start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). add_workers(N) when N >= 0 -> put_your_solution_here. %% supervisor callbacks init(_Args) -> Worker = worker_spec, {ok, {{worker_supervisor_strategy}, [Worker]}}. ================================================ FILE: otp/pool/test/poolie_SUITE.erl ================================================ -module(poolie_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> {ok, _Pid} = application:ensure_all_started(poolie), ok = eunit:test(poolie_test). ================================================ FILE: otp/pool/test/poolie_test.erl ================================================ -module(poolie_test). -include_lib("eunit/include/eunit.hrl"). poolie_worker_sup_test() -> WorkerCount = worker_count(poolie_worker_sup), %% Add 5 workers WorkerList = poolie_worker_sup:add_workers(5), ?assertEqual(length(WorkerList), 5), NewWorkerCount = worker_count(poolie_worker_sup), ?assertEqual(NewWorkerCount, WorkerCount + 5). %% Helper functions worker_count(Sup) -> WorkerSup = supervisor:count_children(Sup), {workers, WorkerCount} = proplists:lookup(workers, WorkerSup), WorkerCount. ================================================ FILE: otp/shopping_cart/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: otp/shopping_cart/README.md ================================================ # Shopping Cart ## Reading Material - [Learn You Some Erlang: What is OTP?](http://learnyousomeerlang.com/what-is-otp) - [Learn You Some Erlang: Clients and Servers](http://learnyousomeerlang.com/clients-and-servers) ## Exercise In this exercise we are going to model a shopping cart. You can put items inside it (an item is just a tuple `{ItemName, Price}`) and ask it for the total cost of all the items. So, using a [gen_server](http://erlang.org/doc/man/gen_server.html), write the following functions: * `shopping_cart:start_link/0`: Start an empty shopping cart. * `shopping_cart:put_item/2`: Takes a PID and an item (a tuple) and saves it into the shopping cart. * `shopping_cart:cost_so_far/1`: Calculates the cost of all the items inside the shopping cart. * `finish/1`: Print the total price and finishes the shopping cart. * All the functions required by the behaviour. Example: ``` erlang {ok, Pid} = shopping_cart:start_link(). shopping_cart:put_item(Pid, {orange, 2}). %% [{orange, 2}] shopping_cart:put_item(Pid, {apple, 1}). %% [{apple, 1}, {orange, 2}] shopping_cart:cost_so_far(Pid). %% 3 ``` [solution](src/solution/shopping_cart.erl) ================================================ FILE: otp/shopping_cart/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: otp/shopping_cart/solution/shopping_cart.app.src ================================================ {application, shopping_cart, [{description, "shopping_part"}, {vsn, "0.1.0"}, {registered, []}, {mod, { shopping_cart_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: otp/shopping_cart/solution/shopping_cart.erl ================================================ -module(shopping_cart). -behaviour(gen_server). -export([start_link/0, put_item/2, finish/1, init/1, handle_call/3, terminate/2, cost_so_far/1, handle_cast/2 ]). %% Client functions start_link() -> gen_server:start_link(?MODULE, [], []). put_item(Pid, Item) -> gen_server:call(Pid, {item, Item}). cost_so_far(Pid) -> gen_server:call(Pid, cost). finish(Pid) -> gen_server:call(Pid, terminate). %% Server functions init([]) -> {ok, []}. handle_call({item, Item}, _From, Cart) -> {reply, [Item|Cart], [Item|Cart]}; handle_call(terminate, _From, Cart) -> {stop, normal, ok, Cart}; handle_call(cost, _From, Cart) -> Total_Price = total_price(Cart), {reply, Total_Price, Cart}. handle_cast(_, _) -> {noreply, ok}. terminate(normal, Cart) -> io:format("The total price was: ~p. ~n", [total_price(Cart)]), ok. %% Private functions total_price(Cart) -> Prices = lists:map(fun({_, Price}) -> Price end, Cart), lists:sum(Prices). ================================================ FILE: otp/shopping_cart/src/shopping_cart.app.src ================================================ {application, shopping_cart, [{description, "shopping_part"}, {vsn, "0.1.0"}, {registered, []}, {mod, { shopping_cart_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: otp/shopping_cart/src/shopping_cart.erl ================================================ -module(shopping_cart). -behaviour(gen_server). -export([start_link/0, put_item/2, finish/1, cost_so_far/1, ]). start_link() -> put_your_solution_here. put_item(Pid, Item) -> put_your_solution_here. cost_so_far(Pid) -> put_your_solution_here. finish(Pid) -> put_your_solution_here. ================================================ FILE: otp/shopping_cart/test/shopping_cart_SUITE.erl ================================================ -module(shopping_cart_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(shopping_cart_test). ================================================ FILE: otp/shopping_cart/test/shopping_cart_test.erl ================================================ -module(shopping_cart_test). -include_lib("eunit/include/eunit.hrl"). shopping_cart_add_item_test() -> {ok, Pid} = shopping_cart:start_link(), Orange = {orange, 3}, % tuple with name and price Ans = shopping_cart:put_item(Pid, Orange), ?assertEqual(Ans, [{orange, 3}]). shopping_cart_calculate_cost_test() -> {ok, Pid} = shopping_cart:start_link(), Apple = {apple, 2}, shopping_cart:put_item(Pid, Apple), Cost = shopping_cart:cost_so_far(Pid), ?assertEqual(Cost, 2). ================================================ FILE: sequential/bank_accounts/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/bank_accounts/README.md ================================================ # Bank Accounts ## Reading Material - [Learn You Some Erlang: Errors and Exceptions](http://learnyousomeerlang.com/errors-and-exceptions) ## Exercise Create a function `bank_account:process_operation/2` that takes a bank (a list of accounts: `{AccountNumber, AmountOfMoney}`) and an operation (a tuple: `{AccountNumber, Operation_Type, Amount}`) and process it, returning the resulting account. #### Operations There are two kind of operations: `withdraw` and `deposit`. #### Examples ``` erlang 1> bank_account:process_operation([{1, 100}, {2, 45}], {1, withdraw, 25}). %% {1, 75} 2> process_operation([{1, 100}, {2, 45}], {2, deposit, 15}). %% {2, 60} ``` #### Error Handling * If the account specified in the operation doesn't exist (i.e. there is no tuple in the list with that account number), return a tuple `{error, account_not_found}`. * If you try to withdraw from an account with insufficient funds, return a tuple `{error, insufficient_funds}`. #### Examples ``` erlang 1> bank_account:process_operation([{1, 100}, {2, 45}], {7, deposit, 55}). %% {error, account_not_found} 2> bank_account:process_operation([{1, 100}, {2, 45}], {2, withdraw, 100}). %% {error, insufficient_funds} ``` Run tests with `$> make`. As a hint, the file you should be editing is `src/bank_account.erl`. But in any case if the things get difficult you can check our [proposed solution](solution/bank_account.erl). ================================================ FILE: sequential/bank_accounts/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/bank_accounts/solution/bank_account.app.src ================================================ {application, bank_account, [{description, "bank_account"}, {vsn, "0.1.0"}, {registered, []}, {mod, { bank_account_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/bank_accounts/solution/bank_account.erl ================================================ -module(bank_account). -export([process_operation/2]). operate({AccountNumber, Money}, withdraw, Amount) -> case (Money - Amount) < 0 of true -> {error, insufficient_funds}; false -> {AccountNumber, Money - Amount} end; operate({AccountNumber, Money}, deposit, Amount) when Amount >= 0 -> {AccountNumber, Money + Amount}. process_operation(Bank, {AccountNumber, Operation_Type, Amount}) -> case lists:keyfind(AccountNumber, 1, Bank) of false -> {error, account_not_found}; Account -> operate(Account, Operation_Type, Amount) end. ================================================ FILE: sequential/bank_accounts/src/bank_account.app.src ================================================ {application, bank_account, [{description, "bank_account"}, {vsn, "0.1.0"}, {registered, []}, {mod, { bank_account_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/bank_accounts/src/bank_account.erl ================================================ -module(bank_account). -export([process_operation/2]). process_operation(Bank, Operation) -> put_your_solution_here. ================================================ FILE: sequential/bank_accounts/test/bank_account_SUITE.erl ================================================ -module(bank_account_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(bank_account_test). ================================================ FILE: sequential/bank_accounts/test/bank_account_test.erl ================================================ -module(bank_account_test). -include_lib("eunit/include/eunit.hrl"). process_operation_withdraw_ok_test() -> Bank = [{213, 150}, {214, 0}], Op = {213, withdraw, 50}, Res = {213, 100}, ?assertEqual(Res, bank_account:process_operation(Bank, Op)). process_operation_not_found_test() -> Bank = [{214, 0}], Op = {213, deposit, 20}, Res = {error, account_not_found}, ?assertEqual(Res, bank_account:process_operation(Bank, Op)). process_operation_insufficient_funds_test() -> Bank = [{213, 150}, {214, 0}], Op = {213, withdraw, 200}, Res = {error, insufficient_funds}, ?assertEqual(Res, bank_account:process_operation(Bank, Op)). ================================================ FILE: sequential/calculate_bmi/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/calculate_bmi/README.md ================================================ # Calculate BMI ## Reading Material - [Learn You Some Erlang](http://learnyousomeerlang.com/a-short-visit-to-common-data-structures) ## Exercise Write a function `calculate_bmi:bmi/1` that takes a person (the record defined in `src/person_record.hrl`) as argument and calculate her [body mass index (BMI)](https://en.wikipedia.org/wiki/Body_mass_index). Then, write `calculate_bmi:classify/1` to classify a person according to her BMI: * `underweight`: when the BMI is less than 18.5. * `normal`: when the BMI is greater than 18.5 and less than 25. * `overweight`: when the BMI is between 25 and 30. * `obese`: when the BMI is greater than 30. #### Examples ``` erlang 1> calculate_bmi:bmi(#person{name = "Maria", weight = 60, height = 1.6}). %% 23.437499999999996 2> calculate_bmi:classify(#person{name = "Maria", weight = 60, height = 1.6}). %% normal ``` Run tests with ``make``. As a hint, the file you should be editing is `src/calculate_bmi.erl`. But in any case if the things get difficult you can check our [proposed solution](solution/calculate_bmi.erl). ================================================ FILE: sequential/calculate_bmi/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/calculate_bmi/solution/calculate_bmi.app.src ================================================ {application, calculate_bmi, [{description, "calculate_bmi"}, {vsn, "0.1.0"}, {registered, []}, {mod, { calculate_bmi_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/calculate_bmi/solution/calculate_bmi.erl ================================================ -module(calculate_bmi). -export([bmi/1, classify/1]). -include("../src/person_record.hrl"). bmi(P) when is_record(P, person) -> P#person.weight / math:pow(P#person.height, 2). classify(P) when is_record(P, person) -> classify(bmi(P)); classify(BMI) when BMI > 25 andalso BMI < 30 -> overweight; classify(BMI) when BMI < 18.5 -> underweight; classify(BMI) when BMI > 30 -> obese; classify(BMI) when BMI > 18.5 andalso BMI < 25 -> normal. ================================================ FILE: sequential/calculate_bmi/solution/person_record.hrl ================================================ % person record declaration: % name % weight specified in kg. % height specified in metres. -record(person, {name, weight, height}). ================================================ FILE: sequential/calculate_bmi/src/calculate_bmi.app.src ================================================ {application, calculate_bmi, [{description, "calculate_bmi"}, {vsn, "0.1.0"}, {registered, []}, {mod, { calculate_bmi_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/calculate_bmi/src/calculate_bmi.erl ================================================ -module(calculate_bmi). -export([bmi/1, classify/1]). -include("../src/person_record.hrl"). bmi(Person) -> put_your_solution_here. classify(Person) -> put_your_solution_here. ================================================ FILE: sequential/calculate_bmi/src/person_record.hrl ================================================ % person record declaration: % name % weight specified in kg. % height specified in metres. -record(person, {name, weight, height}). ================================================ FILE: sequential/calculate_bmi/test/calculate_bmi_SUITE.erl ================================================ -module(calculate_bmi_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(calculate_bmi_test). ================================================ FILE: sequential/calculate_bmi/test/calculate_bmi_test.erl ================================================ -module(calculate_bmi_test). -include_lib("eunit/include/eunit.hrl"). -include("../src/person_record.hrl"). calculate_bmi_bmi_calculation_test() -> Person = #person{ name = "Alicia", weight = 62, height = 1.63}, ?assertEqual(23.335466144755166, calculate_bmi:bmi(Person)). calculate_bmi_classify_test() -> Person1 = #person{ name = "Alicia", weight = 62, height = 1.63}, Person2 = #person{ name = "Peter", weight = 104, height = 1.8}, Person3 = #person{ name = "Ana", weight = 52, height = 1.7}, Person4 = #person{ name = "Charles", weight = 84, height = 1.75}, ?assertEqual(normal, calculate_bmi:classify(Person1)), ?assertEqual(obese, calculate_bmi:classify(Person2)), ?assertEqual(underweight, calculate_bmi:classify(Person3)), ?assertEqual(overweight, calculate_bmi:classify(Person4)). ================================================ FILE: sequential/filter_fibonacci_numbers/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/filter_fibonacci_numbers/README.md ================================================ # Filter Fibonacci Numbers ## Reading material - [Fibonnaci number](https://en.wikipedia.org/wiki/Fibonacci_number) ## Exercise Create a function `filter_fibonacci_numbers:filter/1` that takes a list and filter every Fibonnaci number using **list comprehensions**. Tip: The nth Fibonnaci number, *Fn*, can be calculated by the sum of the two preceding ones. Using F0 = 1 and F1 = 1 as initial values we have: 1, 1, 2, 3, 5, 8, 13, 21, 54, ... Example: ``` erlang 1> filter_fibonacci_numbers:filter([1, 2, 3, 4, 5, 7, 8]). %% [1, 2, 3, 5, 8] ``` Write your answer in `src/filter_fibonacci_numbers.erl`. You can check your answer by doing `$> make` inside this directory. If things gets difficult you can check our [proposed solution](solution/filter_fibonacci_numbers.erl). ================================================ FILE: sequential/filter_fibonacci_numbers/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/filter_fibonacci_numbers/solution/filter_fibonacci_numbers.app.src ================================================ {application, filter_fibonacci_numbers, [{description, "filter_fibonacci_numbers"}, {vsn, "0.1.0"}, {registered, []}, {mod, { filter_fibonacci_numbers_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/filter_fibonacci_numbers/solution/filter_fibonacci_numbers.erl ================================================ -module(filter_fibonacci_numbers). -export([filter/1]). fibonacci(1) -> 1; fibonacci(2) -> 2; fibonacci(N) when N > 2 -> fibonacci(N-1) + fibonacci(N-2). is_fibonacci_number(X) -> is_fibonacci_number(X, []). is_fibonacci_number(X, []) -> F1 = fibonacci(1), case X =:= F1 of true -> true; false -> is_fibonacci_number(X, [fibonacci(1)]) end; is_fibonacci_number(X, Fibs) -> Fn = fibonacci(length(Fibs)), case X =:= Fn of true -> true; false when X > Fn -> is_fibonacci_number(X, [Fn|Fibs]); _ -> false end. filter(L) -> [X || X <- L, is_fibonacci_number(X)]. ================================================ FILE: sequential/filter_fibonacci_numbers/src/filter_fibonacci_numbers.app.src ================================================ {application, filter_fibonacci_numbers, [{description, "filter_fibonacci_numbers"}, {vsn, "0.1.0"}, {registered, []}, {mod, { filter_fibonacci_numbers_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/filter_fibonacci_numbers/src/filter_fibonacci_numbers.erl ================================================ -module(filter_fibonacci_numbers). -export([filter/1]). filter(List) -> put_your_solution_here. ================================================ FILE: sequential/filter_fibonacci_numbers/test/filter_fibonacci_numbers_SUITE.erl ================================================ -module(filter_fibonacci_numbers_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(filter_fibonacci_numbers_test). ================================================ FILE: sequential/filter_fibonacci_numbers/test/filter_fibonacci_numbers_test.erl ================================================ -module(filter_fibonacci_numbers_test). -include_lib("eunit/include/eunit.hrl"). filter_fibonacci_numbers_test() -> List = [1, 2, 3, 4, 5, 7, 8, 9, 10], ?assertEqual([1, 2, 3, 5, 8], filter_fibonacci_numbers:filter(List)). ================================================ FILE: sequential/filter_numbers/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/filter_numbers/README.md ================================================ # Filter Numbers ## Reading material - [Learn You Some Erlang: Starting Out (for real)](http://learnyousomeerlang.com/starting-out-for-real) - How to make a list of numbers: [lists:seq/2](http://erlang.org/doc/man/lists.html#seq-2) ## Exercises ### Filter in Create a function `filter_numbers:filter_in/3` that takes three integers (`From`, `To`, and `N`) and returns a list of all the numbers between `From` and `To` that are multiples of `N`. You **must** use list comprehension. ```erlang 1> filter_numbers:filter_in(1,100, 7). [7,14,21,28,35,42,49,56,63,70,77,84,91,98] ``` ### Filter out Create a function `filter_numbers:filter_out/3` that takes three integers (`From`, `To`, and `N`) and returns a list of all the numbers between `From` and `To` that are **not** multiples of `N`. You **must** use list comprehension. ```erlang 1> filter_numbers:filter_out(1,10, 7). [1,2,3,4,5,6,8,9,10] ``` ### Filter in values Create a function `filter_numbers:filter_in_values/3` that takes two integers and a tuple of two integers(`From`, `To`, and `{Min, Max}`) and returns a list of all the numbers between `From` and `To` that are between the values of `Min` and `Max`. You **must** use list comprehension. ```erlang 1> filter_numbers:filter_in_values(1,100,{25,38}). [25,26,27,28,29,30,31,32,33,34,35,36,37,38] ``` ## Testing your solution To test your solution go to the root of the erlings folder and do `make sequential/filter_numbers`. This will run the tests for this specific exercise. ## Our solution You can check our [proposed solution](solution/filter_numbers.erl). ================================================ FILE: sequential/filter_numbers/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/filter_numbers/solution/filter_numbers.app.src ================================================ {application, filter_numbers, [{description, "filter_numbers"}, {vsn, "0.1.0"}, {registered, []}, {mod, { filter_numbers_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/filter_numbers/solution/filter_numbers.erl ================================================ -module(filter_numbers). -export([filter_in/3, filter_out/3, filter_in_values/3]). filter_in(From, To, N) -> [X || X <- lists:seq(From, To), X rem N =:= 0]. filter_out(From, To, N) -> [X || X <- lists:seq(From, To), X rem N =/= 0]. filter_in_values(From, To, {Min, Max}) -> [X || X <- lists:seq(From, To), X >= Min, X =< Max]. ================================================ FILE: sequential/filter_numbers/src/filter_numbers.app.src ================================================ {application, filter_numbers, [{description, "filter_numbers"}, {vsn, "0.1.0"}, {registered, []}, {mod, { filter_numbers_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/filter_numbers/src/filter_numbers.erl ================================================ -module(filter_numbers). -export([filter_in/3, filter_out/3, filter_in_values/3]). filter_in(From, To, N) -> put_your_solution_here. filter_out(From, To, N) -> put_your_solution_here. filter_in_values(From, To, {Min, Max}) -> put_your_solution_here. ================================================ FILE: sequential/filter_numbers/test/filter_numbers_SUITE.erl ================================================ -module(filter_numbers_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(filter_numbers_test). ================================================ FILE: sequential/filter_numbers/test/filter_numbers_test.erl ================================================ -module(filter_numbers_test). -include_lib("eunit/include/eunit.hrl"). filter_numbers_in_test() -> List = [7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98], ?assertEqual(List, filter_numbers:filter_in(1, 100, 7)). filter_numbers_out_test() -> List = [1, 2, 3, 4, 5, 6, 8, 9, 10], ?assertEqual(List, filter_numbers:filter_out(1, 10, 7)). filter_numbers_in_values_test() -> List = [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38], ?assertEqual(List, filter_numbers:filter_in_values(1, 100, {25, 38})). ================================================ FILE: sequential/hello/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: sequential/hello/README.md ================================================ # Hello world ## Reading Material - [Learn You Some Erlang: Modules](http://learnyousomeerlang.com/modules) ## Exercise This is the most iconic exercise in coding history, we should include this one. Your only task is to create a function `hello:hello/0` that returns the binary ``<<"hello world">>`` (don't worry about understanding binaries, yet). To test that is working correctly check it running `make`. As a hint, the file you should be editing is `src/hello.erl`. But in any case if the things get difficult you can check our [proposal solution](solution/hello.erl). ================================================ FILE: sequential/hello/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/hello/solution/hello.app.src ================================================ {application, hello, [{description, "hello"}, {vsn, "0.1.0"}, {registered, []}, {mod, { hello_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/hello/solution/hello.erl ================================================ -module(hello). -export([hello/0]). hello() -> <<"hello world">>. ================================================ FILE: sequential/hello/src/hello.app.src ================================================ {application, hello, [{description, "hello"}, {vsn, "0.1.0"}, {registered, []}, {mod, { hello_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/hello/src/hello.erl ================================================ -module(write_your_module_here). -export([function_you_want_to_export/0]). hello() -> return_your_binary. ================================================ FILE: sequential/hello/test/hello_SUITE.erl ================================================ -module(hello_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(hello_test). ================================================ FILE: sequential/hello/test/hello_test.erl ================================================ -module(hello_test). -include_lib("eunit/include/eunit.hrl"). hello_world_test() -> <<"hello world">> = hello:hello(). ================================================ FILE: sequential/hello_pattern/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: sequential/hello_pattern/README.md ================================================ # Hello Pattern ## Reading Material - [Learn You Some Erlang: Syntax in functions](http://learnyousomeerlang.com/syntax-in-functions) ## Exercise In this exercise we will introduce you to pattern matching and guards. Write a function `hello_pattern:hello/1` which takes a tuple: - `{morning, Name}`, ignores the name and returns `morning`. - `{evening, Name}`, returns a tuple `{good, evening, Name}`. - `{night, Name}`, ignores the name and return `night`. - `{math_class, Number, Name}`. If the number is lower than zero, return `none`, in any other case return `{math_class, Name}`. Resolve this exercise without using `if`, `case`. You should use pattern matching and guard only. Check if your solution is working running `make`. And if your find your self in trouble you can always check our [suggested solution](solution/hello_pattern.erl). ================================================ FILE: sequential/hello_pattern/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/hello_pattern/solution/hello_pattern.app.src ================================================ {application, hello_pattern, [{description, "hello_pattern"}, {vsn, "0.1.0"}, {registered, []}, {mod, { hello_pattern_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/hello_pattern/solution/hello_pattern.erl ================================================ -module(hello_pattern). -export([hello/1]). hello({morning, _Name}) -> morning; hello({evening, Name}) -> {good, evening, Name}; hello({night, _Name}) -> night; hello({math_class, Number, _Name}) when Number < 0 -> none; hello({math_class, _Number, Name}) -> {math_class, Name}. ================================================ FILE: sequential/hello_pattern/src/hello_pattern.app.src ================================================ {application, hello_pattern, [{description, "hello_pattern"}, {vsn, "0.1.0"}, {registered, []}, {mod, { hello_pattern_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/hello_pattern/src/hello_pattern.erl ================================================ -module(hello_pattern). -export([hello/1]). hello({tuple_element_1, tuple_element_2}) -> resolve. ================================================ FILE: sequential/hello_pattern/test/hello_pattern_SUITE.erl ================================================ -module(hello_pattern_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(hello_pattern_test). ================================================ FILE: sequential/hello_pattern/test/hello_pattern_test.erl ================================================ -module(hello_pattern_test). -include_lib("eunit/include/eunit.hrl"). hello_day_test() -> morning = hello_pattern:hello({morning, "Jimmy"}), {good, evening, "Rick"} = hello_pattern:hello({evening, "Rick"}), night = hello_pattern:hello({night, "Morty"}). hello_math_class_test() -> none = hello_pattern:hello({math_class, -100, "K-Colored"}), {math_class, "Analysis II"} = hello_pattern:hello({math_class, 10, "Analysis II"}). ================================================ FILE: sequential/insert_element_at/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: sequential/insert_element_at/README.md ================================================ # Insert element at position ## Reading Material - [Learn You Some Erlang: A Short Visit to Common Data Structures](http://learnyousomeerlang.com/a-short-visit-to-common-data-structures) - [Learn You Some Erlang: Maps](http://learnyousomeerlang.com/maps#what-maps-shall-be) ## Exercise Since we already know recursion we can play a little bit more, now consider we have a list of element, and we want to replace that element with another one, but at the same time remember the old one. For example if we have `[1,2,3]` and we replace the second element with `"hi"`, it should be `[1, #{current => "hi", old => 2}]`. And if we call that function with `[1, #{current => 10, old => 0}, 3]` replacing the second element with "hi" the result should be `[1, #{current => "hi", old => 10}, 3]`. The function should be `insert_element_at:insert/3`. A sample call of the previous example could be `insert([1,2,3], 2, "hi")`. If you are stuck or want to compare check [our solution](solution/insert_element_at.erl). ================================================ FILE: sequential/insert_element_at/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/insert_element_at/solution/insert_element_at.app.src ================================================ {application, insert_element_at, [{description, "insert_element_at"}, {vsn, "0.1.0"}, {registered, []}, {mod, { insert_element_at_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/insert_element_at/solution/insert_element_at.erl ================================================ -module(insert_element_at). -export([insert/3]). insert([H|T], Pos, Element) -> insert(Pos, Element, H, [], T). insert(1, NewElement, CurrentElement, Pre, Post) -> Pre ++ [replace(CurrentElement, NewElement)] ++ Post; insert(Pos, NewElement, CurrentElement, Pre, [PostH|PostT]) -> insert(Pos - 1, NewElement, PostH, Pre ++ [CurrentElement], PostT). replace(#{current := Old, old := _}, NewElement) -> #{current => NewElement, old => Old}; replace(Element, NewElement) -> #{current => NewElement, old => Element}. ================================================ FILE: sequential/insert_element_at/src/insert_element_at.app.src ================================================ {application, insert_element_at, [{description, "insert_element_at"}, {vsn, "0.1.0"}, {registered, []}, {mod, { insert_element_at_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/insert_element_at/src/insert_element_at.erl ================================================ -module(insert_element_at). -export([insert/3]). insert(_List, _Pos, _Element) -> believe_in_yourself. ================================================ FILE: sequential/insert_element_at/test/insert_element_at_SUITE.erl ================================================ -module(insert_element_at_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(insert_element_at_test). ================================================ FILE: sequential/insert_element_at/test/insert_element_at_test.erl ================================================ -module(insert_element_at_test). -include_lib("eunit/include/eunit.hrl"). insertion_test() -> [1, #{current := "hi", old := 2}, 3] = insert_element_at:insert([1, 2, 3], 2, "hi"), [1, #{current := "hi", old := 10}, 3] = insert_element_at:insert([1, #{current => 10, old => 2}, 3], 2, "hi"). ================================================ FILE: sequential/installing/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump ================================================ FILE: sequential/installing/Makefile ================================================ .PHONY: test test: rebar3 ct ================================================ FILE: sequential/installing/README.md ================================================ # Installing ## Reading Material - [Learn You Some Erlang: Introduction](http://learnyousomeerlang.com/introduction) ## Requirements In this exercise we will setup our environment before we start our real coding. We will need some software: - Erlang 27. - Make (probably already installed on your system). - Rebar. We suggest 2 ways to install Erlang + Rebar: asdf or nix. ### ASDF First, we need to install ASDF: ```sh git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1 ``` Then we'll have to add this to your bash or zsh config file: ```sh . "$HOME/.asdf/asdf.sh" ``` Or, if you're using fish: ```fish source ~/.asdf/asdf.fish ``` Be sure to check asdf's website, since there are some extra goodies to config like shell completions. ```sh asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git ``` And finally, install erlang 27.0.1: ```sh asdf install erlang 27.0.1 && asdf global erlang 27.0.1 ``` The global command is just to tell your environment to always use erlang 27, you can use `asdf local` instead to have distinct versions base on which folder you're currently in. ### Nix Clone the repo: ```sh git clone https://github.com/lambdaclass/erlings.git ``` cd into this folder and eval the nix flake: ```sh cd erlings/sequential/install && nix develop ``` This will drop you into a bare-bones bash shell with erlang and rebar installed, which you can use for the exercises. ## Checking environment To check your environment do the following: ~~~ $> git clone https://github.com/lambdaclass/erlings.git $> cd ~/erlings/sequential/installing $> make ~~~ You should get the following output: ~~~ rebar3 ct ===> Verifying dependencies... ===> Analyzing applications... ===> Compiling installing ===> Running Common Test suites... %%% installing_SUITE: . All 1 tests passed. ~~~ ================================================ FILE: sequential/installing/flake.nix ================================================ { description = "A flake to download Erlang 27 and Rebar3 from Nixpkgs 24.05"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/24.05"; flake-utils.url = "github:numtide/flake-utils/main"; }; outputs = { self, nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in { packages.default = pkgs.mkShell { buildInputs = [ pkgs.erlang_27 pkgs.rebar3 ]; }; }); } ================================================ FILE: sequential/installing/src/installing.app.src ================================================ {application, installing, [{description, "installing"}, {vsn, "0.1.0"}, {registered, []}, {mod, { installing_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/installing/src/installing.erl ================================================ -module(installing). -export([installed/0]). installed() -> 'i can not wait for more code'. ================================================ FILE: sequential/installing/test/installing_SUITE.erl ================================================ -module(installing_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(installing_test). ================================================ FILE: sequential/installing/test/installing_test.erl ================================================ -module(installing_test). -include_lib("eunit/include/eunit.hrl"). installing_test() -> ?assertEqual('i can not wait for more code', installing:installed()). ================================================ FILE: sequential/lists/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/lists/README.md ================================================ # Lists ## Reading Material Recursion and high order functions can be tricky if you don't have a functional programming background, for that reason take your time to read and really understand the concepts. - [Recursion theory](https://en.wikipedia.org/wiki/Recursion_(computer_science)) - [Learn You Some Erlang: Recursion](http://learnyousomeerlang.com/recursion) - [Learn You Some Erlang: Higher Order Functions](http://learnyousomeerlang.com/higher-order-functions) ## Exercises ### Reverse This exercise consists in creating the function `lists_exercises:reverse/1` which should be [Tail Recursive](https://stackoverflow.com/questions/33923/what-is-tail-recursion), and take a list as argument and return another list with every element in the opposed position. Example: ``` erlang 1> lists_exercises:reverse([1, 2, 3, 4]). %% [4, 3, 2, 1] ``` [solution](solution/lists_exercises.erl#L14-L17) ### Remove consecutive Create a function `lists_exercises:rmconsecutive/1` that takes a list and returns another one but without any consecutive repetitions. Example: ``` erlang 1> lists_exercises:rmconsecutive([1,1,1,2,3,4,1,1,1,3]). %% [1, 2, 3, 4, 1, 3] ``` [solution](solution/lists_exercises.erl#L21-L31) ### Even Fibonacci Numbers Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be: ` 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...` By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. [solution](solution/lists_exercises.erl#L35-L60) ### Reduce Implement `lists_exercises:foldl/3` and `lists_exercises:foldl/2` using recursion (see [Erlang foldl reference](http://erlang.org/doc/man/lists.html#foldl-3)). Example: ``` erlang 1> lists_exercises:foldl(fun(X, Y) -> X + Y end, 0, [1, 2, 3, 4]). %% 10 2> lists_exercises:foldl(fun(X, Y) -> X * Y end, [1, 2, 3]). %% 6 3> lists_exercises:foldl(func(X, Y) -> X andalso Y, [true, false, true]). %% false ``` [solution](solution/lists_exercises.erl#L65-L73) ### Rotate Lists Create a function `lists_exercises:rotate/2` that rotates the contents of a list `n` positions. It should take 2 arguments: - A list - A tuple of `{left, N}` or `{right, N}` that indicates the direction and the size of the displacement expected. Example: ``` erlang 1> lists_exercises:rotate([1, 2, 3, 4, 5], {right, 2}). %% [4,5,1,2,3] ``` [solution](solution/lists_exercises.erl#L77-L86) ### Run-length encoding of a list Implement the so-called run-length encoding data compression method. Consecutive duplicates of elements are encoded as terms {N,E} where N is the number of duplicates of the element E. Example: ``` erlang 1> lists_exercises:run_length_encode([a,a,a,a,b,c,c,a,a,d,e,e,e,e]). %%[{4,a},{1,b},{2,c},{2,a},{1,d},{4,e}] ``` [solution](solution/lists_exercises.erl#L90-L103) ### Any Write a function `lists_exercises:list_any/2` that takes a predicate (a function that returns a boolean value) and a list and returns true if any element of the list satisfies the predicate and false otherwise. Note: Implement it without using `lists:list_any`. Example: ``` erlang 1> lists_exercises:list_any(fun(X) -> X > 3 end, [4, 2, 0]). %% true 2> lists_exercises:list_any(fun(X) -> X == 0 end, [1, 2, 3]). %% false ``` [solution](solution/lists_exercises.erl#L107-L108) ### Anagram Write a function `list_exercises:anagram/2` that takes a list of strings, a string ("MyWord"), and returns all elements of the list that are an anagram of "MyWord" Example: ```erlang 1>lists_exercises:anagram(["god","cat","dog"], "dog"). %%["god"] ``` [solution](solution/lists:exercises.erl#L111-L131) ### First letter last letter game There is a game called first letter, last letter. The object of this game is for one player to say a word apple, and for the other player to say a word that begins with the last letter of the previous word, i.e. elephant. Write a function `lists:exercises:last_letter/1` that takes a list of strings and return a list where the subsequent name starts with the final letter of the previous name. Take the first element of the list as the first word of the sequence, that names cannot be repeated. Example: ```erlang 1> lists_exercises:last_letter(["turnIp","Potato","peas","OniOn","yam","letuCe","broccoli", "asparaguS","Artichoke"]). %%["turnIp","Potato","OniOn"] 2> lists_exercises:last_letter([]) %% [] ``` [solution](solution/lists:exercises.erl#L117-L141) ================================================ FILE: sequential/lists/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/lists/solution/lists_exercises.app.src ================================================ {application, lists_exercises, [{description, "lists_exercises"}, {vsn, "0.1.0"}, {registered, []}, {mod, { lists_exercises_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/lists/solution/lists_exercises.erl ================================================ -module(lists_exercises). -export([reverse/1, rmconsecutive/1, even_fib_numbers/0, foldl/2, foldl/3, rotate/2, run_length_encode/1, list_any/2, anagram/2, last_letter/1]). % Reverse reverse(List) -> reverse(List, []). reverse([], Acc) -> Acc; reverse([H|T], Acc) -> reverse(T, [H|Acc]). % Remove Consecutive rmconsecutive([]) -> []; rmconsecutive([H|T]) -> rmconsecutive(H, T). rmconsecutive(E, []) -> [E]; rmconsecutive(E, [E|T]) -> rmconsecutive(E,T); rmconsecutive(E, [H|T]) -> [E| rmconsecutive(H,T)]. % Even Fibonacci Numbers fib(1) -> 1; fib(2) -> 2; fib(N) when N > 2 -> fib(N-1) + fib(N-2). % list of every fibonacci number less than N. fibs_less_than(N) when N < 1-> []; fibs_less_than(N) -> fibs_less_than(N, 1, []). fibs_less_than(N, M, AccList) -> FibM = fib(M), case FibM < N of true -> fibs_less_than(N, M + 1, [ FibM | AccList]); false -> AccList end. even_fib_numbers() -> Less_than_4mill = fibs_less_than(4000000), Even_fibs = lists:filter(fun(X) -> X rem 2 == 0 end, Less_than_4mill), lists:sum(Even_fibs). % Reduce % with accumulator foldl(_, Acc, []) -> Acc; foldl(Fun, Acc, [A | T]) -> NewAcc = Fun(A,Acc), foldl(Fun,NewAcc,T). % w/o accumulator foldl(_, List) when length(List) < 2 -> undefined; foldl(Fun, [A, B | T]) -> foldl(Fun, A, [B | T]). % Rotate Lists rotate([], _) -> []; rotate(L, {Dir, N}) -> case Dir of left -> {Right, Left} = lists:split(N, L); right -> {Right, Left} = lists:split(length(L) - N, L) end, lists:append(Left, Right). % Run-length encoding of a list run_length_encode([], Acc) -> reverse(Acc); run_length_encode([H|T], [{Count, H}|AccT]) -> run_length_encode(T, [{Count + 1, H}|AccT]); run_length_encode([H|T], Acc) -> run_length_encode(T, [{1, H}] ++ Acc). run_length_encode(L) -> run_length_encode(L, []). % Any list_any(F, List) -> lists:foldl(fun(X, Y) -> F(X) or Y end, false, List). %Anagram anagram(List, S) -> LowerS = string:lowercase(S), SortedS = lists:sort(LowerS), lists:filter(fun (X) -> LowerH = string:lowercase(X), LowerH =/= LowerS andalso lists:sort(LowerH) =:= SortedS end, List). %Last Letter game last_letter([]) -> []; last_letter([H|T]) -> last_letter(T, [H]). last_letter([], Acc) -> lists:reverse(Acc); last_letter(List, [H|T]) -> Letter = lists:nthtail(length(H)-1,H), Word = find_word(Letter, List), case Word of [] -> last_letter([], [H|T]); _ -> NewWord = [Word, H|T], NewList = lists:delete(Word, List), last_letter(NewList, NewWord) end. find_word(_L,[]) -> []; find_word(L,[H|T]) -> Letter = string:lowercase(L), [A|_Rest] = string:lowercase(H), case [A] == Letter of true -> H; false -> find_word(L,T) end. ================================================ FILE: sequential/lists/src/lists_exercises.app.src ================================================ {application, lists_exercises, [{description, "lists_exercises"}, {vsn, "0.1.0"}, {registered, []}, {mod, { lists_exercises_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/lists/src/lists_exercises.erl ================================================ -module(lists_exercises). -export([reverse/1, rmconsecutive/1, even_fib_numbers/0, foldl/3, foldl/2, rotate/2, run_length_encode/1, list_any/1, anagram/2, last_letter/1]). reverse(List) -> put_your_solution_here. rmconsecutive(List) -> put_your_solution_here. even_fib_numbers() -> put_your_solution_here. foldl(Fun, Acc, List) -> put_your_solution_here. foldl(Fun, List) -> put_your_solution_here. rotate(List, Tuple) -> put_your_solution_here. run_length_encode(List) -> put_your_solution_here. list_any(N) -> put_your_solution_here. anagram(List, S) -> put_your_solution_here. last_letter(List) -> put_your_solution_here. ================================================ FILE: sequential/lists/test/lists_exercises_SUITE.erl ================================================ -module(lists_exercises_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(lists_exercises_test). ================================================ FILE: sequential/lists/test/lists_exercises_test.erl ================================================ -module(lists_exercises_test). -include_lib("eunit/include/eunit.hrl"). all_the_same_test() -> ?assertEqual([1, 1, 1, 1], lists_exercises:reverse([1, 1, 1, 1])). all_different_test() -> ?assertEqual([5, 4, 3, 2, 1], lists_exercises:reverse([1, 2, 3, 4, 5])). rmconsecutive_test() -> List = [1, 1, 1, 2, 3, 4, 1, 1, 1, 3], ?assertEqual([1,2,3,4,1,3], lists_exercises:rmconsecutive(List)). even_fib_numbers_test() -> ?assertEqual(4613732, lists_exercises:even_fib_numbers()). foldl_multiply_test() -> Multiply = fun (A, B) -> A * B end, ?assertEqual(300, lists_exercises:foldl(Multiply, 10, [2, 5, 3])). foldl_add_without_acc_test() -> Add = fun (A, B) -> A + B end, ?assertEqual(100, lists_exercises:foldl(Add, [20, 20, 10, 50])). rotate_list_right_test() -> Res = [4, 5, 1, 2, 3], ?assertEqual(Res, lists_exercises:rotate([1, 2, 3, 4, 5], {right, 2})). rotate_list_left_test() -> Res = [3, 4, 5, 1, 2], ?assertEqual(Res, lists_exercises:rotate([1, 2, 3, 4, 5], {left, 2})). run_length_encoding_test() -> List = [a, a, a, a, b, c, c, a, a, d, e, e, e, e], Res = [{4, a}, {1, b}, {2, c}, {2, a}, {1, d}, {4, e}], ?assertEqual(Res, lists_exercises:run_length_encode(List)). any_is_even_test() -> List = [2, 3, 4, 1, 5, 6, 9], ?assert(lists_exercises:list_any(fun(X) -> X rem 2 == 0 end, List)). any_empty_test() -> ?assertNot(lists_exercises:list_any(fun(X) -> X == 20 end, [])). anagram_test() -> List = ["Panel", "plane", "Penal", "PlenA", "Nepal", "ArgentinA", "Laos"], String = "Nepal", Res = ["Panel", "plane", "Penal", "PlenA"], ?assertEqual(Res, lists_exercises:anagram(List,String)). last_letter_test()-> List = ["Afghanistan", "Albania", "Algeria", "Andorra", "Nigeria", "Norway", "Yemen", "Nepal", "Morocco", "Oman", "Portugal", "Spain"], Res = ["Afghanistan", "Nigeria", "Albania", "Algeria", "Andorra"], ?assertEqual(Res, lists_exercises:last_letter(List)). ================================================ FILE: sequential/maps/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump .lock ================================================ FILE: sequential/maps/README.md ================================================ # Maps ## Reading Material - [Learn You Some Erlang: Maps](http://learnyousomeerlang.com/maps) ## Exercises ### Sum of Values Write a function `maps_exercises:sum_of_values/1` that takes a map a returns a sum of all values in the map. Note: the function maps:fold/3 can be useful. Example: ``` erlang 1> maps_exercises:sum_of_values(#{a => 1, b =>3, c =>4}). %%8 2> maps_exercises:sum_of_values(#{one => 1, three=> 3, seven => 7}). %% 11 ``` ### Min Value Write a function `maps_exercises:min_value(Map)` that returns the minimum value of that Map. Example: ```erlang 1>maps_exercises:min_value(#{a => 1, b =>3, c =>4}). %%1 2>maps_exercises:min_value(#{five => 5, three => 3, seven => 7, ten =>10}). %%3 ``` ### Sort keys Write a function `maps_exercises:sort_by_keys/1` that takes a map and returns a new map with the keys ordered from min to max. Note: Remember you can use maps:keys/1 that returns a list with all the keys in a map. Example: ``` erlang 1>maps_exercises:sort_by_keys(#{1=> one, 2 => two, 5 => five, 10 => ten, 3 => three, 15 => fifteen}). %% #{1 => one,2 => two,3 => three,5 => five,10 => ten, 15 => fifteen} 2> maps:exercises:sort_by_keys(#{"one" => 1,"two" => 2, "three" => 3, "five" => 5, "four"=>4}). #{"five" => 5,"four" => 4,"one" => 1,"three" => 3,"two" => 2} ``` ### Return values Write a function `maps_exercises:return_values/1` that takes a map a returns a list with all the values in that map Example: ``` erlang 1> maps_exercises:return_values(#{"one" => 1,"two" => 2, "three" =>3 , "five" => 5, "four" => 4}). %% [1,2,3,5,4] 2> maps_exercises:return_values(#{1 => one, 2 => two, 5 => five, 10 => ten, 3 => three, 15 => fifteen}) %%[one,two,three,five,ten,fifteen] ``` ### Merge Map Write a function `maps_exercises:merge/2` that merges 2 maps, if they have a key in common, keep the value from the second map. Note: the function `maps:fold/3` can be useful. Example: ``` erlang 1> maps_exercises:merge(#{}, #{a => 1, b => 2}). %% #{a => 1, b => 2} 2> maps_exercises:merge(#{a => 1, b => 2}, #{a => 5, c => 3}). %% #{a => 5, b => 2, c => 3} ``` [solution](src/solution/maps_exercises.erl#L6-L7). ### Mapping a Map Write a function `maps_exercises:map/2` for mapping a function over the values of a Map without using `maps:map/2`. Example: ``` erlang 1> maps_exercises:map(fun(X) -> X + 1 end, #{a => 4, b => 2}). %% #{a => 5, b => 3} 2> maps_exercises:map(#{}). %% #{} ``` [solution](src/solution/maps_exercises.erl#L10-L13). ### List to Map Create a function `maps_exercises:to_map/1` that converts a list to a [Map](http://learnyousomeerlang.com/maps) without using `maps:from_list`. Example: ``` erlang 1> maps_exercises:to_map([2, 1, 6, 4]). %% #{1 => 2, 2 => 1, 3 => 6, 4 => 4} 2> maps_exercises:to_map([]). %% #{} ``` [solution](src/solution/maps_exercises.erl#L16-L24). ### Records to Maps Create a record named `person` that has the attributes `name` and `age`. Then write a function `maps_exercises:records_to_maps(Records)` that converts a list of records (in this case people) into a list of maps with the attributes. For this you should use `lists:map`. Example: ```erlang 1> maps_exercises:records_to_maps([]). %% [] 2> maps_exercises:records_to_maps([#person{name="Pepe", age=28}, #person{name="Luis", age=77}]). %% [#{age => 28,name => "Pepe"},#{age => 77,name => "Luis"}] ``` [solution](src/solution/maps_exercises.erl#L27-L30). ### Maps to Records Create a record named `person` that has the attributes `name` and `age`. Then write a function `maps_exercises:maps_to_records(Records)` that converts a list of maps into a list of records (in this case people). For this you should only use recursion. Example: ```erlang 1> maps_exercises:maps_to_records([]). %% [] 2> maps_exercises:maps_to_records([#{age => 28,name => "Pepe"},#{age => 77,name => "Luis"}]). %% [#person{name = "Luis",age = 77}, #person{name = "Pepe",age = 28}] ``` [solution](src/solution/maps_exercises.erl#L33-L38). ### Proplist to Map **DISCLAIMER** Erlang provides functions for the following task: `maps:from_list` and `maps:to_list`, but we want you to implement it by hand. Write a recursive function `proplist_to_map/1` that takes a proplist (a list of tuples) and builds a map from it. Use the first component of each tuple as the key and the second component as the value. Example: ```erlang 1> maps_exercises:proplist_to_map([]). %% #{} 2> maps_exercises:proplist_to_map([{firstname, "Pedro"}, {lastname, "Sanches"}, {age, 11}]). %% #{age => 11,firstname => "Pedro",lastname => "Sanches"} ``` [solution](src/solution/maps_exercises.erl#L41-L44). ================================================ FILE: sequential/maps/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/maps/solution/maps_exercises.app.src ================================================ {application, maps_exercises, [{description, "maps_exercises"}, {vsn, "0.1.0"}, {registered, []}, {mod, { maps_exercises_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/maps/solution/maps_exercises.erl ================================================ -module(maps_exercises). -export([sum_of_values/1,return_values/1,sort_by_keys/1,min_value/1,merge/2, map/2, to_map/1, records_to_maps/1, maps_to_records/1, proplist_to_map/1]). -record(person, {name, age}). %% Return the sum of all values in a list sum_of_values(Map) -> maps:fold(fun(_K,V,AccIn) -> AccIn + V end, 0, Map). %% Min value min_value(Map) -> Keys = maps:keys(Map), min_value(Keys, Map, []). min_value([], _Map, Acc)-> lists:min(Acc); min_value([H|T], Map, Acc) -> Element = maps:find(H, Map), {_Atom, Value} = Element, min_value(T, Map, [Value|Acc]). %% Order Map sort_by_keys(Map)-> List = maps:keys(Map), OrderedL = lists:sort(List), build_ordered_map(OrderedL, Map, []). build_ordered_map([], _Map, Acc)-> NewMap = lists:reverse(Acc), maps:from_list(NewMap); build_ordered_map([H|T], Map, Acc) -> Value = maps:get(H, Map), Tuple = {H, Value}, build_ordered_map(T, Map, [Tuple|Acc]). %% Values to list return_values(Map) -> List = maps:keys(Map), get_values(List, Map, []). get_values([], _Map, Acc) -> lists:reverse(Acc); get_values([H|T], Map, Acc)-> Value = maps:get(H, Map), get_values(T, Map, [Value|Acc]). %% Merge map merge(Map1, Map2) -> maps:fold(fun maps:put/3, Map1, Map2). %% Mapping a map map(F, Map) -> List_Of_Tuples = maps:to_list(Map), Mapped_List = lists:keymap(F, 2, List_Of_Tuples), maps:from_list(Mapped_List). %% List to map to_map([]) -> #{}; to_map([H|T]) -> to_map(2, #{1 => H}, T). to_map(_, Map, []) -> Map; to_map(N, Map, [H|T]) -> to_map(N+1, maps:put(N, H, Map), T). %% Records to maps records_to_maps(Records) -> lists:map(fun(#person{name = Name, age = Age}) -> #{name => Name, age => Age} end, Records). %% Maps to records maps_to_records(Maps) -> maps_to_records([], Maps). maps_to_records(Acc, []) -> Acc; maps_to_records(Acc, [#{name := Name, age := Age} | Tail]) -> maps_to_records([#person{name = Name, age = Age} | Acc], Tail). %% Proplist to map proplist_to_map(Proplist) -> lists:foldl(fun({K, V}, Map) -> Map#{K => V} end, #{}, Proplist). ================================================ FILE: sequential/maps/src/maps_exercises.app.src ================================================ {application, maps_exercises, [{description, "maps_exercises"}, {vsn, "0.1.0"}, {registered, []}, {mod, { maps_exercises_app, []}}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/maps/src/maps_exercises.erl ================================================ -module(maps_exercises). -export([sum_of_values/1,return_values/1,sort_by_keys/1,min_value/1,merge/2, map/2, to_map/1, records_to_maps/1, maps_to_records/1, proplist_to_map/1]). sum_of_values(Map) -> put_your_solution_here. min_value(Map)-> put_your_solution_here. sort_by_keys(Map)-> put_your_solution_here. return_values(Map)-> put_your_solution_here. merge(M1, M2) -> put_your_solution_here. map(Function, Map) -> put_your_solution_here. to_map(List) -> put_your_solution_here. records_to_maps(Records) -> put_your_solution_here. maps_to_records(Maps) -> put_your_solution_here. proplist_to_map(Proplist) -> put_your_solution_here. ================================================ FILE: sequential/maps/test/maps_exercises_SUITE.erl ================================================ -module(maps_exercises_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(maps_exercises_test). ================================================ FILE: sequential/maps/test/maps_exercises_test.erl ================================================ -module(maps_exercises_test). -record(person, {name, age}). -include_lib("eunit/include/eunit.hrl"). sum_of_values_test()-> Map = #{a=>1, b=> 5, c => 6, d => 5}, ?assertEqual(17, maps_exercises:sum_of_values(Map)). min_value_test()-> Map = #{a=> 5, b=> 3, c=> 4, d=>2}, ?assertEqual(2, maps_exercises:min_value(Map)). sort_by_keys_test()-> Map = #{1 => a, 5=> c, 3=> d, 7=> f}, OrderedM = #{1=>a,3=>d,5=>c,7=>f}, ?assertEqual(OrderedM, maps_exercises:sort_by_keys(Map)). return_values_test()-> Map = #{a=>1, b=>5, c=>3}, List = [1,5,3], ?assertEqual(List, maps_exercises:return_values(Map)). merge_empty_test() -> Map = #{a => 1, b => 2, c => 3}, ?assertEqual(Map, maps_exercises:merge(#{}, Map)), ?assertEqual(Map, maps_exercises:merge(Map, #{})). merge_update_test() -> Map1 = #{a => 1, b => 2, c => 3}, Map2 = #{b => 6, d => 4}, Map3 = #{a => 1, b => 6, c => 3, d => 4}, ?assertEqual(Map3, maps_exercises:merge(Map1, Map2)). map_empty_map_test() -> F = fun(X) -> X + 1 end, ?assertEqual(#{}, maps_exercises:map(F, #{})). map_add_1_test() -> F = fun(X) -> X + 1 end, Map = #{a => 1, b => 2, c => 3}, Res = #{a => 2, b => 3, c => 4}, ?assertEqual(Res, maps_exercises:map(F, Map)). to_map_test() -> List = [2, 23, a], Map = #{1 => 2, 2 => 23, 3 => a}, ?assertEqual(Map, maps_exercises:to_map(List)). to_map_empty_test() -> ?assertEqual(#{}, maps_exercises:to_map([])). records_to_maps_test() -> Records = [#person{name="Pepe", age=28}, #person{name="Luis", age=77}], Maps = [#{age => 28,name => "Pepe"}, #{age => 77,name => "Luis"}], ?assertEqual(Maps, maps_exercises:records_to_maps(Records)). records_to_maps_empty_test() -> ?assertEqual([], maps_exercises:records_to_maps([])). maps_to_records_test() -> Records = [#person{name="Pepe", age=28}, #person{name="Luis", age=77}], Maps = [#{age => 77,name => "Luis"}, #{age => 28,name => "Pepe"}], ?assertEqual(Records, maps_exercises:maps_to_records(Maps)). maps_to_records_empty_test() -> ?assertEqual([], maps_exercises:maps_to_records([])). proplist_to_map_test() -> Proplist = [{firstname, "Pedro"}, {lastname, "Sanches"}, {age, 11}], Map = #{age => 11,firstname => "Pedro",lastname => "Sanches"}, ?assertEqual(Map, maps_exercises:proplist_to_map(Proplist)). proplist_to_map_empty_test() -> ?assertEqual(#{}, maps_exercises:proplist_to_map([])). ================================================ FILE: sequential/regex/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build .idea *.iml rebar3.crashdump *~ ================================================ FILE: sequential/regex/README.md ================================================ regex ===== Create a function `match/2` that will receive a string and a regex (also a string), and return `true` if it matches or `false` otherwise. You won't be using all of Regex, just a small subset of it as follows: | Syntax | Meaning | Example | Matches | | ------------- |:-------------------------------------------:| :------: | :------------: | | a | match the specified character | k | k | | . | matches any character (except newline) | . | a,b,c,d... | | ? | matches 0 or 1 of the previous character | aq? | a, aq | | * | matches 0 or more of the previous character | b* | "", b, bb, bbb | | ^ | matches the start of a string | ^ca | ca | | $ | matches the end of a string | eb$ | eb | ### Considerations * The given regex won't be invalid (Eg: `"^*"`, `"*?"`) * I can ignore the existance of the newline character since I expect simple strings as inputs. Thus the character `.` matches anything * An empty regex matches nothing so `match(SomeString, EmptyRegex)` will return false * `match/2` can return true as soon it matches something since I don't care about where or what. ================================================ FILE: sequential/regex/rebar.config ================================================ {profiles, [ {ci, [{src_dirs, ["solution"]}]} ]}. ================================================ FILE: sequential/regex/solution/regex.app.src ================================================ {application, regex, [{description, "simple regex engine"}, {vsn, "0.1.0"}, {registered, []}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/regex/solution/regex.erl ================================================ -module(regex). -export([match/2]). match([], _Regex) -> false; match(_String, []) -> false; match(String, [$^ | Regex]) -> matching(String, Regex); match(String, Regex) -> case matching(String, Regex) of true -> true; false -> match(tl(String), Regex) end. matching(_String, []) -> true; matching([], [$$]) -> true; matching([], [_Char, $? | Regex]) -> matching([], Regex); matching([], [_Char, $* | Regex]) -> matching([], Regex); matching([], _Regex) -> false; matching(_String, [$$]) -> false; matching(String, [$., $* | RemRegex] = Regex) -> case matching(String, RemRegex) of true -> true; false -> matching(tl(String), Regex) end; matching([Char | String], [Char, $? | Regex]) -> matching(String, Regex); matching(String, [_OtherChar, $? | Regex]) -> matching(String, Regex); matching([Char | String], [Char, $* | _RemRegex] = Regex) -> matching(String, Regex); matching(String, [_OtherChar, $* | Regex]) -> matching(String, Regex); matching([_Char | String], [$. | Regex]) -> matching(String, Regex); matching([Char | String], [Char | Regex]) -> matching(String, Regex); matching([_Char | _String], [_OtherChar | _Regex]) -> false. ================================================ FILE: sequential/regex/src/regex.app.src ================================================ {application, regex, [{description, "simple regex engine"}, {vsn, "0.1.0"}, {registered, []}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {licenses, ["MIT"]}, {links, []} ]}. ================================================ FILE: sequential/regex/src/regex.erl ================================================ -module(regex). -export([match/2]). match(_String, _Regex) -> put_your_solution_here. ================================================ FILE: sequential/regex/test/regex_SUITE.erl ================================================ -module(regex_SUITE). -include_lib("common_test/include/ct.hrl"). -export([all/0]). -export([run_eunit/1]). all() -> [run_eunit]. run_eunit(_Config) -> ok = eunit:test(regex_test). ================================================ FILE: sequential/regex/test/regex_test.erl ================================================ -module(regex_test). -include_lib("eunit/include/eunit.hrl"). matches_characters_test() -> ?assert(regex:match("a", "a")). matches_numbers_test() -> ?assert(regex:match("49", "49")). matches_question_mark_test() -> ?assert(regex:match("Hi", "h?")). matches_multiple_chars_test() -> ?assert(regex:match("aaaaaaaaa", "a*")). matches_empty_string_test() -> ?assert(regex:match(" ", "a*")). matches_any_character_test() -> ?assert(regex:match("asdf as 42", ".")).