I have a situation in finding a sequence of strings that have patterns like XXXX or CCCC or "IIII". I have tried the following code, but it does not work
let rec checkSequence roman=
let r=List.ofSeq roman
match r with
| [] -> true
| a::b::c::d::tail when (a="I" || a="X" || a="C") && a=b && a=c && a=d -> false
| head::tail -> checkSequence tail
checkSequence "CCC"
The error is: This expression was expected to have type string list but here has type string
1-How can I resolve this error?
2-Is there any simpler way to find this patterns?
If you need use a recursion on list you may do something like this:
let checkSequenceStr str =
let rec checkSequence roman =
match roman with
| [] -> true
| 'I'::'I'::'I'::'I'::tail -> false
| 'X'::'X'::'X'::'X'::tail -> false
| 'C'::'C'::'C'::'C'::tail -> false
| head::tail -> checkSequence tail
checkSequence (str |> List.ofSeq)
Or you could use .NET string methods to check patterns directly (which is easier):
let checkPattern (str : string) =
["IIII";"CCCC";"XXXX"] |> List.exists str.Contains |> not
You are using List.ofSeq, this will type force the roman parameter to be of an type list.
https://msdn.microsoft.com/en-us/library/ee340325.aspx
Therefor your error This expression was expected to have type string list but here has type string is due to calling the function wrongly, then an logical error. Therefor change:
checkSequence "CCC"
Into:
checkSequence ["C"; "C";"C"]
Related
I'm de-serializing some mappings from JSON and later on I need to pattern match based on a string field of the de-serialized types like this:
let mappings = getWorkItemMappings
let result =
workItemMappings
|> Seq.find (fun (m: WorkItemMapping) -> m.Uuid = workTime.workItemUuid)
match mapping.Name with
Even if I complete the pattern match for all cases I still get Incomplete pattern matches on this expression.. Which is obvious to me due to the string type of the Name field.
Is there a way tell the compiler which values for the Name field are available?.
I think I could create a union type for the possible mapping types and try to de-serialize the JSON to this union type but I would like to if there's another option.
If you are pattern matching on a string value, the compiler has no static guarantee that it will only have certain values, because it is always possible to construct a string of a different value. The fact that it comes from JSON does not help - you may always have an invalid JSON.
The best option is to add a default case which throws a custom descriptive exception. Either one that you handle somewhere else (to indicate that the JSON file was invalid) or (if you check the validity elsewhere) something like this:
let parseFood f =
match f with
| "burger" -> 1
| "pizza" -> 2
| _ -> raise(invalidArg "f" $"Expected burger or pizza but got {f}")
Note that the F# compiler is very cautious. It does not even let you handle enum values using pattern matching, because under the cover, there are ways of creating invalid enum values! For example:
type Foo =
| A = 1
let f (a:Foo) =
match a with
| Foo.A -> 0
warning FS0104: Enums may take values outside known cases. For example, the value 'enum (0)' may indicate a case not covered by the pattern(s).
Very hard to understand what you're asking. Maybe this snippet can be of help. It demos how literal string constants can be used in pattern matching, and reused in functions. This gives some added safety and readability when adding and removing cases. If you prefer not to serialize a DU directly, then perhaps this is useful as part of the solution.
type MyDu =
| A
| B
| C
let [<Literal>] A' = "A"
let [<Literal>] B' = "B"
let [<Literal>] C' = "C"
let strToMyDuOption (s: string) =
match s with
| A' -> Some A
| B' -> Some B
| C'-> Some C
| _ -> None
let strToMyDu (s: string) =
match s with
| A' -> A
| B' -> B
| C'-> C
| s -> failwith $"MyDu case {s} is unknown."
let myDuToStr (x: MyDu) =
match x with
| A -> A'
| B -> B'
| C -> C'
// LINQPad
let dump x = x.Dump()
strToMyDuOption A' |> dump
strToMyDuOption "x" |> dump
myDuToStr A |> dump
I have the following f# code
product.code <- productPage.Html
.Descendants["li"]
.Select(fun node -> node.InnerText())
.Where(fun link -> (Regex.Match(link,#"code:").Success))
.FirstOrDefault()
.Replace("code:", "")
.Trim()
I'm having some trouble with nulls.
In c# I would do something like this.
product.code = productPage?.Html
?.Descendants["li"]
?.Select(node => node.InnerText())
?.Where(link => Regex.Match(link,#"code:").Success)
?.FirstOrDefault()
?.Replace("code:", "")
?.Trim() ?? "Not Found"
Is this possible?
In the second example, it looks to me like "?." has to be carried through the whole call chain due to its initial use. Rather than try to recreate this operator and preserve how this looks in C#, I suggest you go for more idiomatic F#. For example:
module String =
let replace (oldValue: string) (newValue: string) (s: string) =
s.Replace (oldValue, newValue)
let trim (s: string) =
s.Trim()
let result =
match isNull productPage with
| true -> None
| false ->
productPage.Html.Descendants.["li"]
|> Seq.map (fun node -> node.InnerText())
|> Seq.tryPick (fun link -> (Regex.Match (link, "code:").Success))
let code =
match result with
| Some html ->
html
|> String.replace "code:" ""
|> String.trim
| None -> "Not Found"
product.code <- code
I have an expression using pipe operator that converts the value to string and then to bool, however sometimes the original value can be null.
How can I use the pattern matching or something else to assume false when the value is null?
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) = d.["fAllDayEvent"] |> string |> bool.Parse
There's quite a few places where you can safeguard via pattern matching: dictionary lookup, casting, parsing. Here's an example with all of those:
let allDayEvent (d: kv) =
match d.TryGetValue "fAllDayEvent" with
| true, v ->
match v with
| null -> printfn "null found"
| :? string as s ->
match bool.TryParse s with
| true, b -> printfn "found a bool: %A" b
| _ -> printfn "That's not a bool?"
| v -> printfn "Found something of type %s" (v.GetType().Name)
| _ -> printfn "No such key"
See also related questions, for example this.
Not sure why you are using a Dictionary, but I would probably have gone for a Map instead. Or at least done some Conversion to Map somewhere. And then I would maybe have thrown in some "automagically" handling of nulls.
And then Pandoras Box is kind of opened, but....
let (|Bool|) str =
match System.Boolean.TryParse(str) with
| (true,bool) -> Some(bool)
| _ -> None
let (|String|) (o:obj) =
match o with
| :? string as s -> Some(s)
| _ -> None
type kv = Dictionary<string, obj>
let allDayEvent (d: kv) =
d :> seq<_>
|> Seq.map (|KeyValue|)
|> Map.ofSeq
|> Map.tryFind "fAllDayEvent"
|> Option.bind (|String|)
|> Option.bind (|Bool|)
Note that allDayEvent in the above now is an Option, which maybe is in fact what you need/want.
And it does keep all data in place. Like true or false is not the same as "did not find stuff" or "could not convert stuff to some bool". Now it is in fact one of the following:
key found and some string like "true": Some(true)
key found and some string like "false": Some(false)
key not found or string not convertable to bool: None
Code is not tested and may need some further massaging.
I try to find the correct pattern to match and run an Expr<int> using the below code:
open System.Linq
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
let runSelectQuery (q:Expr<IQueryable<'T>>) =
match q with
| Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let runCountQuery (q:Expr<int>) =
match q with
| Application(Lambda(builder, Call(None, miRun, [builder2, Quote body])), queryObj) ->
query.Run(Expr.Cast<int>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let countQuery source filter =
let filter = match filter with | Some filter -> filter | _ -> <# fun _ -> true #>
<# query { for item in source do
where ((%filter) item)
count } #>
The runSelectQuery correctly matches the Expr<IQueryable<'T>> pattern. However, I cannot find the correct pattern to match my generic count query Expr<int>
The pattern in the code I derived from the signature of countQuery gives me a:
This expression was expected to have type
Expr but here has type
'a * 'b
Found it! Stupidly I first tried to match the array pattern using a comma separated pattern (as is the list delimiter in C#), that obviously did not work in F# complaining the it was not a list but a tupple and thus not a Rex.
To match agains an int result or any 'T result:
let runQueryToQueryable (q:Expr<IQueryable<'T>>) =
match q with
| Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
let runQueryToType (q:Expr<'T>) =
match q with
| Application(Lambda(builder, Call(None, miRun, [builder2; Quote body])), queryObj) ->
query.Run(Expr.Cast<'T>(body))
| _ -> failwith "Cannot run this query %s" (q.ToString())
Works like a charm.
I am trying to match the beginning of strings in f#. Not sure if I have to treat them as a list of characters or what. Any suggestions would be appreciated.
Here is a psuedo code version of what I am trying to do
let text = "The brown fox.."
match text with
| "The"::_ -> true
| "If"::_ -> true
| _ -> false
So, I want to look at the beginning of the string and match. Note I am not matching on a list of strings just wrote the above as an idea of the essence of what I am trying to do.
Parameterized active patterns to the rescue!
let (|Prefix|_|) (p:string) (s:string) =
if s.StartsWith(p) then
Some(s.Substring(p.Length))
else
None
match "Hello world" with
| Prefix "The" rest -> printfn "Started with 'The', rest is %s" rest
| Prefix "Hello" rest -> printfn "Started with 'Hello', rest is %s" rest
| _ -> printfn "neither"
You could also use a guard on the pattern:
match text with
| txt when txt.StartsWith("The") -> true
| txt when txt.StartsWith("If") -> true
| _ -> false
Yes you have to treat them as a list of characters if you want to use a match expression.
Simply transform the string with:
let text = "The brown fox.." |> Seq.toList
Then you can use a match expression but you will have to use chars (the type of elements in the list) for each letter:
match text with
| 'T'::'h'::'e'::_ -> true
| 'I'::'f'::_ -> true
| _ -> false
As Brian suggest Parameterized Active Patterns are much nicer, there a some useful patterns here (go the end of the page).