Tag Archives: Erlang Thursday

Erlang Thursday – calendar:is_leap_year/1

Today’s Erlang Thursday is on calendar:is_leap_year/1.

calendar:is_leap_year/1 takes a non-negative integer value representing a year, and will return true if that year is a leap year, or false otherwise.

calendar:is_leap_year(2015).
% false
calendar:is_leap_year(2012).
% true
calendar:is_leap_year(2017).
% false
calendar:is_leap_year(2000).
% true
calendar:is_leap_year(1900).
% false
calendar:is_leap_year(0).
% true

By having a built in function as part of the core Erlang libraries, it means you don’t have to code up the rules, or even go lookup the rules to remember how the century years are determined to be leap years or not.

And if you do pass in a negative number for the year, Erlang will raise an exception, as there are no clauses which match a negative number for the year.

calendar:is_leap_year(-1).
% ** exception error: no function clause matching calendar:is_leap_year(-1) (calendar.erl, line 183)
calendar:is_leap_year(-4).
% ** exception error: no function clause matching calendar:is_leap_year(-4) (calendar.erl, line 183)

–Proctor

Erlang Thursday – calendar:valid_date/3

Today’s Erlang Thursday is calendar:valid_date/3.

Originally, I was thinking it was going to be calendar:time_difference/3, but then I looked into the Erlang documentation for the calendar module. and saw that it was marked as obsolete, so today I present calendar:valid_date/3.

The arguments to calendar:valid_date/3 are an integer for the year, integer for the month, and an integer for the day. calendar:valid_date/3 returns the atom true if the day passed in is a valid date, and the atom false if it is not a valid date.

calendar:valid_date(2015, 04, 31).
% false
calendar:valid_date(2015, 04, 30).
% true
calendar:valid_date(2015, 02, 29).
% false
calendar:valid_date(2012, 02, 29).
% true
calendar:valid_date(2015, 11, 31).
% false
calendar:valid_date(2015, 11, 76).
% false
calendar:valid_date(2015, 17, 13).
% false

Just a quick check for our sanity that the day this post was published is a valid date as well.

calendar:valid_date(2015, 04, 23).
% true

Now let’s try to break this a bit and test to see how it can handle 0‘s and negative integer values.

calendar:valid_date(-1, 04, 23).
% false
calendar:valid_date(2015, -7, 21).
% false
calendar:valid_date(2015, 7, -13).
% false
calendar:valid_date(0, 0, 0).
% false

As one might hope, unless you deal with B.C. era dates often, a date with a negative value is not a valid date.

Erlang also provides a calendar:valid_date/1 that takes a tuple of the year, month, and day values as well.

calendar:valid_date({2015, 11, 76}).
% false
calendar:valid_date({2015, 04, 23}).
% true

–Proctor

Erlang Thursday – calendar:date_to_gregorian_days/3

Today’s Erlang Thursday covers calendar:date_to_gregorian_days/3.

As we saw in last week’s Erlang Thursday on calendar:day_of_the_week/3 when we were looking at some error messages, we saw that the errors were coming from calendar:date_to_gregorian_days/3.

calendar:day_of_the_week(0, 0, 0).
% ** exception error: no function clause matching calendar:date_to_gregorian_days(0,0,0) (calendar.erl, line 114)
%      in function  calendar:day_of_the_week/3 (calendar.erl, line 151)
calendar:day_of_the_week(1970, 2, 31).
% ** exception error: no true branch found when evaluating an if expression
%      in function  calendar:date_to_gregorian_days/3 (calendar.erl, line 116)
%      in call from calendar:day_of_the_week/3 (calendar.erl, line 151)
calendar:day_of_the_week(1970, 13, 2).
% ** exception error: no function clause matching calendar:last_day_of_the_month1(1970,13) (calendar.erl, line 243)
%      in function  calendar:date_to_gregorian_days/3 (calendar.erl, line 115)
%      in call from calendar:day_of_the_week/3 (calendar.erl, line 151)

I promised you at the end of that post we would take a deeper look at calendar:date_to_gregorian_days/3 next time, so let’s fulfill that promise.

calendar:date_to_gregorian_days/3 takes three arguments, a non-negative integer for the year, an integer between 1 and 12 (inclusive) for the month, and an integer between 1 and 31 (inclusive) for the day of the month, and returns the number of days since 0000-01-01 in the Gregorian calendar.

