Is there a better way to replace only the first element in a sequence?

I’ve got a sequence of chars where I want to replace only the first char with the upper-case version of that first char.

The sequence may have zero, one or more elements. If there is more than one element then the first element should be converted to upper-case while the rest should remain as they are. If there is only one element then that single char should always be converted to upper-case and returned as a single-element sequence. If there are no elements then the function should just evaluate to either the original sequence or a new empty sequence.

I don’t want the ‘sequence-ness’ of the collection to be ‘broken’ by converting to something else and then back to a sequence.

I’m not hugely bothered about how quickly, or otherwise, it works at the moment.

I’m not concerned about how the case conversion is done, only the replacement of the first sequence element is important.

I came up with a few different versions but none of them seem particularly ‘nice’ to me, except maybe titleCase6, but even that might not be the best way:

open System 

let titleCase1 (chars : char seq) = 
    if chars |> Seq.isEmpty then 
        chars 
    else 
        let head = chars |> Seq.head 
        let tail = chars |> Seq.tail 
        tail |> Seq.insertAt 0 (head |> Char.ToUpper) 

let titleCase2 chars = 
    if chars |> Seq.isEmpty then 
        chars 
    else 
        let head = chars |> Seq.head 
        chars |> Seq.updateAt 0 (head |> Char.ToUpper) 

let titleCase3 chars = 
    chars 
    |> Seq.tryHead 
    |> Option.map Char.ToUpper 
    |> Option.map (fun h -> chars |> Seq.tail |> Seq.insertAt 0 h) 
    |> Option.defaultValue chars 

let titleCase4 chars = 
    chars 
    |> Seq.tryHead 
    |> Option.map Char.ToUpper 
    |> Option.map (fun h -> chars |> Seq.tail |> Seq.append (seq { h })) 
    |> Option.defaultValue chars 

let titleCase5 chars = 
    chars 
    |> Seq.tryHead 
    |> Option.map Char.ToUpper 
    |> Option.map (fun h -> seq { h ; yield! (chars |> Seq.tail) }) 
    |> Option.defaultValue chars 

let titleCase6 chars = 
    chars 
    |> Seq.mapi (fun i c -> if i = 0 then (c |> Char.ToUpper) else c )

let functions = [ titleCase1 ; titleCase2 ; titleCase3 ;titleCase4 ; titleCase5 ; titleCase6 ] 

let sequences = [ seq { 'a'..'e' } ; seq { 'a' ; 'B' ; 'c' ; 'D' ; 'e' }; seq { 'a' } ; seq { 'A' } ; Seq.empty ] 

let ofCharArray (chars : char array) = System.String(chars)

let results = 
    (functions, sequences) 
    ||> List.allPairs  
    |> List.map (fun (f, sequence) -> sequence |> f |> Seq.toArray |> ofCharArray)

Is there a better way to do this?

How ‘nice’ a function looks is certainly in the eyes of the beholder, but to me, a nice function fulfills its purpose while being both clear and concise. :slight_smile:

I would argue that the example below is both clear (easy to follow) and concise (body of only 3 rows).

let titleCase7 chars =
    match Seq.tryHead chars with
    | Some c -> Seq.updateAt 0 (Char.ToUpper c) chars
    | None -> chars

Although I would not argue that it is necessarily “better” than any of the versions you have already provided! They do the same thing, after all. :smiley:

1 Like

Thanks for the extra example.

I was hoping that I could use something like:

let chars = [ 'a'..'e' ] 

let titleCaseList = function 
    | [] -> [] 
    | [single] -> [ single |> Char.ToUpper ]
    | head::tail -> (head |> Char.ToUpper) :: tail

…but for a sequence rather than a list, which would have been ‘nicer’ (to me anyway), but apparently cons won’t work with sequences, which is a shame but there’s probably a very good technical reason for it that I probably won’t understand.

1 Like

Totally agree that the list pattern matching alternative is nicest! Although the single element list match can be skipped, since in that case, tail will be the empty list :smiley: :

let titleCaseList = function 
    | [] -> [] 
    | head::tail -> (head |> Char.ToUpper) :: tail

The reason for cons to only be available for lists has to do with the underlying data structure (at least partly). Lists in F# are implemented as linked lists, which mean it makes sense to work with the head of the list, since the rest of the list will be left unaffected.
Arrays are continuous blocks of memory and sequences are lazily evaluated, potentially infinite collections. In these cases, the cons operator does not make as much sense. :slight_smile:

1 Like

How about:

let UpperStartSeq = Seq.mapi (fun i c → if i = 0 then Char.ToUpper c else c)

Thanks.

Would that be considered to be a ‘point-free’ version of my titleCase6 function?

Or have I misunderstood what ‘point-free’ is?

It is more ‘point-free’ than the original titleCase6 but not completely :slight_smile:
In a ‘point-free’ function, none of the parameters are explicitly written out. Instead, only function composition is used.
In the example

let UpperStartSeq = Seq.mapi (fun i c → if i = 0 then Char.ToUpper c else c)

The sequence parameter is indeed left out, but in the inner function, both the i and c parameter are written out explicitly. In a truly point free function, we would not create an anonymous function with explicitly named parameters. (I don’t know how one would compose such a ‘point-free’ function in this particular case :stuck_out_tongue: )

Worth to mention is that functions written in a purely point-free manner are often harder to read and debug, see Microsofts coding convensions for F# :smiley:

Thank you for discussing this.
Iceland
Best Time for Whale Watching in Iceland

1 Like

Thanks for the extra information Tbobbe.

I’ve dabbled with that kind of function before and found it difficult to understand when I came back to look at what I’d previously understood.

I think I’ll steer clear of that sort of thing unless it happens to be good for something specific.

1 Like