It works with a for loop and mutable variable:
let addLnNum filename =
use outFile = new StreamWriter(@"out.txt")
let mutable count = 1
for line in File.ReadLines(filename) do
let newLine = addPre (count.ToString()) line
outFile.WriteLine newLine
count <- count + 1
But it is very "non-functional" so I'm curious what is the proper way to do this? I figured how to append the index number to a list of strings:
let rec addIndex (startInd:int) l=
match l with
|x::xs -> startInd.ToString()+x :: (addIndex (startInd+1) xs)
|[] -> []
But it won't apply to File.ReadLines:
let addLnNum2 filename =
use outFile = new StreamWriter(@"out.txt")
File.ReadLines(filename)
|> addIndex 1
|> ignore
//Error 1 Type mismatch. Expecting a Collections.Generic.IEnumerable<string> -> 'a
//but given a string list -> string list
Is reading the whole file into memory as a list the only way to do this? Is there something like seq.count so it can be done similar to the following?
let addLnNum3 filename =
use outFile = new StreamWriter(@"out.txt")
File.ReadLines(filename)
|> Seq.map (fun s -> Seq.count + s) //no such thing as Seq.count
|> Seq.iter outFile.WriteLine
|> ignore
For some functions in the Seq
module (same with List
, ...) you'll find versions with an appended i
- for example for Seq.map
you'll find Seq.mapi
and this is what you are looking for - in addition to the value from your collection you get (as the first parameter) the index too:
let addLnNums filename =
use outFile = new System.IO.StreamWriter (@"out.txt")
System.IO.File.ReadLines filename
|> Seq.mapi (sprintf "%d: %s")
|> Seq.iter outFile.WriteLine
also note that you don't need ignore
as Seq.iter
already returns () : unit
If we would not have this then the functional way would be to use Zip
like this:
let addLnNum filename =
use outFile = new System.IO.StreamWriter (@"out.txt")
Seq.zip (Seq.initInfinite id) (System.IO.File.ReadLines filename)
|> Seq.map (fun (index, line) -> sprintf "%d: %s" index line)
|> Seq.iter outFile.WriteLine
which (aside from uncurrying the function to map
) does basically the same
For lists you obviously don't have a List.initInfinte
, so just go with Seq
- also Seq.zip
and List.zip
have different behaviors concerning collections with different item-count - Seq.zip
stops when one collection runs try but List.zip
wants both lists to be of the same size and will throw an exception if not
Your addIndex
function is actually correct - but it works on F# lists. The ReadLine
function returns IEnumerable<T>
rather than an F# list (which makes sense, because it is a .NET library). You can fix the addLnNum2
function by adding List.ofSeq
(to convert IEnumerable<T>
to a list):
let addLnNum2 filename =
let added =
File.ReadLines(filename)
|> List.ofSeq
|> addIndex 1
File.WriteAllLines("out.txt", added)
Using Seq.mapi
or Seq.zip
as mentioned in Carsten's answer is certainly simpler than implementing your own recursive function, but you did get the recursion and pattern matching right :-).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With