calendar:date_to_gregorian_days(2015, 4, 16).
% 736069
calendar:date_to_gregorian_days(0, 1, 1).
% 0
calendar:date_to_gregorian_days(1, 1, 1).
% 366
calendar:date_to_gregorian_days(1970, 1, 1).
% 719528
calendar:date_to_gregorian_days(1999, 12, 31).
% 730484

There is also a version calendar:date_to_gregorian_days/1, that takes a tuple of year, month, and day available if your code already has the date in tuple format.

calendar:date_to_gregorian_days({2015, 4, 16}).
% 736069
calendar:date_to_gregorian_days({0, 1, 1}).
% 0
calendar:date_to_gregorian_days({1, 1, 1}).
% 366

And if we pass something invalid to calendar:date_to_gregorian_days/1, we see that it is calling calendar:date_to_gregorian_days/3. So it is just a nice helper function that does the pattern match destructing for us.

calendar:date_to_gregorian_days({1, 1, 0}).
** exception error: no function clause matching calendar:date_to_gregorian_days(1,1,0) (calendar.erl, line 114)

–Proctor

Erlang Thurday – lists:delete/2

Today’s Erlang Thursday in lists:delete/2.

lists:delete/2 takes a Erlang term as it’s first argument, and will remove that item from the list passed in as the second argument.

lists:delete(1, [8, 7, 6, 5, 4, 3, 2, 1]).
% [8,7,6,5,4,3,2]
lists:delete(4, [1, 1, 2, 3, 5, 8]).
% [1,1,2,3,5,8]
lists:delete(72, "Hello World!").
% "ello World!"
lists:delete(d, [a, b, c, d]).
% [a,b,c]
lists:delete(4, []).
% []
lists:delete({b, 2}, [{a, 1}, {b, 2}, {c, 3}]).
% [{a,1},{c,3}]
lists:delete([1, 2, 3], [[4, 5, 6], [7, 8, 9], [1, 2, 3]]).
% [[4,5,6],[7,8,9]]

Note that lists:delete/2 only removes the first item found in the list, and leaves any other occurrences of the item in the list.

€lists:delete(1, [1, 1, 2, 3, 5, 8]).
% [1,2,3,5,8]

As lists:delete/2 was a easy function to demonstrate, and leaving it at this would be a very short post, I thought it might be worth showing a how you might write a very naive1 implementation of lists:delete/2 yourself.

-module(my_lists).

-export([delete/2]).

delete(Item, List) ->
    delete(Item, [], List).

delete(Item, Checked, [Item|Rest]) ->
    lists:reverse(Checked) ++ Rest;
delete(Item, Checked, [Head|Rest]) ->
    delete(Item, [Head | Checked], Rest);
delete(_Item, Checked, []) ->
    lists:reverse(Checked).

Let’s start with our delete function as we expect it to be called from the outside world.

my_lists:delete/2 is the nice API function that just calls delete/3 which is a “private” function (not exported) so the consumer doesn’t have to worry about passing in the accumulator for the items we have checked so far, which we pass as an empty list for the initial value.

-module(my_lists).

-export([delete/2]).

delete(Item, List) ->
    delete(Item, [], List).

The first function clause of delete/3 uses pattern matching to check if the item we want to delete is also the first item in the rest of the list to check. If the pattern match succeeds, we have found the first occurrence of the item to remove! We can stop processing the list and return the result, which is the reverse of the list of items we have checked so far combined with the rest of the items we never got around to checking.

delete(Item, Checked, [Item|Rest]) ->
    lists:reverse(Checked) ++ Rest;

The second clause “knows” that the item we are wanting to delete and the first item in the rest of the list don’t match. How does it “know”? Because if they did match, the first clause would have matched and this clause would not have been evaluated. As we haven’t found the item to remove, we add the item held by Head to the list of Checked items, and then continue calling delete/3. The fact that we are passing a new list of the checked items by prepending Head to the list in Checked is why we need to reverse Checked in the first and third function clauses.

delete(Item, Checked, [Head|Rest]) ->
    delete(Item, [Head | Checked], Rest);

The third, and final, clause of delete/3 has reached the end of the list and not found the item, so we just return the list we have reversed it.

