How to extract integer from a string in Erlang? - 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).

Related

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"

How to compare multiple fields in Elm?

I'm currently writing a web-based vocabulary trainer in Elm. This requires sorting a list of words by a custom comparator.
The type I want to sort is:
type alias Word =
{ id: Int
, sourceWord: String
, targetWord: String
, numTries: Int
, numCorrect: Int
, createdAt: Maybe Date -- might be empty, therefore wrapped in Maybe
, lastAskedAt: Maybe Date -- might be empty, therefore wrapped in Maybe
}
type alias WordList = List (Word)
My rules for comparison are (in descending order of importance):
number of correct guesses (asc)
number overall guesses (desc)
when word was last asked (asc)
when word was added (desc)
The best approach I could come up with is this:
compareWords: Word -> Word -> Basics.Order
compareWords w1 w2 =
let
dateToComparable d = Date.Format.format "%Y-%m-%d" d
orderNumCorrect = compare w1.numCorrect w2.numCorrect
orderNumTries = compare w2.numTries w1.numTries -- switch ordering to sort descending
orderLastAskedAt = case (w1.lastAskedAt, w2.lastAskedAt) of
(Just a1, Just a2) -> compare (dateToComparable a1) (dateToComparable a2)
(Nothing, Just _) -> Basics.LT
(Just _, Nothing) -> Basics.GT
(Nothing, Nothing) -> Basics.EQ
orderCreatedAt = case (w2.createdAt, w1.createdAt) of -- switch ordering to sort descending
(Just a1, Just a2) -> compare (dateToComparable a1) (dateToComparable a2)
(Nothing, Just _) -> Basics.LT
(Just _, Nothing) -> Basics.GT
(Nothing, Nothing) -> Basics.EQ
in
case orderNumCorrect of
Basics.EQ -> case orderNumTries of
Basics.EQ -> case orderLastAskedAt of
Basics.EQ -> orderCreatedAt
_ -> orderLastAskedAt
_ -> orderNumTries
_ -> orderNumCorrect
which I don't like for a number of reasons:
it's ugly as hell
it requires me to use Date.Format.format (from mgold/elm-date-format) to compare Date values (since Date apparently is not comparable)
Is there a more elegant / Elm-ish way to achieve what I want?
Update + solution
As #"Zimm i48" suggested in their most excellent answer, here's a much shorter version that uses the elm-ordering package:
dateToComparable : Maybe Date -> Time
dateToComparable =
Maybe.map Date.toTime >> Maybe.withDefault 0
compareWords : Ordering Word
compareWords =
Ordering.byField .numCorrect
|> Ordering.breakTiesWith (Ordering.byField (.numTries >> negate))
|> Ordering.breakTiesWith (Ordering.byField (.lastAskedAt >> dateToComparable))
|> Ordering.breakTiesWith
(Ordering.byField (.createdAt >> dateToComparable >> negate))
A more Elm-ish way of doing this kind of things is compositionally, thanks to the |> operator.
The elm-ordering library provides the primitives that you need to do this kind of things, especially the Ordering.byField and Ordering.breakTiesWith functions.
As for the dates, my advice would be to use Date.toTime (the resulting values are comparable).
Bonus: full implementation of your ordering function available for testing here: https://runelm.io/c/xoz. You can see it's much simpler and more readable than yours...

Unexpected keyword 'val' in definition

Learning F# as part of my course, and can do some cool things, but something has been bugging me, whenever I use the val keyword, I get an error. I think it could be due to not declaring something in script, but I don't really know.
module Prime
#light
let nums = [1; 2; 3; 4; 5];;
val nums : list<int>
let rec sum list =
match list with
| h::tail -> (sum tail) + h
| [] -> 0
val sum : list<int> -> int
I get (line 5):
Error 1 Unexpected keyword 'val' in definition . Expected incomplete structured construct at or before this point or other token
Any ideas?
The val keyword in F# (unlike 'val' in ML) is used to declare a field in a class or structure type without initializing it.
http://msdn.microsoft.com/en-us/library/dd469494.aspx
if you want to define mutable value in the Module you can use
let mutable...
By the way, if you define the value with the same name (like 'nums') twice or more times then the effective value for the compiler will be latest defined in the scope.
So actually, I had misread the coursework set out, annoyingly the papers use val to define what the expected output of the function is, as opposed to using it as the keyword it is meant to be. Hence my confusion and lots of head scratching.
This looks like F# interactive output mixed in with code.
If I type this into FSI:
let nums = [1; 2; 3; 4; 5];;
The output is
val nums : int list = [1; 2; 3; 4; 5]
Note that ;; is where FSI parses and runs input. You wouldn't have this in non-interactive code. The output might differ because of an older version or editing, but nontheless, it doesn't belong in code.
Coincidentally, val is also a rarely used F# keyword for explicit fields. Hence the strange error message.
The val keyword is used to declare a field ; it must be used inside a type definition (class or structure). Since in your code the variable nums is already defined and as the list type inferred by F# type inference engine, there is no need for your val line.
An example of val keyword usage is (from msdn) :
type MyType() =
let mutable myInt1 = 10
[<DefaultValue>] val mutable myInt2 : int
[<DefaultValue>] val mutable myString : string
member this.SetValsAndPrint( i: int, str: string) =
myInt1 <- i
this.myInt2 <- i + 1
this.myString <- str
printfn "%d %d %s" myInt1 (this.myInt2) (this.myString)

How to use '/=' to ensure the elements in the array are unique?

I want to ensure the elements in the array are unique.
4> A=[1,2, 3].
[1,2]
5> lists:nth(1, A) /= lists:nth(2, A).
true
6> lists:nth(1, A) /= lists:nth(2, A) /= lists:nth(3, A).
* 1: syntax error before: '/='
Unfortunately the '/=' does not work in the prompt #6 when I extend it to a three or more elements use case.
What is the acceptable erlang syntax?
i guess proper way is to make set from the list and then compare set size and list length. Kind of
A = [1,2,3].
S = sets:from_list(A).
length(A) == sets:size(S).
If your array has a fixed, compile-time known length (then you should use tuples) and you can pattern-match it:
case A of %% Size = 3
{_,X,X} -> true;
{X,_,X} -> true;
{X,X,_} -> true;
{_,_,_} -> false
end
Otherwise, I guess you'll go this very inefficient way.

Resources