Smartest way to convert a list into a string [closed] - f#

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
If I have a list:
let list123 = [ 1; 2; 3 ]
what would be the most F#-approach to converting it into a string "[ 1; 2; 3 ]"? All questions I have found regarding list to string conversion, revolves around only taking the list elements, but I want the entire list expression.

As F# is a .NET language I don't see why System.String.Join is less F#-ish than anything else:
module List =
let toString (lst: 'a list) =
match lst with
| [] -> "[]"
| _ -> sprintf "[ %s ]" (String.Join("; ", lst))
But if you insist doing it by your self in "real" F# it could be something like:
module List =
let join lst =
match lst with
| [] -> ""
| [ h ] -> h |> string
| h::t ->
sprintf "%s" (t |> List.fold (fun res a -> sprintf "%s; %A" res a) (h |> string))
let formatAsString prefix postfix lst =
match lst |> join with
| "" -> sprintf "%s%s" prefix postfix
| str -> sprintf "%s %s %s" prefix str postfix
let toString lst = formatAsString "[" "]" lst
let empty = []
let one = [ 1 ]
let four = [ 1; 2; 3; 4 ]
printfn "%s" (empty |> List.toString)
printfn "%s" (one |> List.toString)
printfn "%s" (four |> List.toString)
outputs:
[]
[ 1 ]
[ 1; 2; 3; 4 ]

Not quite sure what you're asking, but here goes:
let s = list123 |> Seq.map string |> String.concat "; " |> sprintf "[ %s ]"
EDIT
It has the drawback that an empty list becomes [ ] rather than [], and this can be fixed e.g. by using a match similar to what FRocha does in his answer.

Use the string function:
string list123 //val it : string = "[1; 2; 3]"
EDIT
Addressing the good point made by Bent Tranberg.
let inline listToString l =
match l with
| [] -> "[]"
| l ->
(l |> List.head |> string |> (+) "[",List.tail l)
||> List.fold (fun acc elem -> acc + "; " + string elem) |> (+) <| "]"

Related

Print list of document in reverse line by line

So I am working on this assignment where I have to reverse the document but it does not work as intended. If I run my code on a file like this
hello
world
I will get
dlrow
olleh
But what I really need is the following:
world
hello
So I need to reverse it line by line but not for every letter. I dont know how to tackle this problem so I need someone who can push me in the right direction. I think that I need to do something with my "q"
My Code is:
let readFile (filename : string) : string option =
try
let reader = System.IO.File.OpenText filename
Some (reader.ReadToEnd ())
with _ -> None
let tac (filenames : string list) : string option =
try
let q = List.map readFile filenames |> List.choose id |> String.concat ", " |> Seq.toList |> List.rev
(Some (System.String.Concat(Array.ofList (q))))
with _ -> None
As Daniel said, ...divide and conquer
Break it into smaller steps and solve one at a time
When you know what it takes to solve it,
you can then choose to improve it, 'refactor' it
to optimize
or add error handling
or specialize/rewrite a function
...etc
open System
open System.IO
// V1
File.ReadLines "./my.txt"
|> Seq.toList
|> List.rev
|> List.map (fun x -> sprintf "%s%s" x Environment.NewLine)
|> List.fold (+) ""
|> printf "%A\n"
... how do you reverse it without a for loop ?
open System
open System.IO
// v2
let rec reverse =
function
| [] -> []
| h::t -> (reverse t)#[h]
let join = List.fold (+) ""
let newLine x = sprintf "%s%s" x Environment.NewLine
// look , I'm a 'compositor' ! :) ♪
let chain = reverse >> List.map newLine >> join
File.ReadLines "./my.txt"
|> Seq.toList
|> chain
|> printf "%A\n"
... what if it doesn't exists ?
// V3
open System
open System.IO
let rec reverse =
function
| [] -> []
| h::t -> (reverse t)#[h]
let join = List.fold (+) ""
let newLine x = sprintf "%s%s" x Environment.NewLine
// 👣
let chain = reverse >> List.map newLine >> join
// NEW
let readLines path =
if File.Exists path then
// 🐛: What if its Binary or something line that ?
File.ReadLines path |> Seq.toList
else
[]
readLines "./my.txt"
|> chain
|> printf "%A\n"
[Edit]
Optional string version
// v4
open System
open System.IO
// New
let inline maybe test f x =
if test x then x |> f |> Some else None
// New
let inline ifSome f = maybe Option.isSome f
// New
let inline ifNone f = maybe Option.isNone f
// New
let ifExists = maybe File.Exists
let rec reverse =
function
| [] -> []
| h :: t -> (reverse t) # [ h ]
let join = List.fold (+) ""
let newLine x = sprintf "%s%s" x Environment.NewLine
let chain =
reverse
>> List.map newLine
>> join
let readLines path =
(File.ReadLines path)
|> Seq.toList
// usage '$ fsi reverse.fsx "my.txt"'
fsi.CommandLineArgs
|> Seq.skip 1
|> Seq.head // expecting "my.txt"
|> ifExists readLines
|> Option.map chain
|> ifSome (fun x -> printf "%A\n" x)
|> ifNone (fun x -> printf "None! \n")

Slice/Group a sequence of equal chars in F#

I need to extract the sequence of equal chars in a text.
For example:
The string "aaaBbbcccccccDaBBBzcc11211" should be converted to a list of strings like
["aaa";"B";"bb";"ccccccc";"D";"a";"BBB";"z";"cc";"11";"2";"11"].
That's my solution until now:
let groupSequences (text:string) =
let toString chars =
System.String(chars |> Array.ofList)
let rec groupSequencesRecursive acc chars = seq {
match (acc, chars) with
| [], c :: rest ->
yield! groupSequencesRecursive [c] rest
| _, c :: rest when acc.[0] <> c ->
yield (toString acc)
yield! groupSequencesRecursive [c] rest
| _, c :: rest when acc.[0] = c ->
yield! groupSequencesRecursive (c :: acc) rest
| _, [] ->
yield (toString acc)
| _ ->
yield ""
}
text
|> List.ofSeq
|> groupSequencesRecursive []
groupSequences "aaaBbbcccccccDaBBBzcc11211"
|> Seq.iter (fun x -> printfn "%s" x)
|> ignore
I'm a F# newbie.
This solution can be better?
Here a completely generic implementation:
let group xs =
let folder x = function
| [] -> [[x]]
| (h::t)::ta when h = x -> (x::h::t)::ta
| acc -> [x]::acc
Seq.foldBack folder xs []
This function has the type seq<'a> -> 'a list list when 'a : equality, so works not only on strings, but on any (finite) sequence of elements, as long as the element type supports equality comparison.
Used with the input string in the OP, the return value isn't quite in the expected shape:
> group "aaaBbbcccccccDaBBBzcc11211";;
val it : char list list =
[['a'; 'a'; 'a']; ['B']; ['b'; 'b']; ['c'; 'c'; 'c'; 'c'; 'c'; 'c'; 'c'];
['D']; ['a']; ['B'; 'B'; 'B']; ['z']; ['c'; 'c']; ['1'; '1']; ['2'];
['1'; '1']]
Instead of a string list, the return value is a char list list. You can easily convert it to a list of strings using a map:
> group "aaaBbbcccccccDaBBBzcc11211" |> List.map (List.toArray >> System.String);;
val it : System.String list =
["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
This takes advantage of the String constructor overload that takes a char[] as input.
As initially stated, this implementation is generic, so can also be used with other types of lists; e.g. integers:
> group [1;1;2;2;2;3;4;4;3;3;3;0];;
val it : int list list = [[1; 1]; [2; 2; 2]; [3]; [4; 4]; [3; 3; 3]; [0]]
How about with groupby
"aaaBbbcccccccD"
|> Seq.groupBy id
|> Seq.map (snd >> Seq.toArray)
|> Seq.map (fun t -> new string (t))
If you input order matters, here is a method that works
"aaaBbbcccccccDaBBBzcc11211"
|> Seq.pairwise
|> Seq.toArray
|> Array.rev
|> Array.fold (fun (accum::tail) (ca,cb) -> if ca=cb then System.String.Concat(accum,string ca)::tail else string(ca)::accum::tail) (""::[])
This one is also based on recursion though the matching gets away with smaller number of checks.
let chop (txt:string) =
let rec chopInner txtArr (word: char[]) (res: List<string>) =
match txtArr with
| h::t when word.[0] = h -> chopInner t (Array.append word [|h|]) res
| h::t when word.[0] <> h ->
let newWord = word |> (fun s -> System.String s)
chopInner t [|h|] (List.append res [newWord])
| [] ->
let newWord = word |> (fun s -> System.String s)
(List.append res [newWord])
let lst = txt.ToCharArray() |> Array.toList
chopInner lst.Tail [|lst.Head|] []
And the result is as expected:
val text : string = "aaaBbbcccccccDaBBBzcc11211"
> chop text;;
val it : string list =
["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
When you're folding, you'll need to carry along both the previous value and the accumulator holding the temporary results. The previous value is wrapped as option to account for the first iteration. Afterwards, the final result is extracted and reversed.
"aaaBbbcccccccDaBBBzcc11211"
|> Seq.map string
|> Seq.fold (fun state ca ->
Some ca,
match state with
| Some cb, x::xs when ca = cb -> x + ca::xs
| _, xss -> ca::xss )
(None, [])
|> snd
|> List.rev
// val it : string list =
// ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
Just interesting why everyone publishing solutions based on match-with? Why not go plain recursion?
let rec groups i (s:string) =
let rec next j = if j = s.Length || s.[i] <> s.[j] then j else next(j+1)
if i = s.Length then []
else let j = next i in s.Substring(i, j - i) :: (groups j s)
"aaaBbbcccccccDaBBBzcc11211" |> groups 0
val it : string list = ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
As someone other here:
Know thy fold ;-)
let someString = "aaaBbbcccccccDaBBBzcc11211"
let addLists state elem =
let (p, ls) = state
elem,
match p = elem, ls with
| _, [] -> [ elem.ToString() ]
| true, h :: t -> (elem.ToString() + h) :: t
| false, h :: t -> elem.ToString() :: ls
someString
|> Seq.fold addLists ((char)0, [])
|> snd
|> List.rev

Convert integer list to a string

I am trying to achieve the following. Input is list [8;9;4;5;7] and output should be
"8,9,4,5,7," Note the "," in the output
I tried the following
let rec ConvertToString list =
match list with
| head :: tail -> head.ToString() + ConvertToString tail
| [] -> ""
let op= [8;9;4;5;7] |> ConvertToString
But the output which i get is val me : string = "89457"
Can anyone kindly suggest how to get get the "," in the output. The function should be generic.
You need to add the comma between the head and the converted tail, and need another case to convert the last element so you don't add a separating comma.
let rec ConvertToString list =
match list with
| [l] -> l.ToString()
| head :: tail -> head.ToString() + "," + ConvertToString tail
| [] -> ""
Note you can also define your function using String.concat:
let ConvertToString l = l |> List.map (fun i -> i.ToString()) |> String.concat ","
or String.Join:
let ConvertToString (l: 'a seq) = System.String.Join(",", l)
If you only want to allow ConvertToString to take int list arguments, you can specify the type of the input argument explicitly:
let ConvertToString (l : int list) = ...
I think this is a nice version for integer lists:
let convertToString = List.map (sprintf "%i") >> String.concat ","
or if you want a generic version:
let convertToString l = l |> List.map (sprintf "%A") |> String.concat ","
Just another way to do this:
let ConvertToString (l: int list) =
match l with
| [] -> ""
| h :: t -> List.fold (fun acc x -> acc + "," + x.ToString()) (h.ToString()) t

How do I write a ZipN-like function in F#?

I want to create a function with the signature seq<#seq<'a>> ->seq<seq<'a>> that acts like a Zip method taking a sequence of an arbitrary number of input sequences (instead of 2 or 3 as in Zip2 and Zip3) and returning a sequence of sequences instead of tuples as a result.
That is, given the following input:
[[1;2;3];
[4;5;6];
[7;8;9]]
it will return the result:
[[1;4;7];
[2;5;8];
[3;6;9]]
except with sequences instead of lists.
I am very new to F#, but I have created a function that does what I want, but I know it can be improved. It's not tail recursive and it seems like it could be simpler, but I don't know how yet. I also haven't found a good way to get the signature the way I want (accepting, e.g., an int list list as input) without a second function.
I know this could be implemented using enumerators directly, but I'm interested in doing it in a functional manner.
Here's my code:
let private Tail seq = Seq.skip 1 seq
let private HasLengthNoMoreThan n = Seq.skip n >> Seq.isEmpty
let rec ZipN_core = function
| seqs when seqs |> Seq.isEmpty -> Seq.empty
| seqs when seqs |> Seq.exists Seq.isEmpty -> Seq.empty
| seqs ->
let head = seqs |> Seq.map Seq.head
let tail = seqs |> Seq.map Tail |> ZipN_core
Seq.append (Seq.singleton head) tail
// Required to change the signature of the parameter from seq<seq<'a> to seq<#seq<'a>>
let ZipN seqs = seqs |> Seq.map (fun x -> x |> Seq.map (fun y -> y)) |> ZipN_core
let zipn items = items |> Matrix.Generic.ofSeq |> Matrix.Generic.transpose
Or, if you really want to write it yourself:
let zipn items =
let rec loop items =
seq {
match items with
| [] -> ()
| _ ->
match zipOne ([], []) items with
| Some(xs, rest) ->
yield xs
yield! loop rest
| None -> ()
}
and zipOne (acc, rest) = function
| [] -> Some(List.rev acc, List.rev rest)
| []::_ -> None
| (x::xs)::ys -> zipOne (x::acc, xs::rest) ys
loop items
Since this seems to be the canonical answer for writing a zipn in f#, I wanted to add a "pure" seq solution that preserves laziness and doesn't force us to load our full source sequences in memory at once like the Matrix.transpose function. There are scenarios where this is very important because it's a) faster and b) works with sequences that contain 100s of MBs of data!
This is probably the most un-idiomatic f# code I've written in a while but it gets the job done (and hey, why would there be sequence expressions in f# if you couldn't use them for writing procedural code in a functional language).
let seqdata = seq {
yield Seq.ofList [ 1; 2; 3 ]
yield Seq.ofList [ 4; 5; 6 ]
yield Seq.ofList [ 7; 8; 9 ]
}
let zipnSeq (src:seq<seq<'a>>) = seq {
let enumerators = src |> Seq.map (fun x -> x.GetEnumerator()) |> Seq.toArray
if (enumerators.Length > 0) then
try
while(enumerators |> Array.forall(fun x -> x.MoveNext())) do
yield enumerators |> Array.map( fun x -> x.Current)
finally
enumerators |> Array.iter (fun x -> x.Dispose())
}
zipnSeq seqdata |> Seq.toArray
val it : int [] [] = [|[|1; 4; 7|]; [|2; 5; 8|]; [|3; 6; 9|]|]
By the way, the traditional matrix transpose is much more terse than #Daniel's answer. Though, it requires a list or LazyList that both will eventually have the full sequence in memory.
let rec transpose =
function
| (_ :: _) :: _ as M -> List.map List.head M :: transpose (List.map List.tail M)
| _ -> []
To handle having sub-lists of different lengths, I've used option types to spot if we've run out of elements.
let split = function
| [] -> None, []
| h::t -> Some(h), t
let rec zipN listOfLists =
seq { let splitted = listOfLists |> List.map split
let anyMore = splitted |> Seq.exists (fun (f, _) -> f.IsSome)
if anyMore then
yield splitted |> List.map fst
let rest = splitted |> List.map snd
yield! rest |> zipN }
This would map
let ll = [ [ 1; 2; 3 ];
[ 4; 5; 6 ];
[ 7; 8; 9 ] ]
to
seq
[seq [Some 1; Some 4; Some 7]; seq [Some 2; Some 5; Some 8];
seq [Some 3; Some 6; Some 9]]
and
let ll = [ [ 1; 2; 3 ];
[ 4; 5; 6 ];
[ 7; 8 ] ]
to
seq
[seq [Some 1; Some 4; Some 7]; seq [Some 2; Some 5; Some 8];
seq [Some 3; Some 6; null]]
This takes a different approach to yours, but avoids using some of the operations that you had before (e.g. Seq.skip, Seq.append), which you should be careful with.
I realize that this answer is not very efficient, but I do like its succinctness:
[[1;2;3]; [4;5;6]; [7;8;9]]
|> Seq.collect Seq.indexed
|> Seq.groupBy fst
|> Seq.map (snd >> Seq.map snd);;
Another option:
let zipN ls =
let rec loop (a,b) =
match b with
|l when List.head l = [] -> a
|l ->
let x1,x2 =
(([],[]),l)
||> List.fold (fun acc elem ->
match acc,elem with
|(ah,at),eh::et -> ah#[eh],at#[et]
|_ -> acc)
loop (a#[x1],x2)
loop ([],ls)

How do I concatenate a list of strings in F#?

I'm trying this at the moment, but I haven't quite got the method signature worked out... anyone? messages is a field of seq[string]
let messageString = List.reduce(messages, fun (m1, m2) -> m1 + m2 + Environment.NewLine)
> String.concat " " ["Juliet"; "is"; "awesome!"];;
val it : string = "Juliet is awesome!"
Not exactly what you're looking for, but
let strings = [| "one"; "two"; "three" |]
let r = System.String.Concat(strings)
printfn "%s" r
You can do
let strings = [ "one"; "two"; "three" ]
let r = strings |> List.fold (+) ""
printfn "%s" r
or
let strings = [ "one"; "two"; "three" ]
let r = strings |> List.fold (fun r s -> r + s + "\n") ""
printfn "%s" r
I'd use String.concat unless you need to do fancier formatting and then I'd use StringBuilder.
(StringBuilder(), [ "one"; "two"; "three" ])
||> Seq.fold (fun sb str -> sb.AppendFormat("{0}\n", str))
just one more comment,
when you are doing with string, you'd better use standard string functions.
The following code is for EulerProject problem 40.
let problem40 =
let str = {1..1000000} |> Seq.map string |> String.concat ""
let l = [str.[0];str.[9];str.[99];str.[999];str.[9999];str.[99999];str.[999999];]
l |> List.map (fun x-> (int x) - (int '0')) |> List.fold (*) 1
if the second line of above program uses fold instead of concat, it would be extremely slow because each iteration of fold creates a new long string.
System.String.Join(Environment.NewLine, List.to_array messages)
or using your fold (note that it's much more inefficient)
List.reduce (fun a b -> a ^ Environment.NewLine ^ b) messages

Resources