delete(_Item, Checked, []) ->
    lists:reverse(Checked).

There you have it, a very naive1 implementation of your very own version of lists:delete/2.

–Proctor

1. Naive because this is not optimized for performance, or exhaustively tested for completely accurate behavior of lists:delete/2. back

Erlang Thursday – erlang:list_to_atom/1

Today’s Erlang Thursday covers erlang:list_to_atom/1.

erlang:list_to_atom/1 takes a string, and returns an Erlang atom.

list_to_atom("foo").
% foo
list_to_atom("Foo").
% 'Foo'
list_to_atom("Foo_bar").
% 'Foo_bar'
list_to_atom("foo_bar").
% foo_bar
list_to_atom("foo"++"bar").
% foobar
list_to_atom("Erlang").
% 'Erlang'
list_to_atom("Elixir").
% 'Elixir'

This can be useful if you are having to create keys or identifiers based off strings read in from outside the system, such as parsing a CSV style header.

lists:map(fun erlang:list_to_atom/1,
          string:tokens("firstName,lastName,age,gender,preferredName,dateOfBirth", ",")).
% [firstName,lastName,age,gender,preferredName,dateOfBirth]

You do need to be careful when using erlang:list_to_atom/1 on strings acquired from the outside world of your program, as it only handles strings with character values under 256. But any character value1 under 256 is fair game to be turned into an atom.

list_to_atom("Joe, Mike, and Robert").
% 'Joe, Mike, and Robert'
list_to_atom("it's").
% 'it's'
list_to_atom("heyn").
% 'heyn'€
list_to_atom("with_supported_char"++[255]).
% with_supported_charÿ
list_to_atom("with_non_supported_char"++[256]).
% ** exception error: bad argument
%      in function  list_to_atom/1
%         called as list_to_atom([119,105,116,104,95,110,111,110,95,115,117,112,112,111,114,
%                                 116,101,100,95,99,104,97,114,256])

–Proctor


1. Remember that character values are non-negative integer values as well. 0-255 inclusive. back

Erlang Thurday – ordsets:is_disjoint/2

Today’s Erlang Thurday covers ordsets:is_disjoint/2.

There are times in your in your coding day where you have problems where you need to know if given a list of items that none of those items appear in a secondary list.

Your first intuition might be to write out the code as described, like such:

not( lists:any(fun(Item) -> lists:member(Item, [1, 3, 5, 7]) end, [2, 4, 6])).
% true

And while that is accurate, if you redefine your problem in more mathematical terms, you can start to think in sets. When you start to think in terms of sets, you realize that you can check to see if the intersection of the two sets is the empty set.

ordsets:intersection([1, 3, 5, 7], [2, 4, 6]) =:= [].
% true

This is becoming not only more concise, but also more explicit about what you are trying to check.

We can do better still, by checking if the lists are disjoint sets. Enter ordsets:is_disjoint/2.

ordsets:is_disjoint/2 takes two lists, and returns true if no elements are in common.

ordsets:is_disjoint([1, 3, 5, 7], [2, 4, 6]).
% true
ordsets:is_disjoint([1, 2, 3, 5, 7], [2, 4, 6]).
% false

Because ordsets:is_disjoint/2 operates against two lists, we do not have to make sure the elements are unique prior to calling ordsets:disjoint/2.

ordsets:is_disjoint([1, 1, 3, 5, 7, 5, 3], [2, 4, 2, 2, 6]).
% true
€ordsets:is_disjoint([1, 2, 3, 5, 7], [2, 4, 2, 2, 6]).
% false

And if either list passed to ordsets:is_disjoint/2 is an empty list, the result is that the lists are disjoint.

ordsets:is_disjoint([1, 2, 3, 5, 7], []).
% true
ordsets:is_disjoint([], [2, 4, 6]).
% true
ordsets:is_disjoint([], []).
% true

And if you are curious, by running ordsets:is_disjoint/2 through timer:tc/3, we can see that as soon as Erlang knows that the sets are not disjoint, it returns false. And if you remember from the previous Erlang Thrusday on timer:tc/3, the return value is a tuple with the first element being the number of microseconds it took to complete.

timer:tc(ordsets, is_disjoint, [lists:seq(1, 1000000), lists:seq(2000000, 3000000)]).
% {19032,true}
€timer:tc(ordsets, is_disjoint, [lists:seq(1, 1000000), lists:seq(1, 3000000)]).
% {2,false}

