Erlang and io:read() - erlang

I have a problem, I don't know why the program doesn't work correctly.
When I run the program and I insert atom c, then the program calls function io:read() forever.
Thank you for your help and I'm sorry for my English.
-module(temperature).
-export([run/0, convert/2]).
run() ->
run(true).
run(true) ->
{ok, Choice} = io:read("Convert to degrees Celsius or convert to degrees Fahrenheit? c/f :"),
{ok, Temp} = io:read("Insert temperature: "),
{UnitTemp, Convert} = convert(Choice, Temp),
io:format("The converted temperature: ~f ~s\n", [Convert, UnitTemp]),
{ok, Continue} = io:read("New temperature? true/false :"),
run(Continue);
run(false) ->
ok.
convert(c, Fahrenheit) -> {'Celsius', 5 * (Fahrenheit - 32) / 9};
convert(f, Celsius) -> {'Fahrenheit', 9 * Celsius / 5 + 32}.

io:read reads a term, so it does not stop, until you finish your term with ..
1> io:read("Enter term: ").
Enter term: {foo, bar}.
{ok,{foo,bar}}
2> io:read("This will give error: ").
This will give error: }foo
This will give error: .
{error,{1,erl_parse,["syntax error before: ","'}'"]}}
So you can simply type c..
Alternatively, you can use io:get_chars/2 if you don't want to type the dot. The first argument is prompt and second one is number of chars to read, so in your case, it will be:
io:get_chars("prompt ", 1).
prompt c
"c"
Remember, that after typing c, you still have to hit enter and now, you should pattern match on string "c" instead of atom c.

Related

How should Erlang filter the elements in the list, and add punctuation and []?

