Hey guys I'm working on a function that will output a the board of a board game as a string.
Currently I have it working so that I can output the board without any pieces.
I'm trying to add a filter so that if the current (x, y) coordinate is in the list of tuples [Position, Col] where Position is (Int, Int).
If (x, y) is in this list then I want to check the colour Col and then output accordingly.
drawBoardCell :: (Int, Int) ->[(Position, Col)] -> String
drawBoardCell (x, y) pieces = do
let test = filter (\((a,b),_) -> a == x && b == y) pieces
if snd(test) == Black
then " b "
else if snd(test) == White
then " w "
else " . "
This is what i have tried so far and run into the error:
Display.hs:47:14: error:
• Couldn't match expected type ‘(a0, Col)’
with actual type ‘[((Int, Int), Col)]’
• In the first argument of ‘snd’, namely ‘(test)’
In the first argument of ‘(==)’, namely ‘snd (test)’
In the expression: snd (test) == Black
Display.hs:49:19: error:
• Couldn't match expected type ‘(a1, Col)’
with actual type ‘[((Int, Int), Col)]’
• In the first argument of ‘snd’, namely ‘(test)’
In the first argument of ‘(==)’, namely ‘snd (test)’
In the expression: snd (test) == White
filter returns a list, but snd expects a tuple. These are different types.
You could tweak your conditional as
if snd(head test) == Black
then " b "
else if snd(head test) == White
then " w "
else " . "
but what if test is an empty list, []?
You could code this as
if not (null test) && snd(head test) == Black
then " b "
else not (null test) && if snd(head test) == White
then " w "
else " . "
but this is not a very nice Haskell. Instead, we can get a bit better code with pattern matching:
case test of
((_,Black):_) -> " b "
((_,White):_) -> " w "
_ -> " . "
Or restructure your code to use
find :: (a -> Bool) -> [a] -> Maybe a
instead of the
filter :: (a -> Bool) -> [a] -> [a]
You can use Hoogle to find out more.
The problem is that test is still a list [(Position, Col)], so it can't be passed into snd which expects a single tuple (a, b). If you can guarantee that there will be a match in your pieces array, then you should change your body to:
drawBoardCell (x, y) pieces =
let [match] = filter (\((a,b),_) -> a == x && b == y) pieces
case match of
(_, Black) -> " b "
(_, White) -> " w "
_ -> " . "
Note the use of case here instead of if. It's not strictly necessary, but it's cleaner when comparing a value against multiple options to match this way.
If, however, as I suspect is the case, you might have 0 or 1 matches, you need to use something like the following instead:
drawBoardCell (x, y) pieces = do
case filter (\((a,b),_) -> a == x && b == y) pieces of
[(_, Black)] -> " b "
[(_, White)] -> " w "
_ -> " . "
This will print " . " if the value is not present in the filtered list, if the value is present multiple times, or if the Col of the single match is neither White nor Black.
It is pointed out in the comments that you may want to handle no matches and multiple matches differently. In this case:
drawBoardCell (x, y) pieces = do
case filter (\((a,b),_) -> a == x && b == y) pieces of
[(_, Black)] -> " b "
[(_, White)] -> " w "
[] -> " . " -- Case of no matches
_ -> error "Multiple pieces cannot occupy the same position " ++ show (x, y)
This example will cause a Runtime error if the list has multiple matches (which can be useful for debugging).
Related
I'm having a problem while trying to increment my value of x inside the inner foldl call. I make x equal to shiftValue that's passed in and attempt to increment it whenever I find a #" " or #"*" in the inner foldl call, but the value of x that gets returned is always the same as shiftvalue was when passed in.
The function takes in a tuple of (string, int) where the string will have leading spaces and asterisk chopped off that come before any other characters. Also any spaces or asterisk on the end not followed by any other characters will get chopped off. The int that is passed in is a shiftValue that tracks how many spaces the string was shifted over before getting passed into this function. Whenever I take off a leading space or asterisk I need to increment the shiftValue "x" by one.
The inner foldl call removes asterisks and spaces from the front. The outer foldl call removes them from the back. The asterisks and spaces get removed right, the x value just isn't getting updated.
(*Take string str and get rid of leading and following #"*"s and #" "s. For every
leading #"*" or #" " removed increment the shiftValue returned in the tuple*)
fun trimStarsOnNode (str, shiftValue) =
let
val x = shiftValue
in
((implode(rev (foldl (fn (cur, a) =>
if length a = 0 andalso cur = #"*" then a # []
else
if length a = 0 andalso cur = #" " then a # []
else a # [cur]) [] (rev (foldl (fn (cur, a) =>
if length a = 0 andalso cur = #"*" then (x = x + 1; a # [])
else
if length a = 0 andalso cur = #" " then (x = x + 1; a # [])
else a # [cur]) [] (explode str)))))), x)
end;
trimStarsOnNode ("***hello", 3); (* Should print out ("hello", 6) *) but prints out ("hello", 3)
Look at your x - in the beginning of your function, you do:
val x = shiftValue
Then, later, you try to do this:
x = x + 1
Remember, in SML, you can't change the value of a variable (actually, they're just called values in SML, for that reason). x = x + 1 just compares x and x + 1, so the value of the statement x = x + 1 is boolean false.
As Tayacan says, variables are not mutable in SML. If you want mutability you need to use reference types -- but usually, they are best avoided and it's preferable to stick to functional style.
It's also worth noting that your function is going to be very inefficient (O(n^2)), because of your use of list concatenation and length on every iteration. And it is incorrect, because it will also remove stars in the middle of the string (and then redundantly go over the whole list a second time). Finally, your solution is far too complicated.
FWIW, here is the shortest implementation I can think of, using the Substring library module and the function composition operator o:
fun isStarOrSpace c = (c = #"*" orelse c = #" ")
val trimStars =
let open Substring
in string o dropl isStarOrSpace o dropr isStarOrSpace o full end
This does not use your shiftValue because I don't understand what it's supposed to do. You can easily compute the number of removed characters by comparing the old and new string size. That is, your intended function (IIUC) could easily be expressed on top of mine as
fun trimStarsOnNode(s, shift) =
let val s' = trimStars s in (s', size s - size s' + shift) end
But to be honest, I don't understand what this version would be good for.
Edit: A version that returns the left drop count:
fun trimStars s =
let
open Substring
val ss = dropl isStarOrSpace (dropr isStarOrSpace (full s))
in
(string ss, #2(base ss))
end
I am reading Learn you a Haskell for great good and on page 40 - as-patterns.
I have changed the example slightly to be:
firstLetter :: String -> String
firstLetter "" = "Empty string, oops"
firstLetter all#(x:xs) = "The first letter of " ++ all ++ " is " ++ [x] ++ " otherbit " ++ xs
Then can use like this:
*Main> firstLetter "Qwerty"
"The first letter of Qwerty is Q otherbit werty"
But I was confused about the difference between [x] and x and why I have to use [x] in the above example.
For example if I change to
firstLetter :: String -> String
firstLetter "" = "Empty string, oops"
firstLetter all#(x:xs) = "The first letter of " ++ all ++ " is " ++ x ++ " otherbit " ++ xs
I get error:
Couldn't match expected type `[Char]' with actual type `Char'
In the first argument of `(++)', namely `x'
In the second argument of `(++)', namely `x ++ " otherbit " ++ xs'
In the second argument of `(++)', namely
`" is " ++ x ++ " otherbit " ++ xs'
I can use xs to print "werty" but have to use [x] to print "Q". Why is that?
What does [x] mean?
In the (x:xs), : just delimits each element, so x is the first element. Why can I not print by using x?
Also xs is of what type? list of values? So does this mean x is an element and xs must be of type list?
++ is for concatenating lists:
(++) :: [a] -> [a] -> [a]
[x] is a list, x is not a list.
firstLetter (x:xs) is an example of pattern matching.
(:) :: a -> [a] -> [a]
This operator adds element before list.
Therefore type of x is element and type of xs is list of elements.
It is common in Haskell to add suffix 's' to lists' names.
String is defined as type String = [Char].
"Qwerty" is shorthand for ['Q', 'w', 'e', 'r', 't', 'y']; this in turn is shorthand for 'Q' : 'w' : 'e' : 'r' : 't' : 'y' : [].
So when you match x : xs against "Qwerty", you get x = 'Q' and xs = "werty".
x : xs
('Q') : ('w' : 'e' : 'r' : 't' : 'y' : [])
Note: x = 'Q', NOT x = "Q". 'Q' is a Char and "Q" is a String (i.e. a [Char]). But if you have 'Q' and you want "Q", you can write ['Q'] because "Q" is just shorthand for ['Q'].
So the short answer is that you have to do it to make the types match up. [x] is a list of length one, its single element being x.
In this:
firstLetter all#(x:xs)
= "The first letter of " ++ all ++ " is " ++ x ++ " otherbit " ++ xs
You're building up the string to be printed using ++. That has type
(++) :: [a] -> [a] -> [a]
i.e. ++ takes two lists, and gives you back a list.
But x is not a list. So you get a type error.
Instead you use
"The first letter of " ++ all ++ " is " ++ [x] ++ " otherbit " ++ xs
Now all the parameters to ++ are lists, and all the types match up.
But you could instead use
"The first letter of " ++ all ++ " is " ++ x : " otherbit " ++ xs
because the type of : is given by
(:) :: a -> [a] -> [a]
[x] means "a list containing only the element x".
As a String in Haskell is a list of characters, if x is the character 'Q' then [x] is the string "Q".
The ++ operator expects to receive two strings as arguments, so you can't give it only a character.
That's what the compiler is telling you: Couldn't match expected type `[Char]' with actual type `Char' means that the compiler was expecting an argument of type [Char] (which is a list of character, the same as String) but you are passing it a Char.
One could also note that in many programming languages the concatenation operator would either be overloaded so that there are several implementations of it depending on the types on both sides, or the arguments would be cast to strings. This is not the case in Haskell.
Would someone please explain why the code below,
let list = ["A"; "B"; "C"]
let rec processList2 aList str =
match aList with
| h::t -> let z = str + ", " + h
printfn "%s" z
processList2 t z
| [] -> aList |> ignore
returns the following,
val list : string list = ["A"; "B"; "C"]
> processList2 list "X";;
X, A
X, A, B
X, A, B, C
val it : unit = ()
>
instead of this?
val list : string list = ["A"; "B"; "C"]
> processList2 list "X";;
X, A
X, A, X, B
X, A, X, B, X, C
val it : unit = ()
>
The function is recursive and passes 'z' to 'str' with each pass, so it seems like it should work...
I really appreciate the experts' help here. I am trying to learn F# and struggling with lists.
Also, how does one declare a 'list of strings?' I had an issue where a list expected to return a unit instead of a string.
If we follow along with each step it should help us understand why you get the result you get:
processList2 list "X";;
The first iteration takes h::t or "A"::["B"; "C"]. It then sets z to "X" + ", " + "A".
The next iteration takes "B"::["C"]. It then sets z to "X, A" + ", " + "B".
As you can see "X" does not get inserted in each iteration. Rather z gets appended to and set through each iteration building on the last. To append "X" at each iteration it would need to be something like:
let list = ["A"; "B"; "C"]
// Append "X, " to each item
let mapList item = "X, " + item
// reduce to single comma seperated list
let redList l r = l + ", " + r
// apply map and reduce functions to given list
let result = list |> List.map(mapList) |> List.reduce(redList)
printfn "%s" result
If you wanted you could even use String.Join to reduce the list however that requires a few more hoops to jump through:
let list = ["A"; "B"; "C"]
let mapList item = "X, " + item
let joinList (lst:list<string>) = System.String.Join(", ", lst)
let result = list |> List.map(mapList) |> joinList
printfn "%s" result
As for you last question: how does one declare a 'list of strings?, the answer depends on what you mean by declare. Are you trying to declare a variable of that type or a parameter that accepts it?
Declaring a variable as a certain type if generally done like this: let lst:string list = ["A"; "B"; "C"] where the type is given after the : during the declaration. If it is in a parameter than you have to be a little more explicit as you have to tell the compiler that you are setting the parameters type and not the return type:
// Set the lst parameter type to be a list<string> (same as string list)
let joinList (lst:list<string>) = System.String.Join(", ", lst)
// fails as we are telling the compiler to expect a return of string list yet we are only returning string
let joinList lst:list<string> = System.String.Join(", ", lst)
// Explicitly set both return and parameters
let joinList (lst:string list):string = System.String.Join(", ", lst)
Typically this wont be required as the type inference system in F# is very good and figuring out what type you want/need in these situations.
I would like to print some rectangles one by one in a terminal like that:
4 5 7 8
2 5
3 : bool 6 : int
Which represents that, given an array a, the zone from a([2,3], [4,5]) is bool and the zone from a([5,6], [7,8]) is int.
So the key is to print a block of data in several rows, instead of 1 row as default. Does anyone know how to realize that in Ocaml?
Thank you very much!
Basically, there are two approaches possible:
accumulate your two-dimensional output and use a specialized print function which rearranges the strings in a way you wish
print to a medium with 2D capabilities like terminal or GUI element (to play with terminal screen, one can use a binding to ncurses)
The first approach is more universal and remains functional in spirit. For example:
let item1 =
[" 4 5 "
;"2 "
;"3 : bool "
]
let item2 =
[" 7 8 "
;"5 "
;"6 : int "
]
let transpose ll =
let rec pick_one ll =
match ll with
| [] -> []
| [] :: _ -> []
| _ ->
let tear (reaped, rest) l =
match l with
| [] -> assert false
| hd :: tl -> (hd :: reaped, tl :: rest)
in
let (reaped, rest) = List.fold_left tear ([], []) ll in
(reaped :: (pick_one rest))
in
pick_one ll
let multiline_print items =
let by_lines = transpose items in
let show_line line = List.iter print_string line; print_endline "" in
List.iter show_line by_lines
let _ = multiline_print [item1; item2]
Depending on your needs, you may build printf-like functionality around this.
You need to route through a "layout engine" the strings produced by the functions in your new Printf-like module.
i'm having some trouble with one part of a function. My function needs an input string of at least 3 characters to avoid error, to do this a want to add one or two "." in the var. Looks something like this:
fun function(a, b) =
if size(a) < 2 then a ^ " " else if size(a) < 3 then a ^ " "
if size(b) < 2 then b ^ " " else if size(b) < 3 then b ^ " "
the function code;
my question is, how do i end the first if line? as it is now the secound if statement get an error.
thanks / joakim
Firstly, this doesn't make any sense. An if-then-else must have all three parts: you can not omit the else, which your trailing if-then clearly does.
Secondly, multiple statements (separated by ;) are only useful when there are side-effects, which there are not. You may change your code to
fun function (a, b) =
( if size a < 2 then a ^ " " else
if size a < 3 then a ^ " " else
a
; if size b < 2 then b ^ " " else
if size b < 3 then b ^ " " else
b
)
but the result of the first statement will be discarded, and is completely useless.
Perhaps you want something more like
fun padLeft (n, a) =
if size a < n
then a ^ CharVector.tabulate(n - size a, fn _ => #" ")
else a
fun function1 (a, b) = (padLeft (3, a), padLeft (3, b))
fun function2 (a, b) = (print (padLeft (3, a)); print (padLeft (3, b)))
where function1 returns a pair using both inputs, and function2 returns unit but has a visible side-effect using both inputs.