–Proctor

Erlang Thursday – lists:flatmap/2

Today’s Erlang Thursday is on lists:flatmap/2.

The selection of this function comes from some Elixir exercises I was doing earlier in the week, and was getting confused because when using the Enum.flat_map/2 function in Elixir wasn’t working as I expected it to.

I circled back to Erlang to check the behavior of lists:flatmap/2 in Erlang, and after getting over my confusion that the flatmap function wasn’t working in Erlang, the common behavior made sense, and I realized I contorted the concept in my head and it was time to take a step back and figure out what lists:flatmap/2 actually does.

Somehow what I had convinced myself that lists:flatmap/2 did was to take a list of items that were nested arbitrarily deep, and mapped over the flattened list in the equivalent of this:

lists:map(fun(X) -> X * X end, lists:flatten([1, [[2, [3]], 4]])).
% [1,4,9,16]

Looking closer at the documentation, after even trying Ruby’s flat_map function, it was clear I had completely confused myself as to how it should work, so time to carefully read the Erlang docs.

They stated that lists:flatmap/2 takes a function that takes an item of type A and returns a list of items that are of type B, and that the second argument to lists:flatmap/2 was a list of items of type A.

Being already confused based off how I thought it should work, it wasn’t registering until I saw the part of the documentation that describes that lists:flatmap/2 behaves as if defined as

flatmap(Fun, List1) ->
    append(map(Fun, List1)).

This started to click for me for what lists:flatmap/2 was actually doing. In the picture that I had in my head, I was expecting to flatten the list first, and then map over the items, but it does the map first, and then does only the flatten, but of only one level deep.

lists:flatmap(fun({Item, Count}) -> 
                  lists:duplicate(Count, Item)
              end,
              [{a, 1}, {b, 2}, {'C', 3}, {'_d_', 4}]).
% [a,b,b,'C','C','C','_d_','_d_','_d_','_d_']

And if we pass those values to the “equivalent” behavior of calling map and then calling append on the list returned from map, we see the results are the same.

lists:append(
    lists:map(fun({Item, Count}) ->
                  lists:duplicate(Count, Item)
              end,
              [{a, 1}, {b, 2}, {'C', 3}, {'_d_', 4}])).
% [a,b,b,'C','C','C','_d_','_d_','_d_','_d_']

And to further clarify, lists:flatmap/2 doesn’t even do a flatten on the resulting list, but simply adjoins the lists that were returned from the mapping function. This can be seen below, as we can see there is still an nested list structure in the results, and the resulting list is not only one level deep.

lists:flatmap(fun(X) -> [X, [X]] end, [a, b, c, d]).
% [a,[a],b,[b],c,[c][/c],d,[d]]

Hope this can save you the confusion I managed to get myself into,
–Proctor

Erlang Thursday – dict:merge/3

Today’s Erlang Thursday is on dict:merge/3.

dict:merge/3 takes 3 arguments, the first argument is a merge function to be called when there is a key collision, and the second and third arguments are dictionaries.

The merge function is a function that takes the key as the first argument, the value from the first dictionary as the second argument, and the value from the second dictionary as the the third argument.

dict:merge(fun (_Key, Value1, Value2) -> [Value1, Value2] end,
           dict:from_list([{a, 1}, {b, 2}, {x, 5}]),
           dict:from_list([{x, 7}, {y, 8}, {z, 10}])).
