Finding duplicate characters in a string

I wrote a function to find if a character is a duplicate in a string.
The way it is defined, the function when called isDuplicate 'b' "abbey" False it will return True.

How can it refactored to eliminate Bool as one of the arguments/parameters?
Defining a helper function will get the job done, but is that the only solution?

isDuplicate : Char -> String -> Bool -> Bool
isDuplicate xs word_ foundAlready =
    case ( String.uncons word_, foundAlready ) of
        ( Nothing, _ ) ->
            False

        ( Just ( first, rest ), _ ) ->
            let 
                foundNow = Char.toLower first == xs
            in 
                if foundNow && foundAlready 
                    then True
                    else isDuplicate xs rest (foundNow || foundAlready)

Yes the usual pattern is to rename the function you have already to isDuplicateHelp and then make another function isDuplicate to call it with the initial value. External code calls the new isDuplicate, which doesn’t have the Bool argument.

Edit: so my point is that this is totally idiomatic and normal, I wouldn’t see any reason to look for another way.

1 Like

But this returns True for isDuplicate 'b' "abey"…am I missing something?

  1. The function has to be case insensitive.
  2. And it has to return True only when there are duplicates.
    For e.g. isDuplicate 'b' "AbBey" → True

Meanwhile, I decided to move the default argument to the first, like this:

isDuplicate_ : Bool -> Char -> String -> Bool
isDuplicate_ foundAlready xs word_ =

isDuplicate = isDuplicate_ False

and in the calling site, I used isDuplicate 'b' "Abbey" == True.
Thanks,

And thanks to @jan2100, I realized my cases can be simplified after all.
So now I have:

isDuplicate_ : Bool -> Char -> String -> Bool
isDuplicate_ foundAlready xs word_ =
    case ( String.uncons word_) of
        ( Nothing ) ->
            False

        ( Just ( first, rest ) ) ->
            let 
                foundNow = Char.toLower first == xs
            in 
                if foundNow && foundAlready 
                    then True
                    else isDuplicate_ (foundNow || foundAlready) xs rest 

Thanks,

1 Like

You can also do this using List.foldl. Here’s how I would implement such a function:

peakCount max cur match =
    if cur == max then max
    else if match then cur + 1
    else 0

isMulti: Int -> Char -> String -> Bool
isMulti count toFind findIn =
    (String.toList findIn 
    |> List.foldl (\ch cur -> peakCount count cur (toFind == Char.toLower ch)) 0) == count

isDuplicate: Char -> String -> Bool
isDuplicate = isMulti 2

The peakCount function works by counting “Trues”: it will add one for every true until it hits max at which point it will always return max. If it get a false before getting to max, it resets and returns 0. The isMulti function looks for the specified character in the string, using List.foldl and the peakCount function to count that character in sequence. isDuplicate is just a specialization of isMulti. The nice thing about isMulti is that it’s no more difficult to find a sequence of 10 letters than it is to find a duplicate. The call would be:

isMulti 10 'b' "abbbbbbbbbbey"
1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.