Can I use pattern matching on a collection?
I have a grid that represents a tic-tac-toe board.
How can I use pattern matching to identify streaks of either 'X' or 'O'?
So far I built a grid like this:
// ----------------
// 0 | 1 | 2 |
// ----------------
// 3 | 4 | 5 |
// ----------------
// 6 | 7 | 8 |
// ----------------
type Marker =
| X = 0
| O = 1
| NULL = 3
let cells = [0..8]
let grid = [for cell in cells -> (cell, Marker.NULL)]
let streakExists =
match grid with
| ???
| ???
So I want to use pattern matching to identify a streak like the following:
// if grid.[..2] all have X
// or grid.[3..5] all have X
// or grid.[6..8] all have X
// return true
//-----------------
// if grid.[0;3;6;] all have X
// or grid.[1;4;5] all have X
// or grid.[2;5;8] all have X
// return true
//------------------
// if grid.[0;4;8;] all have X
// return true
NOTE:
I am learning the basics of F#.
As a result please forgive me if this question appears obvious.
You can use pattern matching on collections, but there are quite a lot of cases in that you would have to cover in tic-tac-toe, so it is probably not the best option.
If I simplify your code a bit (to use a discriminated union rather then enum and to use just a list of values of the discriminated union), it would look like this:
type Marker =
| X
| O
| NULL
let grid = [ for cell in 0 .. 8 -> NULL ]
let streakExists =
match grid with
| [X;X;X;_;_;_;_;_;_]
| [_;_;_;X;X;X;_;_;_]
| [_;_;_;_;_;_;X;X;X] -> "X wins"
| _ -> "Not sure"
This would work, but you can see how hard it will be to cover all cases. If I wanted to solve the same problem, I would probably write it differently. You could create a list of lists that represent all possible streaks in the grid:
let streaks =
[ for row in [0;3;6] do // Generate one streak for each row
yield [row;row+1;row+2]
for col in [0;1;2] do // Generate one streak for each column
yield [col;col+3;col+6]
yield [0;4;8] // Explicitly add two
yield [2;4;6] ] // diagonal streaks
Now you could check if grid contains a winning streak by testing whether there is any streak (from streaks) such that values at all the specified indices are X or O. This should be easy to do with List.forall and List.exists.
As Tomas wrote, it'd be easier to simplify the Marker type to a Discriminated Union:
type Marker = X | O | NULL
Also, since the board is so small, you can just keep it as a list of eight values:
let grid = List.init 8 (fun _ -> NULL)
I agree with Tomas that pattern matching probably isn't the best way to address this problem, but for completeness' sake, you can make matches that somewhat visibly communicate the patterns they match on:
let hasStreak = function
| [X; X; X;
_; _; _;
_; _; _]
| [_; _; _;
X; X; X;
_; _; _]
| [_; _; _;
_; _; _;
X; X; X]
| [X; _; _;
X; _; _;
X; _; _]
| [_; X; _;
_; X; _;
_; X; _]
| [_; _; X;
_; _; X;
_; _; X]
| [X; _; _;
_; X; _;
_; _; X]
| [_; _; X;
_; X; _;
X; _; _] -> true
// The same sort of cases should go here for O...
| _ -> false
If you pattern match directly to the grid, I'm not sure the syntax is going to be very nice, your options are:
let streakExists grid =
match grid with
|[Marker.X; Marker.X; Marker.X; _; _; _; _; _; _] -> true
|[_; _; _; Marker.X; Marker.X; Marker.X; _; _; _] -> true
|[_; _; _; _; _; _; Marker.X; Marker.X; Marker.X] -> true
...
| _ -> false
or
let streakExists grid =
match grid with
|Marker.X :: Marker.X :: Marker.X :: _ -> true
| _ :: _ :: _ :: Marker.X :: Marker.X :: Marker.X :: _ -> true
| _ :: _ :: _ :: _ :: _ :: _ :: Marker.X :: Marker.X :: Marker.X :: [] -> true
...
| _ -> false
(this option isn't ideal because it could return true on incorrectly sized grids so don't use this, I just want to demonstrate the syntax.)
You could make a function to pattern match the lists in sets of 3:
let streakExists grid =
let checkListOf3 list =
match list with
|[Marker.X; Marker.X; Marker.X] -> true
|_ -> false
if grid.[0..2] |> checkListOf3 then true
elif grid.[3..5] |> checkListOf3 then true
elif grid.[6..8] |> checkListOf3 then true
...
else false
Edit
You could combine this last approach with the one suggested by Tomas using his streaks to create:
let streakExists grid =
let checkListOf3 list =
match list with
|[Marker.X; Marker.X; Marker.X] -> true
|_ -> false
streaks |> List.exists (List.map (fun i -> List.item i grid) >> checkListOf3)
Related
This function is supposed to just return the index of a list. That part works. However when a element is not in a list it must return -1.
For some reason it does not return -1.
let rec search f list =
match list with
| head::tail ->
if f head then 0
else 1 + search f tail
| [] -> -1
printfn "%A" (search (fun x -> x = 5) [ 5; 4; 3; 2 ])
//>> return index 0 for #5
printfn "%A" (search (fun x -> x = 6) [ 5; 4; 3; 2 ])
//>> should return -1 but it returns 3 which is the len of the list not -1
EDIT: Can not use nested functions.
You could use e.g.
let search f list =
let rec where at list =
match list with
| [] -> -1
| head::tail ->
if f head then at
else where (at + 1) tail
where 0 list
which has the benefit of being tail-recursive. Regarding your comment:
let rec search f list =
match list with
| [] -> -1
| head::tail ->
if f head then 0 else
match search f tail with
| -1 -> -1
| i -> i + 1
I need some help with my hometask: to express one function (sort) through others (smallest, delete, insert). If you know how, please, tell me, how I can do running my recursion cicle? it doing now only one step. maybe something like this: val4 -> head :: tail |> sort tail on line 25 (val4)?
let rec smallest = function
| x :: y :: tail when x <= y -> smallest (x :: tail)
| x :: y :: tail when x > y -> smallest (y :: tail)
| [x] -> Some x
| _ -> None
let rec delete (n, xs) =
match (n, xs) with
| (n, x :: xs) when n <> x -> x :: delete (n, xs)
| (n, x :: xs) when n = x -> xs
| (n, _) -> []
let rec insert (xs, n) =
match (xs, n) with
| ([x], n) when x < n -> [x]#[n]
| (x :: xs, n) when x < n -> x :: insert (xs, n)
| (x :: xs, n) when x >= n -> n :: x :: xs
| (_, _) -> []
let rec sort = function
| xs -> let val1 = smallest xs
let val2 = val1.[0]
let val3 = delete (val2, xs)
let val4 = insert (val3, val2)
val4
let res = sort [5; 4; 3; 2; 1; 1]
printfn "%A" res
This is sort of like insertion sort, but since you're always finding the smallest number in the whole list instead of the next highest number, it will recurse forever unless you skip whatever you've already found to be the smallest.
Furthermore, your insert and delete functions act not on the item index, but on equality to the value, so it won't be able to handle repeated numbers.
Keeping most of your original code the same, usually you have an inner recursive function to help you keep track of state. This is a common FP pattern.
let sort lst =
let size = lst |> List.length
let rec sort' xs = function
| index when index = size -> xs
| index ->
let val1 = smallest (xs |> List.skip index)
let val2 = val1.[0]
let val3 = delete (val2, xs)
let val4 = insert (val3, val2)
sort' val4 (index + 1)
sort' lst 0
let res = sort [5; 3; 2; 4; 1; ]
printfn "%A" res
Needless to say, this isn't correct or performant, and each iteration traverses the list multiple times. It probably runs in cubic time.
But keep learning!
I found it... I only had changed 4 & 5 lines above in the "smallest" on this: | [x] -> Some x
| _ -> None, when there was: | [x] -> [x]
| _ -> []
let rec sort = function
| xs -> match xs with
| head :: tail -> let val1 = smallest xs
match val1 with
| Some x -> let val2 = delete (x, xs)
let val3 = insert (val2, x)
let val4 = (fun list -> match list with head :: tail -> head :: sort tail | _ -> [])
val4 val3
| None -> []
| _ -> []
// let res = sort [5; 4; 3; 2; 1]
// printfn "%A" res
I'm trying to find the maximum element in a list without using List.Max for a school assignment using the below given template.
let findMax l =
let rec helper(l,m) = failwith "Not implemented"
match l with
| [] -> failwith "Error -- empty list"
| (x::xs) -> helper(xs,x)
The only solution to the problem I can think of, atm is
let rec max_value1 l =
match l with
|[] -> failwith "Empty List"
|[x] -> x
|(x::y::xs) -> if x<y then max_value1 (y::xs)
else max_value1 (x::xs)
max_value1 [1; 17; 3; 6; 1; 8; 3; 11; 6; 5; 9];;
Is there any way I can go from the function I built to one that uses the template? Thanks!
Your helper function should do the work, the outer function just validates that the list is not empty and if it's not, calls the helper, which should be something like this:
let rec helper (l,m) =
match (l, m) with
| [] , m -> m
| x::xs, m -> helper (xs, max m x)
Note, that you since you're matching against the last argument of the function you can remove it and use function instead of match with:
let rec helper = function
| [] , m -> m
| x::xs, m -> helper (xs, max m x)
let findMax l =
let rec helper(l,m) =
match l with
| [] -> m
| (x::xs) -> helper(xs, if (Some x > m) then Some x else m)
helper (l,None)
Example:
[-2;-6;-1;-9;-56;-3] |> findMax
val it : int option = Some -1
An empty list will return None.
You could go for a tuple to pass both, or simply apply the helper function in your main match (instead of the empty list guard clause). I'm including the answer for someone who might find this question in the future and not have a clear answer.
let findMax l =
let rec walk maxValue = function
| [] -> maxValue
| (x::xs) -> walk (if x > maxValue then x else maxValue) xs
match l with
| [] -> failwith "Empty list"
| (head::tail) -> walk head tail
findMax [1; 12; 3; ] //12
Using fold:
let findMax l = l |> List.fold (fun maxValue x -> if x > maxValue then x else maxValue) (List.head l)
I am not sure of what the exact rules of your assigment are but the max of a list is really just List.reduce max. So
let listMax : int list -> int = List.reduce max
You need the type annotation to please the typechecker.
let inline listMax xs = List.reduce max xs
also works and is generic so it works with e.g. floats and strings as well.
this code i got is from Alexander Battisti about how to make a tree from a list of data:
let data = [4;3;8;7;10;1;9;6;5;0;2]
type Tree<'a> =
| Node of Tree<'a> * 'a * Tree<'a>
| Leaf
let rec insert tree element =
match element,tree with
| x,Leaf -> Node(Leaf,x,Leaf)
| x,Node(l,y,r) when x <= y -> Node((insert l x),y,r)
| x,Node(l,y,r) when x > y -> Node(l,y,(insert r x))
| _ -> Leaf
let makeTree = List.fold insert Leaf data
then i want to implement this code to my binary search tree code
let rec BinarySearch tree element =
match element,tree with
| x,Leaf -> BinarySearch (Node(Leaf,x,Leaf)) x
| x,Node(l,y,r) when x<=y ->
BinarySearch l y
| x,Node(l,y,r) when x>y ->
BinarySearch r y
| x,Node(l,y,r) when x=y ->
true
| _ -> false
then i use my search code like this:
> BinarySearch makeTree 5;;
and the result is none because it's like i got an infinite looping
can someone help me? if my code is wrong, please help me to correct it, thank you
The solution by Yin is how I would write it too.
Anyway, here is a solution that is closer to your version and (hopefully) explains what went wrong:
let rec BinarySearch tree element =
match element,tree with
| x, Leaf ->
// You originally called 'BinarySearch' here, but that's wrong - if we reach
// the leaf of the tree (on the path from root to leaf) then we know that the
// element is not in the tree so we return false
false
| x, Node(l,y,r) when x<y ->// This needs to be 'x<y', otherwise the clause would be
// matched when 'x=y' and we wouldn't find the element!
BinarySearch l element // Your recursive call was 'BinarySearch l y' but
// that's wrong - you want to search for 'element'
| x, Node(l,y,r) when x>y ->
BinarySearch r element
| x,Node(l,y,r) -> // You can simplify the code by omitting the 'when'
true // clause (because this will only be reached when
// x=y. Then you can omit the last (unreachable) case
let rec BinarySearch tree element =
match tree with
| Leaf -> false
| Node(l, v, r) ->
if v = element then
true
elif v < element then
BinarySearch r element
else
BinarySearch l element
BinarySearch makeTree 5
I have two snippets of code that tries to convert a float list to a Vector3 or Vector2 list. The idea is to take 2/3 elements at a time from the list and combine them as a vector. The end result is a sequence of vectors.
let rec vec3Seq floatList =
seq {
match floatList with
| x::y::z::tail -> yield Vector3(x,y,z)
yield! vec3Seq tail
| [] -> ()
| _ -> failwith "float array not multiple of 3?"
}
let rec vec2Seq floatList =
seq {
match floatList with
| x::y::tail -> yield Vector2(x,y)
yield! vec2Seq tail
| [] -> ()
| _ -> failwith "float array not multiple of 2?"
}
The code looks very similiar and yet there seems to be no way to extract a common portion. Any ideas?
Here's one approach. I'm not sure how much simpler this really is, but it does abstract some of the repeated logic out.
let rec mkSeq (|P|_|) x =
seq {
match x with
| P(p,tail) ->
yield p
yield! mkSeq (|P|_|) tail
| [] -> ()
| _ -> failwith "List length mismatch" }
let vec3Seq =
mkSeq (function
| x::y::z::tail -> Some(Vector3(x,y,z), tail)
| _ -> None)
As Rex commented, if you want this only for two cases, then you probably won't have any problem if you leave the code as it is. However, if you want to extract a common pattern, then you can write a function that splits a list into sub-list of a specified length (2 or 3 or any other number). Once you do that, you'll only use map to turn each list of the specified length into Vector.
The function for splitting list isn't available in the F# library (as far as I can tell), so you'll have to implement it yourself. It can be done roughly like this:
let divideList n list =
// 'acc' - accumulates the resulting sub-lists (reversed order)
// 'tmp' - stores values of the current sub-list (reversed order)
// 'c' - the length of 'tmp' so far
// 'list' - the remaining elements to process
let rec divideListAux acc tmp c list =
match list with
| x::xs when c = n - 1 ->
// we're adding last element to 'tmp',
// so we reverse it and add it to accumulator
divideListAux ((List.rev (x::tmp))::acc) [] 0 xs
| x::xs ->
// add one more value to 'tmp'
divideListAux acc (x::tmp) (c+1) xs
| [] when c = 0 -> List.rev acc // no more elements and empty 'tmp'
| _ -> failwithf "not multiple of %d" n // non-empty 'tmp'
divideListAux [] [] 0 list
Now, you can use this function to implement your two conversions like this:
seq { for [x; y] in floatList |> divideList 2 -> Vector2(x,y) }
seq { for [x; y; z] in floatList |> divideList 3 -> Vector3(x,y,z) }
This will give a warning, because we're using an incomplete pattern that expects that the returned lists will be of length 2 or 3 respectively, but that's correct expectation, so the code will work fine. I'm also using a brief version of sequence expression the -> does the same thing as do yield, but it can be used only in simple cases like this one.
This is simular to kvb's solution but doesn't use a partial active pattern.
let rec listToSeq convert (list:list<_>) =
seq {
if not(List.isEmpty list) then
let list, vec = convert list
yield vec
yield! listToSeq convert list
}
let vec2Seq = listToSeq (function
| x::y::tail -> tail, Vector2(x,y)
| _ -> failwith "float array not multiple of 2?")
let vec3Seq = listToSeq (function
| x::y::z::tail -> tail, Vector3(x,y,z)
| _ -> failwith "float array not multiple of 3?")
Honestly, what you have is pretty much as good as it can get, although you might be able to make a little more compact using this:
// take 3 [1 .. 5] returns ([1; 2; 3], [4; 5])
let rec take count l =
match count, l with
| 0, xs -> [], xs
| n, x::xs -> let res, xs' = take (count - 1) xs in x::res, xs'
| n, [] -> failwith "Index out of range"
// split 3 [1 .. 6] returns [[1;2;3]; [4;5;6]]
let rec split count l =
seq { match take count l with
| xs, ys -> yield xs; if ys <> [] then yield! split count ys }
let vec3Seq l = split 3 l |> Seq.map (fun [x;y;z] -> Vector3(x, y, z))
let vec2Seq l = split 2 l |> Seq.map (fun [x;y] -> Vector2(x, y))
Now the process of breaking up your lists is moved into its own generic "take" and "split" functions, its much easier to map it to your desired type.