-module(solarSystem).
-export([process_csv/1, is_numeric/1, parseALine/2, parse/1, expandT/1, expandT/2,
parseNames/1]).
parseALine(false, T) ->
T;
parseALine(true, T) ->
T.
parse([Name, Colour, Distance, Angle, AngleVelocity, Radius, "1" | T]) ->
T;%Where T is a list of names of other objects in the solar system
parse([Name, Colour, Distance, Angle, AngleVelocity, Radius | T]) ->
T.
parseNames([H | T]) ->
H.
expandT(T) ->
T.
expandT([], Sep) ->
[];
expandT([H | T], Sep) ->
T.
% https://rosettacode.org/wiki/Determine_if_a_string_is_numeric#Erlang
is_numeric(L) ->
S = trim(L, ""),
Float = (catch erlang:list_to_float(S)),
Int = (catch erlang:list_to_integer(S)),
is_number(Float) orelse is_number(Int).
trim(A) ->
A.
trim([], A) ->
A;
trim([32 | T], A) ->
trim(T, A);
trim([H | T], A) ->
trim(T, A ++ [H]).
process_csv(L) ->
X = parse(L),
expandT(X).
The problem is that it will calls process_csv/1 function in my module in a main, L will be a file like this:
[["name "," col"," dist"," a"," angv"," r "," ..."],["apollo11 ","white"," 0.1"," 0"," 77760"," 0.15"]]
Or like this:
["planets ","earth","venus "]
Or like this:
["a","b"]
I need to display it as follows:
apollo11 =["white", 0.1, 0, 77760, 0.15,[]];
Planets =[earth,venus]
a,b
[[59],[97],[44],[98]]
My problem is that no matter how I make changes, it can only show a part, and there are no symbols. The list cannot be divided, so I can't find a way.
In addition, because Erlang is a niche programming language, I can't even find examples online.
So, can anyone help me? Thank you, very much.
In addition, I am restricted from using recursion.
I think the first problem is that it is hard to link what you are trying to achieve with what your code says thus far. Therefore, this feedback maybe is not exactly what you are looking for, but might give some ideas. Let's structure the problem into the common elements: (1) input, (2) process, and (3) output.
Input
You mentioned that L will be a file, but I assume it is a line in a file, where each line can be one of the 3 (three) samples. In this regard, the samples also do not have consistent pattern.For this, we can build a function to convert each line of the file into Erlang term and pass the result to the next step.
Process
The question also do not mention the specific logic in parsing/processing the input. You also seem to care about the data type so we will convert and display the result accordingly. Erlang as a functional language will naturally be handling list, so on most cases we will need to use functions on lists module
Output
You didn't specifically mention where you want to display the result (an output file, screen/erlang shell, etc), so let's assume you just want to display it in the standard output/erlang shell.
Sample file content test1.txt (please note the dot at the end of each line)
[["name "," col"," dist"," a"," angv"," r "],["apollo11 ","white","0.1"," 0"," 77760"," 0.15"]].
["planets ","earth","venus "].
["a","b"].
Howto run: solarSystem:process_file("/Users/macbook/Documents/test1.txt").
Sample Result:
(dev01#Macbooks-MacBook-Pro-3)3> solarSystem:process_file("/Users/macbook/Documents/test1.txt").
apollo11 = ["white",0.1,0,77760,0.15]
planets = ["earth","venus"]
a = ["b"]
Done processing 3 line(s)
ok
Module code:
-module(solarSystem).
-export([process_file/1]).
-export([process_line/2]).
-export([format_item/1]).
%%This is the main function, input is file full path
%%Howto call: solarSystem:process_file("file_full_path").
process_file(Filename) ->
%%Use file:consult to convert the file content into erlang terms
%%File content is a dot (".") separated line
{StatusOpen, Result} = file:consult(Filename),
case StatusOpen of
ok ->
%%Result is a list and therefore each element must be handled using lists function
Ctr = lists:foldl(fun process_line/2, 0, Result),
io:format("Done processing ~p line(s) ~n", [Ctr]);
_ -> %%This is for the case where file not available
io:format("Error converting file ~p due to '~p' ~n", [Filename, Result])
end.
process_line(Term, CtrIn) ->
%%Assume there are few possibilities of element. There are so many ways to process the data as long as the input pattern is clear.
%%We basically need to identify all possibilities and handle them accordingly.
%%Of course there are smarter (dynamic) ways to handle them, but below may give you some ideas.
case Term of
%%1. This is to handle this pattern -> [["name "," col"," dist"," a"," angv"," r "],["apollo11 ","white"," 0.1"," 0"," 77760"," 0.15"]]
[[_, _, _, _, _, _], [Name | OtherParams]] ->
%%At this point, Name = "apollo11", OtherParamsList = ["white"," 0.1"," 0"," 77760"," 0.15"]
OtherParamsFmt = lists:map(fun format_item/1, OtherParams),
%%Display the result to standard output
io:format("~s = ~p ~n", [string:trim(Name), OtherParamsFmt]);
%%2. This is to handle this pattern -> ["planets ","earth","venus "]
[Name | OtherParams] ->
%%At this point, Name = "planets ", OtherParamsList = ["earth","venus "]
OtherParamsFmt = lists:map(fun format_item/1, OtherParams),
%%Display the result to standard output
io:format("~s = ~p ~n", [string:trim(Name), OtherParamsFmt]);
%%3. Other cases
_ ->
%%Display the warning to standard output
io:format("Unknown pattern ~p ~n", [Term])
end,
CtrIn + 1.
%%This is to format the string accordingly
format_item(Str) ->
StrTrim = string:trim(Str), %%first, trim it
format_as_needed(StrTrim).
format_as_needed(Str) ->
Float = (catch erlang:list_to_float(Str)),
case Float of
{'EXIT', _} -> %%It is not a float -> check if it is an integer
Int = (catch erlang:list_to_integer(Str)),
case Int of
{'EXIT', _} -> %%It is not an integer -> return as is (string)
Str;
_ -> %%It is an int
Int
end;
_ -> %%It is a float
Float
end.

String function clause matching

I'm running into a problem when writing some simple erlang code for an old Advent of Code task.
The following program is supposed to read lines, group characters in a string by occurrence and then count the number of lines that have a repeat of three characters.
count_occurrences([], Map) -> Map;
count_occurrences([H | T], Map) ->
count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).
count(Line, Count) ->
Map = count_occurrences(Line, #{}),
case lists:member(3, maps:values(Map)) of
true -> Count + 1;
false -> Count
end.
run() ->
{ok, Binary} = file:read_file("data.txt"),
Lines = binary:split(Binary, <<"\n">>, [global]),
Result = lists:foldl(fun count/2, 0, Lines),
Result.
However, I get this error message:
10> c(day2).
{ok,day2}
11> day2:run().
** exception error: no function clause matching day2:count_occurrences(<<"bpacnmelhhzpygfsjoxtvkwuor">>,#{}) (day2.erl, line 5)
in function day2:count/2 (day2.erl, line 10)
in call from lists:foldl/3 (lists.erl, line 1263)
I don't understand why <<"bpacnmelhhzpygfsjoxtvkwuor">>,#{} doesn't match the second "count_occurrences" function clause - a string is the same as a list, right? Why doesn't it match [H | T]?
Check out this example:
-module(a).
-compile(export_all).
go([_H|_T], _X) ->
"First arg was a list";
go("a", _X) ->
"First arg was a string";
go(<<"a">>, _X) ->
"First arg was a binary".
In the shell:
5> a:go(<<"a">>, #{a=>1, b=>2}).
"First arg was a binary"
and:
6> a:go("a", #{a=>1, b=>2}).
"First arg was a list"
a string is the same as a list, right?
Yes, a double quoted string is a shortcut for creating a list of integers where the integers in the list are the ascii codes of the characters. Hence, the second function clause above will never match:
a.erl:6: Warning: this clause cannot match because a previous clause
at line 4 always matches
But....a binary, such as <<"abc">> is NOT a string, and therefore a binary is not a shortcut for creating a list of integers.
8> "a" =:= [97].
true
Okay, you knew that. But, now:
9> "a" =:= <<"a">>.
false
10> <<"a">> =:= <<97>>.
true
11> "a" =:= <<97>>.
false
And, finally:
13> <<"abc">> =:= <<97, 98, 99>>.
true
The last example shows that specifying a double quoted string inside a binary is just a shortcut for specifying a comma separated list of integers inside a binary--however specifying a double quoted string inside a binary does not somehow convert the binary to a list.
Note that you can also iterate through a binary with only slightly different syntax:
count_occurrences(<<>>, Map) -> Map;
count_occurrences(<<H, T/binary>>, Map) ->
count_occurrences(T, maps:put(H, maps:get(H, Map, 0) + 1, Map)).
By default, H is assumed to be a byte, but you can add modifiers to specify how many bits you want to select, and more. See the documentation for the Bit Syntax.
You get this error cuz function count_occurrences/2 expect first argument list - [<<"bpacnmelhhzpygfsjoxtvkwuor">>] or "bpacnmelhhzpygfsjoxtvkwuor" but was put binary - <<"bpacnmelhhzpygfsjoxtvkwuor">>. Double check input data Line in function count/2 of module day2.erl at line 10:
1> is_list([]).
true
2> is_list("").
true
3> is_list(<<"">>).
false
4> is_list(binary_to_list(<<"">>)).
true

Erlang: Get first n characters of a string

I have credit card number, let's say 5940043543536. And for security purposes I only want to display the first four digits.
How would one do that in erlang?
A string in Erlang is just a list of integers, so you can use lists:sublist/3:
1> String = "5940043543536".
"5940043543536"
2> lists:sublist(String, 1, 4).
"5940"
Note that the position argument starts from 1 and not 0.
In case you are receiving binary (instead of string)
binary:part(<<"123455678901234">>, 1, 4).
<<"2345">>
or if you need get last four digits
binary:part(<<"123455678901234">>, {byte_size(<<"123455678901234">>), -4}).
<<"1234">>
newer versions of Erlang have built in string functions. For your case
1> string:slice("123455678901234", 1, 4).
"1234"
there is a string:substring function too, which works the same way, but it has been depreciated for slice.
You can try use pattern matching:
1> String = "5940043543536".
"5940043543536"
2> [A,B,C,D|_] = String.
"5940043543536"
3> [A,B,C,D].
"5940"
Or you can create your own function, eg:
1> String = "5940043543536".
"5940043543536"
2> GetDigits = fun F(_, Acc, 0) -> lists:reverse(Acc);
F([H|T], Acc, N) -> F(T, [H|Acc], N - 1) end.
#Fun<erl_eval.43.91303403>
3> GetDigits(String, [], 4).
"5940"

Erlang badarith on IO input

I'm learning Erlang, and I'm trying to pass a message from one process to another with some input from the stdio input.
This is the code I have (I know I could use normal functions but that is not the topic).
-module(play).
-compile(export_all).
calculateArea() ->
receive
{rectangle, W, H,SenderPID} -> SenderPID ! {area,W * H};
{circle, R,SenderPID} -> SenderPID ! {area,3.14 * R * R};
_ -> io:format("We can only calculate area of rectangles or circles.")
end,
calculateArea().
doStuff() ->
CalculateArea = spawn(play,calculateArea,[]),
{ok,Width} = io:fread("Enter width","~d"),
{ok,Height} = io:fread("Enter height","~d"),
CalculateArea ! {rectangle,Width,Height,self()},
receive
{area,Size} -> io:write(Size)
end,
io:fwrite("done").
When I run play:doStuff(). I get an {badarith,[{play,calculateArea,0,[{file,"play.erl"},{line,10}]}]} error.
I don't understand why, according to the docs, "~d" will give me a decimal value, and it sure looks like that if I print it.
Whats the catch here?
io:fread returns
Result = {ok, Terms :: [term()]}
| {error, {fread, FreadError :: io_lib:fread_error()}}
| server_no_data()
So Width and Height will be lists containing a single number each. Fix by using {ok, [Width/Height]} = ....

How to extract integer from a string in Erlang?

I have this variable Code in erlang which has this value "T00059"
I want to extract this value 59 from Code.
I try to extract with this code this value "00059".
NewCode = string:substr(Code, 2, length(Code)),
Now I want to know how can we eliminate the first zero before the first integer not null. I mean how can we extract "59"?
For example if I have this value "Z00887" I should have in the final this value 887.
You can simply do (output from an interactive erlsession):
1> Code = "Z00887",
1> {NewCode, _Rest} = string:to_integer(string:substr(Code, 2, length(Code))),
1> NewCode.
887
(My answer in test with loop in erlang goes into more detail regarding the same problem)
This code will skip starting zeros. If you want to save them change $1 to $0
extract_integer([]) -> [];
extract_integer([H|T]) when (H >= $1) and (H =< $9) -> [H] ++ T;
extract_integer([_H|T]) -> extract_integer(T).

Resources