% {dict,5,16,16,8,80,48,
%       {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%       {{[],
%         [[a|1]],
%         [[b|2]],
%         [],[],[],[],[],
%         [[x,5,7]],
%         [[y|8]],
%         [[z|10]],
%         [],[],[],[],[]}}}

dict:merge(fun (_Key, Value1, Value2) -> Value1 * Value2 end,
           dict:from_list([{a, 1}, {b, 2}, {x, 5}]),
           dict:from_list([{x, 7}, {y, 8}, {z, 10}])).
% {dict,5,16,16,8,80,48,
%       {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%       {{[],
%         [[a|1]],
%         [[b|2]],
%         [],[],[],[],[],
%         [[x|35]],
%         [[y|8]],
%         [[z|10]],
%         [],[],[],[],[]}}}

The merge function passed to dict:merge/3 only gets called in the case of a collision, as shown below. Note that there is a call to exit in the body of the function which would cause the process to terminate if the function was ever invoked.

dict:merge(fun (_Key, _Value1, _Value2) -> exit(merge_happened) end,
           dict:from_list([{a, 1}, {b, 2}]),
           dict:from_list([{x, 7}, {y, 8}, {z, 10}])).
% {dict,5,16,16,8,80,48,
%       {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%       {{[],
%         [[a|1]],
%         [[b|2]],
%         [],[],[],[],[],
%         [[x|7]],
%         [[y|8]],
%         [[z|10]],
%         [],[],[],[],[]}}}

If you wish to treat the merge as an overlay of the second dictionary over the first, the merge function just needs to return the value from the second dictionary in the case of a key conflict.

dict:merge(fun (_Key, _Value1, Value2) -> Value2 end,
           dict:from_list([{a, 1}, {b, 2}, {x, 5}]),
           dict:from_list([{x, 7}, {y, 8}, {z, 10}])).
% {dict,5,16,16,8,80,48,
%       {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%       {{[],
%         [[a|1]],
%         [[b|2]],
%         [],[],[],[],[],
%         [[x|7]],
%         [[y|8]],
%         [[z|10]],
%         [],[],[],[],[]}}}

If you want to keep all of the keys and values in the first dictionary, and just add the keys and values that are in the second dictionary, but not in the first dictionary, the merge function should just return the value associated with the first dictionary.

dict:merge(fun (_Key, Value1, _Value2) -> Value1 end,
           dict:from_list([{a, 1}, {b, 2}, {x, 5}]),
           dict:from_list([{x, 7}, {y, 8}, {z, 10}])).
% {dict,5,16,16,8,80,48,
%       {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
%       {{[],
%         [[a|1]],
%         [[b|2]],
%         [],[],[],[],[],
%         [[x|5]],
%         [[y|8]],
%         [[z|10]],
%         [],[],[],[],[]}}}

Just a peek into the new Maps that came in to Erlang in the 17.0 release.

–Proctor

Erlang Thursday – string:join/2

Today’s Erlang Thursday is on string:join/2.

string:join/2 takes a list of strings as its first argument, and a string separator used to join the strings together into a single string.

91> string:join(["a", "b", "c"], "").
"abc"
92> string:join(["a", "b", "c"], "-").
"a-b-c"

The separator string can be a string of any length, and doesn’t just have to be a single character.

93> string:join(["a", "b", "c"], "___").
"a___b___c"
94> string:join(["a", "b", "c"], " ").  
"a b c"

And as with any string, a list of characters, or even integers, can be used as the separator string.

string:join(["a", "b", "c"], [$A]).
# "aAbAc"
string:join(["a", "b", "c"], [52]).
# "a4b4c"

–Proctor

Erlang Thursday – string:tokens/2

Today’s Erlang Thursday is string:tokens/2.

string:tokens/2 takes a string as the first argument, and a list of separators to split the string on, and returns a list of token strings.

string:tokens("foo", "").
% ["foo"]
string:tokens("banana", "a").
% ["b","n","n"]
string:tokens("It was the best of times, it was the worst of times", " ").
% ["It","was","the","best","of","times,","it","was","the",
%  "worst","of","times"]

If consecutive separators appear in the string they will be treated as a single separator, and no empty strings will be returned.

string:tokens("Mississippi", "s").
% ["Mi","i","ippi"]
65> string:tokens("Mississippi", "sp").
% ["Mi","i","i","i"]
string:tokens("Mississippi", "is").
% ["M","pp"]

The order of the separators in the separator list passed to string:tokens/2 does not matter, and can be specified in any order.

string:tokens("Mississippi", "ps").
% ["Mi","i","i","i"]
65> string:tokens("Mississippi", "sp").
% ["Mi","i","i","i"]

And as the separator list is just simply a list of separators, instead of passing a string, the integer values for the characters to use as the separators can be passed as a list, as a list of the integers is the same as a string.

$s.
% 115
$p.
% 112
[115, 112].
% "sp"
string:tokens("Mississippi", [115]).
% ["Mi","i","ippi"]
string:tokens("Mississippi", [115, 112]).
% ["Mi","i","i","i"]

–Proctor