1
votes

I have a function with a recursive function within, to count the letters in words so that "hello" returns [|("h",1);("e",1);("l",2);("o",1);|]. My code is as follows:

let letterCheck (a:string) =
    let aList = a |> Seq.toList;

    let rec _check (charList:char list) (result:(char * int) array) = 
        match charList with
        | head :: tail -> 
            if Array.exists (fun (c,i) -> c = head) result then
                let index = Array.findIndex (fun (c,i) -> if c = head then true else false) result;
                Array.set result index (result.[index].[1]+1);
            else
                Array.append result [|(head,1)|];
            _check tail result;
        | [] -> result;
    _check aList [||];

but even though I type annotated result it errors on the Array.set line with an error:

Program.fs(73,45): error FS0752: The operator 'expr.[idx]' has been used on an object of indeterminate type based on information prior to this program point. Consider adding further type constraints.

How can I fix this error or am I doing something fundamentally wrong?

1

1 Answers

3
votes

am I doing something fundamentally wrong?

This code looks like it's expecting to work with mutable values:

        if Array.exists (fun (c,i) -> c = head) result then
            let index = Array.findIndex (fun (c,i) -> if c = head then true else false) result;
            Array.set result index (result.[index].[1]+1);
        else
            // new array value will be discarded
            Array.append result [|(head,1)|];
        _check tail result; // result will be unchanged

Array.append returns a modified array value; it doesn't mutate the input array's value. result.[index].[1]+1 looks like you're trying to get the second item from a tuple using array index syntax (use snd instead), but even if it was fixed this function would still have problems because it'll always recurse with the same/unchanged value of result.

Also, you don't need to end every line with a semicolon in F#.

Here's a version of your function that works with minimal changes and without extra type annotations:

let letterCheck (a:string) =
    let aList = a |> Seq.toList
    let rec _check charList result = 
        match charList with
        | head :: tail -> 
            if Array.exists (fun (c,i) -> c = head) result then
                let index = Array.findIndex (fun (c,i) -> c = head) result
                Array.set result index (head, (snd(result.[index])+1))
                _check tail result
            else
                _check tail (Array.append result [|(head,1)|])
        | [] -> result
    _check aList [||]

> letterCheck "hello";;
val it : (char * int) array = [|('h', 1); ('e', 1); ('l', 2); ('o', 1)|]

Or to take all the fun out of it:

"hello" |> Seq.countBy id