[Beginner] How do I compare characters correctly?

As you can see from the posted code I am quite new to Elixir but I figured this is the sort of thing people always have a hard time getting used to in a new language so it could be valuable to the community if someone would (over)explain what is happening here.

iex(4)> 'a' == 'b'
false
iex(5)> 'a' == 'a'    
true
iex(6)> Enum.count('abcaa', fn(x) -> x == 'a' end)
0
iex(20)> Enum.count('abcaa', fn(x) -> x == to_charlist 'a' end)
0
iex(21)> Enum.count('abcaa', fn(x) -> to_charlist 'a' == x end)
5
iex(22)> Enum.count('abcaa', fn(x) -> 'a' == x end)            
0
iex(23)> Enum.count('abcaa', fn(x) -> 97 == x end) 
3
iex(24)> Enum.count('abcaa', fn(x) -> x == 97 end) 
3
iex(25)> Enum.count('abcaa', fn(x) -> x == byte_size 'a' end)
** (ArgumentError) argument error
             :erlang.byte_size('a')
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (stdlib) erl_eval.erl:470: :erl_eval.expr/5
    (elixir) lib/enum.ex:492: anonymous fn/3 in Enum.count/2
    (elixir) lib/enum.ex:2843: Enumerable.List.reduce/3
    (elixir) lib/enum.ex:491: Enum.count/2
iex(25)> strand = 'a'                                        
'a'
iex(26)> Enum.count('abcaa', fn(x) -> x == ^strand end)      
** (CompileError) iex:26: cannot use ^strand outside of match clauses
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    (elixir) src/elixir_fn.erl:33: anonymous fn/3 in :elixir_fn.expand/3
iex(26)> Enum.count('abcaa', fn(x) -> x = ^strand end) 
** (CompileError) iex:26: cannot use ^strand outside of match clauses
    (elixir) src/elixir_fn.erl:33: anonymous fn/3 in :elixir_fn.expand/3
    (stdlib) lists.erl:1239: :lists.map/2
    (elixir) src/elixir_fn.erl:36: :elixir_fn.expand/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
iex(26)> Enum.count('abcaa', fn(x) -> ^strand = x end)
** (MatchError) no match of right hand side value: 97

As may be apparent to some of you I am solving Exercism problem number 2 in the Elixir track so I will get suggestions on how to improve my code there but I’d also like to upgrade my mental model of what is happening when I use Enum with anon functions and know how to compare [char] or char properly. Hence this topic.

Thank you!

Edit: I forgot to put the solution I ended up using:

iex(30)> Enum.count('abcaa', fn(x) -> x == List.first 'a' end)
3

Edit2: I can’t mark more than one post as the solution but both answers have value!

Strings, chars, and charslist can be a little bit confusing at the first time. But you can get some infos about terms in iex.

iex(19)> i 'a'
Term
 'a'
Data type
 List
Description
 This is a list of integers that is printed as a sequence of characters
 delimited by single quotes because all the integers in it represent valid
 ASCII characters. Conventionally, such lists of integers are referred to as
 "charlists" (more precisely, a charlist is a list of Unicode codepoints,
 and ASCII is a subset of Unicode).
Raw representation
 [97]
...

You can see that ‘a’ is not really a char. It is a List ‘[97]’ and 97 is the codepoint for ‘a’. Your can get a codepoint for a with ?a in iex.

iex(20)> ?a
97
iex(23)> ?a == hd 'abc'
true

I hope that helps a little bit.

How is your name at exercism?

2 Likes

50574E is my exercism as well. Ah I should have figured out what the question marks mean =_=

Alright thanks :slight_smile:

Sure. :slight_smile:
In entire detail. ^.^

This should be as expected, you are comparing a list of characters with another list of characters that does not match, so you get false.

This should also be as expected, you are comparing a list of characters with another list of characters that does match, so you get true. ^.^

This is also expected, you are comparing each character in a list of characters ('abcaa' == [?a, ?b, ?c, ?a, ?a]) with a list of a character ('a' == [?a]), since a singular character can never equal a list of anything, it always returns false, thus count is 0 (‘a’ != ?a). :slight_smile:

So fixing this up so it looks correct (I like explicit parenthesis):

So this is also expected, calling to_charlist/1 on something that is already a character list ([?a] in this case) just returns the same thing, just like how calling to_string/1 on a string just returns the original string. :slight_smile:

Also entirely expected, however your lack of parenthesis is probably hiding something, so lets fix that:

So calling to_charlist('a' == x) is always going to return 'false' since 'a' == x returns false and calling to_charlist(false) returns the character list version of the boolean false, thus 'false'. :slight_smile:

Again, comparing a list of characters ('a' == [?a]) to a single character (whatever is in the binding x) will always fail, so entirely expected.

Indeed, ?a == 97, so this returns true when x is ?a. :slight_smile:

Indeed, you can only call byte_size on a binary/string, and 'a' is a list of characters, not a binary string, thus boom. ^.^

Yep yep, strand is now binding to 'a'/[?a]/[97]. :smiley:

A pin operator can only go on the ‘left’ side of a match, not a right (where it makes no sense), thus you need to put it on the left side (where it will ‘work’ but always return false since you are comparing a list of characters to a single character). :slight_smile:

Why not just do this?

5 Likes

I didn’t know about that questionmark codepoint trick and had spent a while looking through docs and reading anything that seemed relevant. As soon as you mentioned it everything became obvious :þ

Thank you so much, hopefully this has some google value to future beginners.

2 Likes

That is what is hoped for. ^.^

EDIT: I did sneak in a few other nuggets of info in my description, like proper parenthesis usage and such, do read over it. ^.^