Would someone please help me understand why the code below gives me the error 'Block following let is unfinished. Expected an Expression'? The value of x is expected to be a string list and that is how F# sees it. So why does x not become a string list for use later in the function?
let fxProper (str : string) (values : obj[,]) =
let x =
values
|> Seq.cast<obj>
|> Seq.filter (fun x -> not (x :? ExcelEmpty))
|> Seq.map string
|> Seq.toList
You need to do something with the x value you just set
let fxProper (str : string) (values : obj[,]) =
let x =
values
|> Seq.cast<obj>
|> Seq.filter (fun x -> not (x :? ExcelEmpty))
|> Seq.map string
|> Seq.toList
x
should work.
This
let fxProper (str : string) (values : obj[,]) =
values
|> Seq.cast<obj>
|> Seq.filter (fun x -> not (x :? ExcelEmpty))
|> Seq.map string
|> Seq.toList
should work as well.
You're doing it right. The let binding for x is working properly. The error is telling you that your function fxProper isn't currently returning anything. If your intent is to return x then you need to add it at the end of fxProper like below, otherwise just add a dummy return value until you're finished writing your function.
let fxProper (str : string) (values : obj[,]) =
let x =
values
|> Seq.cast<obj>
|> Seq.filter (fun x -> not (x :? ExcelEmpty))
|> Seq.map string
|> Seq.toList
x //this returns the value of x from fxProper, this could also just the default value of whatever you actually want to return here
Related
Is it possible to print out a sequence in a pipe forward sequence? I have the following code:
let rec crawlPage (page : String, nestingLevel : int) : seq<string> =
System.Threading.Thread.Sleep 1200
//printfn "URL: %s" page
//printfn "Nesting Level: %i \n" nestingLevel
HtmlDocument.Load(page)
|> fun m -> m.CssSelect("a")
|> List.map(fun a -> a.AttributeValue("href"))
|> Seq.distinctBy id
|> Seq.filter (fun x -> x.Contains baseUrl1)
//|> Seq.map (printfn "%A") // I would like to be able to do something like this.
|> Seq.map (fun x -> https + x)
|> Seq.map (fun x ->
match nestingLevel with
| _ when (nestingLevel > 0) -> crawlPage(x, (nestingLevel - 1))
| _ -> Seq.singleton x)
|> Seq.concat
|> Seq.distinctBy id
I was able to work around it by writing a helper function like follows:
let strPrint (str : string) : string =
printfn "%s" str
str
I would rather just use the pipe forwarding if possible though.
Sure, you can just do the exact same thing inline, with an anonymous function:
...
|> Seq.map (fun str ->
printfn "%s" str
str)
|>
...
As a rule, if you have let f x = e, you can always replace any occurrence of f with its anonymous equivalent (fun x -> e)
I am trying to use FSharp.Data's HTML Parser to extract a string List of links from href attributes.
I can get the links printed out to console, however, i'm struggling to get them into a list.
Working snippet of a code which prints the wanted links:
let results = HtmlDocument.Load(myUrl)
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.map (fun x -> x.Elements("a"))
|> Seq.iter (fun x -> x |> Seq.iter (fun y -> y.AttributeValue("href") |> printf "%A"))
How do i store those strings into variable links instead of printing them out?
Cheers,
On the very last line, you end up with a sequence of sequences - for each td.pagenav you have a bunch of <a>, each of which has a href. That's why you have to have two nested Seq.iters - first you iterate over the outer sequence, and on each iteration you iterate over the inner sequence.
To flatten a sequence of sequences, use Seq.collect. Further, to convert a sequence to a list, use Seq.toList or List.ofSeq (they're equivalent):
let a = [ [1;2;3]; [4;5;6] ]
let b = a |> Seq.collect id |> Seq.toList
> val b : int list = [1; 2; 3; 4; 5; 6]
Applying this to your code:
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.map (fun x -> x.Elements("a"))
|> Seq.collect (fun x -> x |> Seq.map (fun y -> y.AttributeValue("href")))
|> Seq.toList
Or you could make it a bit cleaner by applying Seq.collect at the point where you first encounter a nested sequence:
let links =
results.Descendants("td")
|> Seq.filter (fun x -> x.HasClass("pagenav"))
|> Seq.collect (fun x -> x.Elements("a"))
|> Seq.map (fun y -> y.AttributeValue("href"))
|> Seq.toList
That said, I would rather rewrite this as a list comprehension. Looks even cleaner:
let links = [ for td in results.Descendants "td" do
if td.HasClass "pagenav" then
for a in td.Elements "a" ->
a.AttributeValue "href"
]
What's an alternative to Seq.iter so that I can return the result of the operation for the last item?
Seq.iter returns a unit. However, I want to iterate through my collection and return the last result.
Consider the following code:
let updatedGrid = grid |> Map.toSeq
|> Seq.map snd
|> Seq.iter (fun c -> grid |> setCell c
NOTE: SetCell returns a new Map:
Here's the actual code:
let setCell cell (grid:Map<(int * int), Cell>) =
grid |> Map.map (fun k v -> match k with
| c when c = (cell.X, cell.Y) -> { v with State=cell.State }
| _ -> v)
let cycleThroughCells (grid:Map<(int * int), Cell>) =
let updatedGrid = grid |> Map.toSeq
|> Seq.map snd
|> Seq.iter (fun c -> grid |> setCell c
|> ignore)
updatedGrid
Again, I just want to take the result of the last operation in the iter function
[UPDATED]
I think this works (using map):
let cycleThroughCells (grid:Map<(int * int), Cell>) =
let updatedGrid = grid |> Map.toSeq
|> Seq.map snd
|> Seq.map (fun c -> grid |> setCell c)
|> Seq.last
updatedGrid
As I said in a comment, it seems like you almost certainly want a fold so that the updated grid is passed to each successive call; otherwise the modifications are all dropped except for the last one.
I think this would do the trick:
let cycleThroughCells (grid:Map<(int * int), Cell>) =
grid
|> Map.toSeq
|> Seq.map snd
|> Seq.fold (fun grid c -> grid |> setCell c) grid
and if you reorder the arguments to setCell so that the grid argument comes first then the last line can just be |> Seq.fold setCell grid.
I don't think one exists but you can define your own using fold:
let tapSeq f s = Seq.fold (fun _ x -> f x; Some(x)) None s
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)
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|]