DEV Community

Anton
Anton

Posted on

No duplicates challenge in Elm

There is a 99 Elm problems book (originally was for Prolog).

Write a function to remove consecutive duplicates of list elements.

noDupes [1, 1, 2, 2, 3, 3, 3, 4, 5, 4, 4, 4, 4]
    == [1, 2, 3, 4, 5, 4]

I'm doing something wrong here. Am I on the track or completely off track?

noDupes : List a -> List a
noDupes list =
    case list of
        [] -> []
        x::y::xs ->
            if x == y then
                x :: noDupes xs
            else
                x::y::xs
        x::xs ->
            if x == xs then
                x :: xs
            else
                x

The compiler already gave me a heads up that I'm comparing an element to a list:

17|             if x == xs then
                   ^^^^^^^
The left side of (==) is:

    a

But the right side is:

    List a

Different types can never be equal though! Which side is messed up?

Hint: Did you forget to add [] around it?

Interesting I just wrapped the x in [x]. But I now have the second the last case to deal with.

Top comments (11)

Collapse
 
avalander profile image
Avalander • Edited

You're forgetting to handle the case of a list with a single element.

Also, in the case x :: y :: xs, when x is not equal to y you should return x :: (noDupes y :: xs) to make sure the rest of the list gets deduplicated.

I don't think you need to handle the case x :: xs at all, but you might need to handle the case of a list with two elements.

Collapse
 
antonrich profile image
Anton

How do I pattern match on the list with two elements?

noDupes : List a -> List a
noDupes list =
    case list of
        [] -> []
        x::y ->
            if x == y then
                x :: list
            else
                x :: y :: list
        x::y::xs ->
            if x == y then
                x :: noDupes xs
            else
                x :: (noDupes y::xs)

And in the case of two element how do you add that to the rest of the list? As you can see I use "list" in the second case. I don't think it's correct.

Collapse
 
antonrich profile image
Anton

Now I have some missing possibilities:

This `case` does not have branches for all possibilities:

 9|>    case list of
10|>        [] -> []
11|>        x::y::[]->
12|>            if x == y then
13|>                x :: list
14|>            else
15|>                x :: y :: list
16|>        x::y::xs ->
17|>            if x == y then
18|>                x :: noDupes xs
19|>            else
20|>                x :: (noDupes (y::xs))

Missing possibilities include:

    [_]

I would have to crash if I saw one of those. Add branches for them!

Hint: If you want to write the code for each branch later, use `Debug.todo` as a
placeholder. Read <https://elm-lang.org/0.19.0/missing-patterns> for more
guidance on this workflow.

Collapse
 
1hko profile image
1hko • Edited

You cases check for 1) an empty list, 2) a list with exactly two elements, and 3) a list with 2 elements and a tail. You forgot to check for the singleton list (a list with exactly one element) which is what the compiler is warning you about. Note, checking for a list with exactly two elements is not effective for this particular program.

Thread Thread
 
antonrich profile image
Anton

I'm taking a note of "checking for a list with exactly two elements is not effective for this particular program."

Collapse
 
antonrich profile image
Anton

Interesting this code

noDupes : List a -> List a
noDupes list =
    case list of
        [] -> []

        [x] -> [x]

        x::y::xs ->
            if x == y then
                x :: noDupes xs
            else
                [x]

when applied to

noDupes [ 2, 2, 2, 1, 1, 1 ]

returns

[2, 2]
Collapse
 
1hko profile image
1hko • Edited

the x::y::xs -> case does not recur in the else branch, so an input of [1,2,2,2,3,3,3] will simply return [1] when the first two elements of the input do not match - leaving the [2,2,2,3,3,3] portion completely unprocessed.

Collapse
 
1hko profile image
1hko • Edited

Consider this implementation

noDupes : List a -> List a
noDupes list = 
    case list of
        [] ->
            []

        [ a ] ->
            [ a ]

        a :: b :: more ->
            if a == b then
                noDupes (a :: more)
            else
                a :: noDupes (b :: more)

When we test it

noDupes [1, 1, 2, 2, 3, 3, 3, 4, 5, 4, 4, 4, 4]
-- [ 1, 2, 3, 4, 5, 4 ] : List number

noDupes ["a", "b", "b", "c", "c", "c", "d", "d", "c" ]
-- [ "a", "b", "c", "d", "c" ] : List String
Collapse
 
antonrich profile image
Anton

I had this noDupes (a :: more) in the else branch when experimented with the code but didn't consider putting that in the first branch.

Thank you. I appreciate your help.

Collapse
 
josephthecoder profile image
Joseph Stevens

Haha,I like to think I know FP well, but, when I try to do recursive functions like this, my whole brain freezes up 😅

Collapse
 
1hko profile image
1hko • Edited

It's all about mastering inductive reasoning.

someFunc n = 
    if n == 0 then
        -- do something when n IS zero (base case)
    else
        -- do something where n is (by inductive reasoning) NOT zero

Or

someFunc list =
    if List.isEmpty list then
        -- handle the empty list (base case)
    else
        -- the list (by inductive reasoning) has at least one element

Pattern matching streamlines this for us by allowing us to express the base case and any induction steps using a nice syntax

someFunc n =
    case n of
        0 ->  -- base case
        _ -> -- induction step

Or

someFunc list =
    case list of
        [] -> -- base case
        first :: more -> -- induction step