Understanding F# seqs - f#

I am trying to use FAKE for automating my project builds. In the process, I wrote the following F# code -
open System
open System.IO
module FileUtils =
type BuildConfiguration = {Name:string; Directory:string}
let DebugBuildConfiguration = {Name = "Debug"; Directory = #"\bin\Debug"}
let ReleaseBuildConfiguration = {Name = "Release"; Directory = #"\bin\Release"}
let StagingBuildConfiguration = {Name = "Staging"; Directory = #"\bin\Staging"}
let codeDir = #"C:\source-control\s4sold\src"
let sourceDirs = [| "co"; "lo"; "mo"; "po"; "pr"; "pro"; "re"; "ro" |]
let GetAllBuildConfigurationDirs buildConfiguration =
let allSourceDirs = sourceDirs
|> Seq.map (fun i -> Path.Combine(codeDir, i))
|> Seq.map (fun d -> Directory.GetDirectories(d))
|> Array.concat
allSourceDirs |> printf "%A"
List.ofArray allSourceDirs
|> List.map (fun i -> Path.Combine(i, buildConfiguration.Directory))
// |> Seq.toArray
Now the problem I am facing is that when I print allSourceDirs, it correctly prints the directories under my /src folder. But when I run GetAllBuildConfigurationDirs, all I get is an array of "\bin\Debug", which means it doesn't take the output of allSourceDirs into consideration.
I am really at a loss to understand what's happening here. I am an F# newbie, but know some Clojure. Also how can I use Seq.map over a seq of seqs (so that I might avoid the Array.concat call)?

The problem is the way you are combining paths, you have to remove the \ at the beginning, just write #"bin\Debug"
Also you can combine both maps into one, since map f |> map g is equivalent to map (f |> g) and instead of map + concat you can use collect.
Here's your original code with these corrections:
open System
open System.IO
module FileUtils =
type BuildConfiguration = {Name:string; Directory:string}
let DebugBuildConfiguration = {Name = "Debug"; Directory = #"bin\Debug"}
let ReleaseBuildConfiguration = {Name = "Release"; Directory = #"bin\Release"}
let StagingBuildConfiguration = {Name = "Staging"; Directory = #"bin\Staging"}
let codeDir = #"C:\source-control\s4sold\src"
let sourceDirs = [| "co"; "lo"; "mo"; "po"; "pr"; "pro"; "re"; "ro" |]
let allSourceDirs = sourceDirs
|> Array.collect (fun i -> Path.Combine(codeDir, i) |> Directory.GetDirectories)
allSourceDirs |> printf "%A"
List.ofArray allSourceDirs
|> List.map (fun i -> Path.Combine(i, buildConfiguration.Directory))
// |> Seq.toArray

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")

Read all lines from file

I want to read all the lines from the file and implement acquired the following:
let s1 = File.ReadAllText("\\test.txt")
let splitArr = s1.Split[|' '|] |> Array.toList
let checkList = check splitArr
let final = String.concat " " checkList |> toLower |> makeUpper
Console.Write(final)
I even tried to looping through the file but perhaps did wrong:
let myfile = File.OpenText("\\test.txt")
let fileLoop() =
let s1 = myfile.ReadLine()
let splitArr = s1.Split[|' '|] |> Array.toList
let checkList = check splitArr
let final = String.concat " " checkList |> toLower |> upper
Console.Write(s1)
while fileLoop() do ignore None
Your existing code bears little resemblance with your textual description of what you are trying to achieve. Ignoring that, a simple observation to make your current code work: You are using File.ReadAllText, which returns a giant blob of text - but you are afterwards assuming it comes already separated by lines. Use one of the methods in the File module that returns the text linewise, that is ReadLines or ReadAllLines. Try the following:
File.ReadLines("\\test.txt")
|> Seq.iter (fun s1 ->
let splitArr = s1.Split[|' '|] |> Array.toList
let checkList = check splitArr
let final = String.concat " " checkList |> toLower |> makeUpper
Console.WriteLine(final)
)
Or making heavier use of pipes:
File.ReadLines "\\test.txt"
|> Seq.iter (fun s1 ->
s1.Split [|' '|]
|> Array.toList
|> check
|> String.concat " "
|> toLower
|> makeUpper
|> Console.WriteLine
)

F# suitable container for (string, float, float) triads?

I have the following problem and I hope somebody can help me.
Short description of the problem: i need to store a (string A, float B, float C) triad into a suitable container. The triad originates fomr a double "for" loop.
But the essential point is that I will need to slice this container when the loops are over to perform other operations.
An example that can be executed from the .fsx shell (using Deedle frames) follows. The triad is what is beeing printed on the screen.
open Deedle
let categorical_variable = [| "A"; "B"; "C"; "A"; "B"; "C"; |]
let vec_1 = [| 15.5; 14.3; 15.5; 14.3; 15.5; 14.3; |]
let vec_2 = [| 114.3; 17.5; 9.3; 88.7; 115.5; 12.3; |]
let dframe = frame ["cat" =?> Series.ofValues categorical_variable
"v1" =?> Series.ofValues vec_1
"v2" =?> Series.ofValues vec_2 ]
let distinct_categorical_variables = categorical_variable |> Array.toSeq |> Seq.distinct |> Seq.toArray
let mutable frame_slice : Frame<int, string> = Frame.ofRows []
let mutable frame_slice_vec_1 : float[] = Array.empty
let mutable frame_slice_vec_1_distinct : float[] = Array.empty
for cat_var in distinct_categorical_variables do
frame_slice <- (dframe |> Frame.filterRowValues (fun row -> row.GetAs "cat" = cat_var))
frame_slice_vec_1 <- (frame_slice?v1).Values |> Seq.toArray
frame_slice_vec_1_distinct <- (frame_slice_vec_1 |> Array.toSeq |> Seq.distinct |> Seq.toArray)
for vec_1_iter in frame_slice_vec_1_distinct do
printfn "%s, %f, %f \n" cat_var vec_1_iter (Array.average ((frame_slice?v2).Values |> Seq.toArray) ) |> ignore
So, is there any suitable object where to store this triad? I saw Array3d objects, but I don't think they are the right solution cause A, B and C of my triad have different types.
Many thanks in advance.
you probably want a sequence expression with tuples:
let mySequence =
seq { for cat_var in distinct_categorical_variables do
...
for vec_1_iter in ... do
yield cat_var, vec_1_iter, Array.average ... }
// then use it like
for cat_var, vec_1_iter, result in mySequence do
...

Get a list of invalid drive letters

let private GetDrives = seq{
let all=System.IO.DriveInfo.GetDrives()
for d in all do
//if(d.IsReady && d.DriveType=System.IO.DriveType.Fixed) then
yield d
}
let valid={'A'..'Z'}
let rec SearchRegistryForInvalidDrive (start:RegistryKey) = seq{
let validDrives=GetDrives |> Seq.map (fun x -> x.Name.Substring(0,1))
let invalidDrives= Seq.toList validDrives |> List.filter(fun x-> not (List.exists2 x b)) //(List.exists is the wrong method I think, but it doesn't compile
I followed F#: Filter items found in one list from another list but could not apply it to my problem as both the solutions I see don't seem to compile. List.Contains doesn't exist (missing a reference?) and ListA - ListB doesn't compile either.
open System.IO
let driveLetters = set [ for d in DriveInfo.GetDrives() -> d.Name.[0] ]
let unused = set ['A'..'Z'] - driveLetters
Your first error is mixing between char and string, it is good to start with char:
let all = {'A'..'Z'}
let validDrives = GetDrives |> Seq.map (fun x -> x.Name.[0])
Now invalid drive letters are those letters which are in all but not in validDrives:
let invalidDrives =
all |> Seq.filter (fun c -> validDrives |> List.forall ((<>) c))
Since validDrives is traversed many times to check for membership, turning it to a set is better in this example:
let all = {'A'..'Z'}
let validDrives = GetDrives |> Seq.map (fun x -> x.Name.[0]) |> Set.ofSeq
let invalidDrives = all |> Seq.filter (not << validDrives.Contains)

f# array.filter based on a bool array

if I have array A, and I have another bool array isChosen with the same length of A how can I build a new array from A where isChosen is true? something like A.[isChosen]? I cannot use Array.filter directly since isChosen is not a function of A elements and there is no Array.filteri like Array.mapi.
zip should help:
let l = [|1;2;3|]
let f = [|true; false; true|]
let r = [| for (v, f) in Seq.zip l f do if f then yield v|]
// or
let r = (l, f) ||> Seq.zip |> Seq.filter snd |> Seq.map fst |> Seq.toArray
Try the zip operator
seq.zip A isChosen
|> Seq.filter snd
|> Seq.map fst
|> Array.ofSeq
This will create a sequence of tuples where one value is from A and the other is from isChosen. This will pair the values together and make it very easy to filter them out in a Seq.filter expression
It's not as elegant or 'functional' as the other answers, but every once in a while I like a gentle reminder that you can use loops and array indices in F#:
let A = [|1;2;3|]
let isChosen = [|true; false; true|]
let r = [| for i in 0..A.Length-1 do
if isChosen.[i] then
yield A.[i] |]
printfn "%A" r
:)
And here are two more ways, just to demonstrate (even) more F# library functions:
let A = [|1;2;3|]
let isChosen = [|true;false;true|]
let B = Seq.map2 (fun x b -> if b then Some x else None) A isChosen
|> Seq.choose id
|> Seq.toArray
let C = Array.foldBack2 (fun x b acc -> if b then x::acc else acc) A isChosen []
|> List.toArray
My personal favorite for understandability (and therefore maintainability): desco's answer
let r = [| for (v, f) in Seq.zip l f do if f then yield v|]

Resources