Today we're getting to better know the Enum module, which contains functions for interacting with anything enumerable. In my experience so far, this is one of the most used modules in Elixir. I think it's particularly important to understand the functions in this module and what they are capable of.
Many of these functions are higher-order functions. Higher-order functions are pluggable algorithms that have been written for us. All we need is to plug in another function to make it do what we want.
Since the Enum module operates on enumerables, everything will perform linearly, in O(n) time. The functions will iterate over the enumerable to arrive at their answer. Judging from enumerables in other languages, Elixir enumerables only provide the minimum functionality to iterate over their elements.
Remember that not just lists are enumerable in Elixir. Maps are also enumerable, and each map element consists of a {key, value}
tuple. Ranges are enumerable. Tuples are not enumerable.
The Enum module (as of the time I wrote this) has 73 functions. That's a lot to go over in a single post. So that I won't bore you to death, and so I won't get tired of writing and lose motivation, I'm going to split this up, probably into 4 different posts.
Enum.all?/2
The Enum.all?/2
function takes in an enumerable and a function as parameters. It calls the function for each element, and if the function evaluates to true for every element, Enum.all?/2
returns true. If the function you pass in returns false at least once, the call to Enum.all?/2
returns false.
This is useful to verify that all the elements in a collection meet some criteria, where the criteria are defined in the function that was passed in as a parameter.
#Create some reusable functions
iex> is_even = fn x -> rem(x, 2) == 0 end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> is_odd = fn x -> rem(x, 2) == 1 end
#Function<6.99386804/1 in :erl_eval.expr/5>
#Check if all the numbers in a list are even (they are)
iex> Enum.all?([2, 4, 6, 8, 10], is_even)
true
#Check if all the numbers in a list are even (they are not)
iex> Enum.all?([2, 3, 4, 6, 8, 10], is_even)
false
#Check if all the numbers in an empty list are even (this returns true)
iex> Enum.all?([], is_even)
true
#Check if all the numbers in a list are odd (they are)
iex> Enum.all?([1, 3, 5, 7, 9, 11], is_odd)
true
#Check if all the numbers in a list are odd (they are not)
iex> Enum.all?([1, 3, 5, 7, 9, 10, 11], is_odd)
false
#Check if all the names in a list start with "B" (they do)
iex> Enum.all?(["Bob", "Bill", "Bambi", "Barf"], fn name -> String.starts_with?(name, "B") end)
true
#Check if all the names in a list start with "B" (they do not)
iex> Enum.all?(["Bob", "Bill", "Dilbert", "Bambi", "Barf"], fn name -> String.starts_with?(name, "B") end)
false
In the examples above, we use Enum.all/2
to determine if all the elements in a list are odd or even numbers. We also determine whether all the names in a list start with "B".
This function is equivalent to the every
function in Javascript arrays
const isOdd = number => number % 2 == 1;
let allOdd = [1, 3, 5, 7].every(isOdd);
The C# IEnumerable LINQ extensions have something similar as well.
List<int> numberList = new List<int>() {1, 3, 4, 7};
bool allOdd = numberList.All(number => number % 2 == 1);
Enum.any?/2
Whereas Enum.all?/2
will return true only if the criteria are true for all the elements in a collection, Enum.any?/2
returns true if any of elements match the criteria. It will only return false if the function we passed in never returns true when it is applied to the elements in the list.
This is useful to verify that at least one of the elements in a collection meet some criteria, where the criteria are defined in the function that was passed in as a parameter.
There's some short-circuiting potential with this function because it can return true when the criteria function first returns true, and does not always have to traverse the entire collection. Enum.any?/2
only needs to find one element for which the criteria function returns true, so it can know the answer before it finished iterating.
#Create some reusable functions
iex> is_even = fn x -> rem(x, 2) == 0 end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> is_odd = fn x -> rem(x, 2) == 1 end
#Function<6.99386804/1 in :erl_eval.expr/5>
#Check if there are any odd numbers in the list (there are)
iex> Enum.any?([3, 5, 7], is_odd)
true
#Check if there are any even numbers in the list (there are not)
iex> Enum.any?([3, 5, 7], is_even)
false
#Check if there are any even numbers in the list (there are)
iex> Enum.any?([3, 4, 5, 7], is_even)
true
#Check if there are any odd numbers in the same list (there are)
iex> Enum.any?([3, 4, 5, 7], is_odd)
true
This function is equivalent to the some
function in Javascript arrays
const isOdd = number => number % 2 == 1;
const oddNumbersFound = [1, 3, 5, 6, 7].some(isOdd);
The C# equivalent is the LINQ IEnumerable.Any.
Func<int, bool> isOdd = number => number % 2 == 1;
List<int> numberList = new List() {3, 4, 5, 7};
bool oddNumbersFound = numberList.Any(isOdd);
Enum.at/3
The Enum.at/3
function retrieves the value at a particular index in an enumerable. Since the Enum module just uses functionality that is common to all enumerables, this means that the performance will be O(n) even if the data type has other functionality that allows a O(1) value lookup.
We can give this function the following parameters:
- The enumerable
- The index of the value to be retrieved. This value can also be negative, in which case the function counts from the end of the enumerable instead of from the beginning. So -1 is the last element, -2 is the second-to-the-last element, etc.
- The default value that will be returned if the index does not exist in the enumerable. This is an optional parameter. If you don't specify a value, this parameter will default to
nil
.
I would not recommend using this function frequently, since there are data structures more suitable to random-access, but if you need to do this occasionally, it's not so bad. There's always some sort of tradeoff.
Let's take a look at some examples of using this function.
#Access values that exist
iex> Enum.at([1, 2, 3, 4, 5], 0)
1
iex> Enum.at([1, 2, 3, 4, 5], 2)
3
iex> Enum.at([1, 2, 3, 4, 5], 4)
5
#Access values that don't exist
iex> Enum.at([1, 2, 3, 4, 5], 6)
nil
iex> Enum.at([1, 2, 3, 4, 5], 10)
nil
#Access values using negative indexes
iex> Enum.at([1, 2, 3, 4, 5], -1)
5
iex> Enum.at([1, 2, 3, 4, 5], -2)
4
iex> Enum.at([1, 2, 3, 4, 5], -4)
2
iex> Enum.at([1, 2, 3, 4, 5], -5)
1
#Access values that don't exist using negative indexes
iex> Enum.at([1, 2, 3, 4, 5], -6)
nil
iex> Enum.at([1, 2, 3, 4, 5], -10)
nil
#Override the default value parameter with an atom
iex> Enum.at([1, 2, 3, 4, 5], 2, :not_found)
3
iex> Enum.at([1, 2, 3, 4, 5], 7, :not_found)
:not_found
iex> Enum.at([1, 2, 3, 4, 5], -7, :not_found)
:not_found
The equivalent to this function in Javascript is just the array element access operator, []
. However, the array access operator doesn't work in the same way an enumerable function would operate, in that it's specific to the array type. Javascript has a concept of enumerable properties, but it doesn't appear to have a general enumerable interface that an object can implement.
array = [1, 3, 4];
value = array[2];
C# has an equivalent LINQ extension method IEnumerable.ElementAtOrDefault()
, which uses only the methods defined in the IEnumerable interface.
List<int> numberList = new List<int>() {1, 3, 4};
int value = numberList.ElementAtOrDefault(2);
If the index does not exist, the default value is returned. For an int
data type, that default value is 0
.
Enum.chunk_by/2
The Enum.chunk_by/2
function is a function that is a little harder to explain than any previous function. Like many of the functions in the Enum module, we pass in an enumerable and a function. The Enum.chunk_by/2
function breaks an enumerable into chunks, where each chunk is a list.
The function we pass in as a parameter examines an element and returns a result. As long as the result stays the same, the elements are included in the same chunk. If the next element produces a different result than the previous element, a new chunk is created. To me, this sounded a lot like a grouping function at first. It is in a sense, but it's not a grouping function in the sense that it will group all elements that produce the same function result in the same chunk.
At first glance I expected it to work, something like this:
is_even = &(rem(&1, 2) == 0)
chunks = Enum.chunk_by([1, 2, 3, 4, 5], is_even)
#chunks is equal to [[2, 4], [1, 3, 5]], where one chunk contains
#the even numbers and the other chunk contains the odd numbers
I figured that if the function returns a true for even numbers, it would put all even numbers in the same chunk and the odd numbers in another chunk. This is not how it works. Every time the result differs from the previous result, it will create a new chunk.
So the previous example actually looks like this:
iex> is_even = &(rem(&1, 2) == 0)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> Enum.chunk_by([1, 2, 3, 4, 5], is_even)
[[1], [2], [3], [4], [5]]
Since the even and odd numbers are interleaved, the function always returns a different result than for the previous element. So only consecutive even and odd numbers will be included in the same chunk.
iex> Enum.chunk_by([1, 2, 4, 3, 5, 6, 4, 8, 5], is_even)
[[1], [2, 4], [3, 5], [6, 4, 8], [5]]
So it's a grouping function that only groups similar consecutive elements.
Here's an example that groups an ordered collection into groups of 10.
iex> numbers = 1..100
1..100
iex> Enum.chunk_by(numbers, &(div(&1 - 1, 10)))
[
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
')*+,-./012',
'3456789:;<',
'=>?@ABCDEF',
'GHIJKLMNOP',
'QRSTUVWXYZ',
'[\\]^_`abcd'
]
The later numbers are of course interpreted by IEx as character lists because they match the code points of printable characters.
So while I didn't immediately grasp the usefulness of Enum.chunk_by/2
, I started to get a feel for how to use it after playing around with it for a bit.
Neither C#, Javascript, or the widely-used Javascript library Lodash have an equivalent to this function.
Enum.chunk_every/2
The Enum.chunk_every/2
function is fairly simple. It divides up an enumerable into chunks of size N, where N is a positive integer. This function is useful when you only want to process a chunk of a collection at a time, and you need a way to divide it into chunks.
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 3)
[[5, 8, 9], [3, -3, 0], [12, -8, 5], '\n']
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 4)
[[5, 8, 9, 3], [-3, 0, 12, -8], [5, 10]]
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 1)
[[5], '\b', '\t', [3], [-3], [0], '\f', [-8], [5], '\n']
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 10)
[[5, 8, 9, 3, -3, 0, 12, -8, 5, 10]]
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 9)
[[5, 8, 9, 3, -3, 0, 12, -8, 5], '\n']
iex> Enum.chunk_every([5, 8, 9, 3, -3, 0, 12, -8, 5, 10], 100)
[[5, 8, 9, 3, -3, 0, 12, -8, 5, 10]]
In the very last example for the previous function Enum.chunk_by/2
, I divided the range enumeration 1..100 into chunks of 10, but doing that using Enum.chunk_by/2
only works on an ordered enumerable. This example will work with any enumerable, ordered or unordered.
iex> Enum.chunk_every(1..100, 10)
[
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
')*+,-./012',
'3456789:;<',
'=>?@ABCDEF',
'GHIJKLMNOP',
'QRSTUVWXYZ',
'[\\]^_`abcd'
]
I could see myself making good use of this particular function, since I've often divided up collections into chunks in the past.
Neither Javascript nor C# appears to have any chunking functions at all, but the popular Javascript library, lodash, does have an equivalent.
array = [1, 2, 3, 4, 5];
let chunks = _.chunk(array, 2);
//chunks === [[1, 2], [3, 4], [5]]
Enum.chunk_every/4
The Enum.chunk_every/4
function is the fancier, more complex version of Enum.chunk_every/2
. It's the same functionality plus some extra options.
Here are the parameters:
- enumerable: the enumerable (same as
Enum.chunk_every/2
) - count: the size of the chunks to create (same as
Enum.chunk_every/2
) - step: defines offset the beginning of the enumerable before starting to create the chunks. This offset increases by the value of
step
every time a chunk is created, having the effect of creating overlapping chunks. If step == count, there is no overlap. If step < count, there is an overlap. If step > count, some elements are skipped. You have to see some examples to make this clear. - leftover: what to do with the final chunk if it's smaller than the chunk size. This parameter can be passed a list of elements to use to fill in the final chunk until it's the same size or you can pass the
:discard
atom to indicate that the final chunk is to be discarded if it is smaller than the chunk size. This last parameter is optional and the default value is an empty list. An empty list means that nothing will be used to fill in that final chunk.
That's a fair amount of complexity in one function, so let's take a look at some examples.
Here are some examples of using the step
parameter in various ways.
#The step parameter is the same as the count parameter. There is no overlap
iex> Enum.chunk_every(1..20, 10, 10)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]
#The step parameter is 7 and the count is 10. That means that there is an overlap of 3 elements
#between the end of one chunk and beginning of the next.
iex> Enum.chunk_every(1..20, 10, 7)
[ ```
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
[15, 16, 17, 18, 19, 20]
]
#The step parameter is 9 and the count is 10. That means that there is an overlap of 1 element
#between the end of one chunk and beginning of the next.
iex> Enum.chunk_every(1..20, 10, 9)
[
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[19, 20]
]
#The step parameter is 9 and the count is 12. That means that 2 elements are skipped
#between the end of one chunk and beginning of the next.
iex> Enum.chunk_every(1..20, 10, 12)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [13, 14, 15, 16, 17, 18, 19, 20]]
Notice that we didn't specify a leftover
parameter at the end, so the final chunks were just left as-is if there weren't enough elements to form a full-size chunk.
Now let's look at some examples of how we use the leftover
parameter. I'll keep the step
parameter the same as the count
parameter to keep things simple.
#I'm chunking up the range 1..20 into chunks of 6 without a leftover parameter. The final chunk
#has only 2 elements because there weren't enough to make a full-sized chunk.
iex> Enum.chunk_every(1..20, 6, 6)
[[1, 2, 3, 4, 5, 6], '\a\b\t\n\v\f', [13, 14, 15, 16, 17, 18], [19, 20]]
#The last chunk is filled in using the leftover list, but there aren't enough elements included
#the leftover list to turn the final chunk into a full chunk
iex> Enum.chunk_every(1..20, 6, 6, ["a", "b"])
[
[1, 2, 3, 4, 5, 6],
'\a\b\t\n\v\f',
[13, 14, 15, 16, 17, 18],
[19, 20, "a", "b"]
]
#The last chunk if filled in using the leftover list. There are more elements in the leftover list
#than needed, so only the needed elements are used to fill in the final chunk
iex> Enum.chunk_every(1..20, 6, 6, ["a", "b", "c", "d", "e", "f", "g"])
[
[1, 2, 3, 4, 5, 6],
'\a\b\t\n\v\f',
[13, 14, 15, 16, 17, 18],
[19, 20, "a", "b", "c", "d"]
]
#I used the :discard option to discard the final chunk because it wasn't a full-sized chunk
iex> Enum.chunk_every(1..20, 6, 6, :discard)
[[1, 2, 3, 4, 5, 6], '\a\b\t\n\v\f', [13, 14, 15, 16, 17, 18]]
This function was significantly more complex than its little brother, and I had to play around with it to get a good feel of what it did. I certainly didn't understand the step
parameter fully until I tried using it.
I don't think I've ever needed a chunk function like this before, but it certainly looks like it would be quite useful in the right circumstances.
Neither C# nor Javascript have an equivalent to this function.
Enum.chunk_while/4
The Enum.chunk_while/4
function is the most generic and most capable chunking function, giving you the most control over chunking. With that great power, however, comes great complexity. You'll only want to use this to create some chunking logic that the other chunking functions don't provide.
You can think of this as the chunking equivalent of reduce. We haven't covered reduce yet, but it's a common concept that is present in many programming languages. It's a more general function that can be used to implement more specific collection functions, which is useful when you want to do something that doesn't already have a more specific implementation. The Enum module has a reduce function, and we'll get to that in detail later. This function is the chunking equivalent of reduce in that it's more general and more powerful, but more complex.
The Enum.chunk_while/4
function takes four parameters:
enumerable
: the enumerable to be chunkedacc
: the accumulator, the accumulator is data containing the current state of the chunking process that is passed from one iteration ofchunk_fun
to the next. It contains any data the developer deems useful, like the previous element, the current chunk in progress, etc.chunk_fun
: This is the function that is called on every element in the enumerable, and receives two parameters: the current element in the enumerable and the accumulator (acc) data. It can choose to emit a chunk by returning{:cont, chunk, acc}
or not emit a chunk by calling{:cont, acc}
.after_fun
: This function is called at the very end after all iteration has been completed. It receives the accumulator (acc) after it has been passed along through all the calls tochunk_fun
. Theafter_fun
function allows the chunk creator to make a decision regarding what to do with the final chunk. It can emit the final chunk as-is, modify it before emitting it, or just discard it entirely.
I'm going to demonstrate this by creating a chunker that chunks all consecutive even and odd numbers into chunks. Only consecutive numbers of the same "evenness" are put into the same chunk. This is code entered into IEx, but I added a lot of comments after-the-fact to explain what is going on. I make use of pattern matching and guard clauses to make this work.
#Create a chunker function
iex> even_number_chunker = fn
#This clause should be called for the first element, assuming that an empty list was passed in as the initial accumulator.
#It will return a list of the chunk so far as the accumulator.
...> (element, []) -> {:cont, [element]}
#This clause is called when there is a previous element in the list and its "evenness" matches the current element
#It adds the current element to the accumulator chunk and returns it
...> (element, chunk = [prev | _]) when rem(prev, 2) == rem(element, 2) -> {:cont, [element | chunk]}
#This clause is called when there is a previous element in the list and its "evenness" does *not* match the current element
#It emits the existing chunk as-is and creates a new chunk with the current element, returning it as the accumulator
...> (element, chunk = [prev | _]) when rem(prev, 2) != rem(element, 2) -> {:cont, chunk, [element]}
...> end
#Function<12.99386804/2 in :erl_eval.expr/5>
#Create the after function that just emits the final chunk as-is.
iex> after_func = fn chunk -> {:cont, chunk, []} end
#Function<6.99386804/1 in :erl_eval.expr/5>
#Now we finally pass these functions and the data to chunk_while.
#The consecutive odds and evens are grouped together
iex> Enum.chunk_while([1, 2, 4, 10, 3, 31, 43, 20], [], even_number_chunker, after_func)
[[1], [10, 4, 2], [43, 31, 3], [20]]
#Change the after_func to suppress the final chunk by not returning it
iex> after_func = fn _ -> {:cont, []} end
#Function<6.99386804/1 in :erl_eval.expr/5>
#The final output is now missing the last chunk because it was discarded
iex> Enum.chunk_while([1, 2, 4, 10, 3, 31, 43, 20], [], even_number_chunker, after_func)
[[1], [10, 4, 2], [43, 31, 3]]
It took me a while to figure out exactly how Enum.chunk_while/4
worked, and playing around with it really helped. The Elixir documentation did not discuss what the point of after_fun
is, so I had to work out what it was for and how I would use it. Hopefully, my example marked up with comments is understandable and will help you use this function. I recommend you take a careful look at the code and try playing around with it yourself. It's complexity may take some time to understand, but it's very flexible.
If you ever use this function in a real Elixir project, please wrap it in another function that accurately describes what it is doing. Its a lot easier to read the code and figure out what its doing if it's encapsulated in a function that describes the operation.
Neither C# nor Javascript have an equivalent to this function.
Enum.concat/1
The Enum.concat/1
function is quite a simple one. It receives a list of enumerables and returns a list with the elements from all those enumerables concatenated together. The elements of any enumerable can be concatenated, not just lists.
Let's look at a demonstration.
iex> Enum.concat([[1, 2], ["a", "b"]])
[1, 2, "a", "b"]
iex> Enum.concat([["a", "b"], %{name: "Bob", species: :human}, 1..10])
["a", "b", {:name, "Bob"}, {:species, :human}, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Javascript arrays have a similar concat
function.
array = [1, 2, 4];
joinedArray = array.concat([8, 12, 22]);
//joinedArray is [1, 2, 4, 8, 12, 22]
The LINQ extension methods in C# also provide an IEnumerable.Concat
method.
List<int> numberList1 = new List() {1, 2, 4};
List<int> numberList2 = new List() {8, 12, 22};
List<int> concattedlist = numberList1.Concat(numberList2);
Enum.concat/2
The Enum.concat/2
function works just like the Enum.concat/1
function except that it receives two enumerables as parameters instead of a single list of enumerables. It concatenates the two enumerables together and returns a list of the results.
iex> Enum.concat([1, 2], ["a", "b"])
[1, 2, "a", "b"]
iex> Enum.concat(["a", "b"], -3..3)
["a", "b", -3, -2, -1, 0, 1, 2, 3]
C# and Javascript also have concatenation functions. See the section on Enum.concat/1
for details.
Enum.count/1
The Enum.count/1
function just returns the number of elements in an enumerable.
iex> numbers = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> bob_map = %{name: "Bob", age: 32}
%{age: 32, name: "Bob"}
iex> Enum.count(numbers)
5
iex> Enum.count(bob_map)
2
iex> Enum.count([])
0
That's all there is too it: really simple.
In Javascript, the array length property is the equivalent.
array = [1, 2, 3, 4, 5];
length = array.length;
C# has a version of the LINQ extension method IEnumerable.Count
that just counts the number of elements in an enumerable.
List<int> numbers = new List<int>() {1, 2, 3, 4, 5};
length = numbers.Count();
Enum.count/2
The Enum.count/2
function works a lot like Enum.count/1
, except that it counts the number of elements in the enumerable that meet certain criteria, where those criteria are determined by the function that is passed as a parameter. The criteria function must return a truthy value (not necessarily a boolean true
) in order for an element to be counted.
Let's see some examples.
#Define the data to work with
iex> numbers = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> bob_map = %{name: "Bob", age: 32}
%{age: 32, name: "Bob"}
#Define functions to tell us if a number is even or odd
iex> is_even = &(rem(&1, 2) == 0)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> is_odd = &(rem(&1, 2) == 1)
#Function<6.99386804/1 in :erl_eval.expr/5>
#Count all the numbers
iex> Enum.count(numbers)
5
#Count the even numbers
iex> Enum.count(numbers, is_even)
2
#Count the odd numbers
iex> Enum.count(numbers, is_odd)
3
#Count the number of key-value pairs in the map where the key is :name
iex> has_name_key = fn {key, _value} -> key == :name end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> Enum.count(bob_map, has_name_key)
1
#Count the number of key-value pairs in the map where the key is :location
iex> has_location_key = fn {key, _value} -> key == :location end
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> Enum.count(bob_map, has_location_key)
0
Javascript does not have an equivalent to this version of count and neither does lodash. That surprised me, as I would have thought this would have been a part of lodash. Lodash does have a _.countBy
function that will return multiple counts, one for every value returned by the parameter function, so that can be used to get the same information.
const isEven = number => number % 2 === 0;
let numbers = [1, 2, 3, 4, 5];
let counts = _.countBy(numbers, isEven);
let evenCount = counts[true];
let oddCount = counts[false];
C# does have a direct equivalent with a version of the LINQ extension method IEnumerable.Count
.
List<int> numbers = new List<int>() {1, 2, 3, 4, 5};
Func<int, bool> isEven = number => number % 2 == 0;
int evenCount = numbers.Count(isEven);
Enum.dedup/1
The Enum.dedup/1
function, which sounds like a funny name to me, returns a list where all consecutive duplicate values have been removed. Duplicate values that are not consecutive will not be removed. If you want to remove all duplicate values, no matter where they are, then shove the data into a MapSet
instead, since sets can never contain duplicate values.
This function may become more clear when we look at some examples.
#No numbers are eliminated because there are no duplicates
iex> Enum.dedup([1, 2, 3, 4, 5])
[1, 2, 3, 4, 5]
#The duplicate numbers are eliminated because they are all consecutive
iex> Enum.dedup([1, 1, 2, 2, 3, 4, 4, 4, 5])
[1, 2, 3, 4, 5]
#All the consecutive duplicates are eliminated, but the non-consecutive duplicates are not
iex> Enum.dedup([1, 1, 2, 2, 3, 4, 4, 4, 5, 4, 4, 3, 3, 2, 1])
[1, 2, 3, 4, 5, 4, 3, 2, 1]
You can think of this function as "squishing" a series of duplicates down into a single value.
I can see this function being useful when you are only interested the first occurrence of consecutive data values. For example, someone pressing a key may generate multiple key_down
events (this is something I made up, and is not part of the Elixir language that I know of), but we are only interested in the first key_down
event and the first of any subsequent such events after the key is released. The Enum.dedup/1
function is perfect for that.
iex> keyboard_events = [:key_down, :key_up, :key_down, :key_down, :key_down, :key_down, :key_up, :key_down, :key_down]
[:key_down, :key_up, :key_down, :key_down, :key_down, :key_down, :key_up,
:key_down, :key_down]
iex> Enum.dedup(keyboard_events)
[:key_down, :key_up, :key_down, :key_up, :key_down]
Neither Javascript nor Lodash have a function that is equivalent. Lodash does have a function that extracts all unique values from an array, but does not take into account whether those values are consecutive or not. C# does not appear to have an equivalent either.
Enum.dedup_by/2
The Enum.dedup_by/2
function works just like Enum.dedup/1
except that it also accepts a parameter function that converts an element to a unique value. This is useful when two different pieces of data are considered the same thing functionally, even though they don't equal each other.
For example, let's repeat the earlier example where we removed duplicate :key_down
events. This time a :key_down
event is in a tuple that also identifies which key was pressed. For this example, we don't care about which key was pressed and any :key_down
event is considered a duplicate.
#Define a list of keyboard events
iex> keyboard_events = [{:key_down, :key_r}, {:key_up, :key_r}, {:key_down, :key_a}, {:key_down, :key_s}, {:key_down, :key_a}, {:key_down, :key_d}, {:key_up, :key_a}, {:key_down, :key_a}, {:key_down, :key_r}]
[
key_down: :key_r,
key_up: :key_r,
key_down: :key_a,
key_down: :key_s,
key_down: :key_a,
key_down: :key_d,
key_up: :key_a,
key_down: :key_a,
key_down: :key_r
]
#Run the events through Enum.dedup/1. None of the events are removed because there are no consecutive duplicates.
iex> Enum.dedup(keyboard_events)
[
key_down: :key_r,
key_up: :key_r,
key_down: :key_a,
key_down: :key_s,
key_down: :key_a,
key_down: :key_d,
key_up: :key_a,
key_down: :key_a,
key_down: :key_r
]
#Run the same events through Enum.dedup_by/2, where a tuple is considered a duplicate if it has the same event.
#This time, events are removed.
iex> Enum.dedup_by(keyboard_events, fn {event, _key} -> event end)
[
key_down: :key_r,
key_up: :key_r,
key_down: :key_a,
key_up: :key_a,
key_down: :key_a
]
There is nothing even remotely similar to this function in Javascript or C#.
Enum.drop/2
The Enum.drop/2
function returns a list with elements removed from the beginning or end of the enumerable. The first parameter is the enumerable and the second parameter indicates how many elements are to be dropped. Whether the elements are dropped from the beginning or end is determined by whether the second parameter is positive or negative. A positive number means that elements are to be dropped from the beginning and a negative number means that elements are to be dropped from the end.
iex> numbers = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> Enum.drop(numbers, 2)
[3, 4, 5]
iex> Enum.drop(numbers, 4)
[5]
iex> Enum.drop(numbers, 5)
[]
iex> Enum.drop(numbers, 6)
[]
iex> Enum.drop(numbers, -1)
[1, 2, 3, 4]
iex> Enum.drop(numbers, -3)
[1, 2]
iex> Enum.drop(numbers, -7)
[]
iex> Enum.drop(numbers, 0)
[1, 2, 3, 4, 5]
Javascript does not have an equivalent to this function in the native array functionality, although the array slice()
or splice()
functions could be used to achieve the same effect. Lodash, however, does have an equivalent function.
numbers = [1, 2, 3, 4, 5];
//Drop the 1 and 2 from the beginning of the array
let fewerNumbers = numbers.drop(2);
//Drop the 4 and 5 from the end of the array
fewerNumbers = numbers.dropRight(2);
The LINQ extension functions IEnumerable.Skip()
and IEnumerable.SkipLast()
offer equivalent functionality in C#.
List<int> numbers = new List<int>() {1, 2, 3, 4, 5};
//Drop the 1 and 2 from the beginning of the list
var fewerNumbers = numbers.Skip(2);
//Drop the 4 and 5 from the end of the array
fewerNumbers = numbers.SkipLast(2);
Enum.drop_every/2
The Enum.drop_every/2
function drops every Nth element from an enumerable starting with the first element, where N is an integer that is passed in as a parameter.
iex> numbers = 1..20
1..20
iex> Enum.drop_every(numbers, 2)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
iex> Enum.drop_every(numbers, 3)
[2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20]
iex> Enum.drop_every(numbers, -2)
** (FunctionClauseError) no function clause matching in Enum.drop_every/2
The following arguments were given to Enum.drop_every/2:
# 1
1..20
# 2
-2
(elixir) lib/enum.ex:721: Enum.drop_every/2
I was confused when I first used this function because I had forgotten that it starts with the first element. The first element is always dropped no matter what the second parameter is. Negative numbers are not allowed to be passed in for the second parameter.
Neither C# nor Javascript have equivalent functions, although you could achieve the same effect in other ways, such as using a reduce function.
Enum.drop_while/2
The Enum.drop_while/2
function is similar to Enum.drop/2
except that accepts a function as the second parameter instead of an integer. Enum.drop_while/2
will keeps dropping elements starting at the beginning of the enumerable as long as the parameter function returns a truthy value. Once the parameter function returns a falsey value, no more elements will be dropped, even if later elements would cause it to return a truthy value.
iex> is_even = &(rem(&1, 2) == 0)
#Function<6.99386804/1 in :erl_eval.expr/5>
iex> is_odd = &(rem(&1, 2) == 1)
#Function<6.99386804/1 in :erl_eval.expr/5>
#Drop elements as long as the numbers are even. The even numbers after the odd number will not be dropped.
iex> Enum.drop_while([2, 2, 4, 10, 12, 13, 4, 2, 8], is_even)
[13, 4, 2, 8]
#Drop elements as long as the numbers are odd. The first element is an even number,
#so no numbers will be dropped
iex> Enum.drop_while([2, 2, 4, 10, 12, 13, 4, 2, 8], is_odd)
[2, 2, 4, 10, 12, 13, 4, 2, 8]
#Drop elements as long as the numbers are odd.
#This time there are odd numbers at the start of the list
iex> Enum.drop_while([1, 3, 5, 10, 12, 13, 4, 2, 8], is_odd)
[10, 12, 13, 4, 2, 8]
Javascript does not have a native equivalent to this function, but Lodash does.
const isEven = number => number % 2 === 0;
let numbers = [2, 4, 8, 9, 10, 12];
let fewerNumbers = _.dropWhile(numbers, isEven);
C# also offers an equivalent in the LINQ extension method IEnumerable.SkipWhile()
.
List<int> numbers = new List<int>() {2, 4, 8, 9, 10, 12};
Func<int, bool> isEven = number => number % 2 == 0;
var fewerNumbers = numbers.SkipWhile(isEven);
Enum.each/2
The Enum.each/2
function accepts an enumerable and a function as parameters and applies the function to every element in the list. Enum.each/2
does not actually modify the enumerable in any way or calculate anything based on the enumerable. It just calls the function for every element and then returns :ok
no matter what happened.
What's the point of this function? This is what you use to perform side effects. Although side effects are generally something to be avoided when possible, they are unavoidable. You have to eventually interact with the user, the file system, the network, or something else that is external to the application. Enum.each/2
is meant to be used for side effects. You can write a function that does something side-effecty for every element in enumerable, such as writing it to the screen or writing it to a file.
Let's look at an example of writing the contents of an enumerable to the screen, which is really the only side effect I currently know how to perform in Elixir. The IO.puts/1
will take whatever you pass to it and display it on the screen.
iex> Enum.each(1..22, &(IO.puts(&1)))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
:ok
iex> Enum.each(["Bob", "Rob", "Lob"], &(IO.puts(&1)))
Bob
Rob
Lob
:ok
What happens here is that each value in the enumerables are output to the screen and then Enum.each/2
returns :ok
, which is displayed by IEx after the output, since IEx always displays what the function returned.
That's all there is to it. When you want to produce some side effects for each element of an enumerable, this is how you achieve that.
Javascript has an equivalent for arrays: forEach(). This function doesn't do anything but call a function for every element in an array
let numbers = [2, 4, 8, 9, 10, 12];
numbers.forEach(number => console.log(number));
The C# equivalent cannot be found in the IEnumerable interface, but rather in the ListList<T>.ForEach()
. If you want to apply ForEach()
to an IEnumerable, convert the IEnumerableToList()
method.
List<int> numbers = new List<int>() {2, 4, 8, 9, 10, 12};
numbers.ForEach(number => Console.WriteLine(number));
Enum.empty?/1
The Enum.empty?/1
function determines if an enumerable is empty. An empty enumerable is one that has no elements.
iex> Enum.empty?([1, 2, 3])
false
iex> Enum.empty?([1])
false
iex> Enum.empty?([])
true
iex> Enum.empty?(%{})
true
It doesn't get simpler than that.
Those are all the Enum module functions I'm going to cover today. I know I learned a lot, and I hope you did too. We've only covered about 25% of the functions in the Enum module at this point, so we have a lot more functions to cover. I'm going to switch to another topic for the next post and come back to more Enum functions later so that things don't get too tedious.