I have a character list [#"h", #"i", #" ", #"h", #"i"] which I want to get the first word from this (the first character sequence before each space).
I've written a function which gives me this warning:
stdIn:13.1-13.42 Warning: type vars not generalized because of value
restriction are instantiated to dummy types (X1,X2,...)
Here is my code:
fun next [] = ([], [])
| next (hd::tl) = if(not(ord(hd) >= 97 andalso ord(hd) <= 122)) then ([], (hd::tl))
else
let
fun getword [] = [] | getword (hd::tl) = if(ord(hd) >= 97 andalso ord(hd) <= 122) then [hd]#getword tl else [];
in
next (getword (hd::tl))
end;
EDIT:
Expected input and output
next [#"h", #"i", #" ", #"h", #"i"] => ([#"h", #"i"], [#" ", #"h", #"i"])
Can anybody help me with this solution? Thanks!
This functionality already exists within the standard library:
val nexts = String.tokens Char.isSpace
val nexts_test = nexts "hi hi hi" = ["hi", "hi", "hi"]
But if you were to build such a function anyway, it seems that you return ([], []) sometimes and a single list at other times. Normally in a recursive function, you can build the result by doing e.g. c :: recursive_f cs, but this is assuming your function returns a single list. If, instead, it returns a tuple, you suddenly have to unpack this tuple using e.g. pattern matching in a let-expression:
let val (x, y) = recursive_f cs
in (c :: x, y + ...) end
Or you could use an extra argument inside a helper function (since the extra argument would change the type of the function) to store the word you're extracting, instead. A consequence of doing that is that you end up with the word in reverse and have to reverse it back when you're done recursing.
fun isLegal c = ord c >= 97 andalso ord c <= 122 (* Only lowercase ASCII letters *)
(* But why not use one of the following:
fun isLegal c = Char.isAlpha c
fun isLegal c = not (Char.isSpace c) *)
fun next input =
let fun extract (c::cs) word =
if isLegal c
then extract cs (c::word)
else (rev word, c::cs)
| extract [] word = (rev word, [])
in extract input [] end
val next_test_1 =
let val (w, r) = next (explode "hello world")
in (implode w, implode r) = ("hello", " world")
end
val next_test_2 = next [] = ([], [])
Related
I need to process a string using foldr where '#' means deleting the previous character. For example:
>backspace "abc#d##c"
"ac"
>backspace "#####"
""
It needs to be done using foldr through one pass of the list, without using reverse and/or (++).
Here what I have got so far:
backspace :: String -> String
backspace xs = foldr func [] xs where
func c cs | c /= '#' = c:cs
| otherwise = cs
But it just filter the '#' from the string. I thought about deleting the last element of current answer every time c == '#' and got something like that
backspace :: String -> String
backspace xs = foldr func [] xs where
func c cs | c /= '#' = c:cs
| cs /= [] = init cs
| otherwise = cs
but it is not working properly,
ghci> backspace "abc#d##c"
"abc"
You can use (Int, String) as state for your foldr where the first Int is the number of backspaces, and the String is the current string constructed.
This thus means that you can work with:
backspace :: String -> String
backspace = snd . foldr func (0, [])
where func '#' (n, cs) = (n+1, cs)
func c (n, cs)
| n > 0 = … -- (1)
| otherwise = … -- (2)
In case we have a character that is not a #, but n > 0 it means we need to remove that character, and thus ignore c and decrement n. In case n == 0 we can add c to the String.
I leave filling in the … parts as an exercise.
I am newbie to SML, trying to write recursive program to delete chars from a string:
remCharR: char * string -> string
So far wrote this non-recursive prog. Need help to write recursive one.
- fun stripchars(string,chars) = let
= fun aux c =
= if String.isSubstring(str c) chars then
= ""
= else
= str c
= in
= String.translate aux string
= end
= ;
You have already found a very idiomatic way to do this. Explicit recursion is not a goal in itself, except perhaps in a learning environment. That is, explicit recursion is, compared to your current solution, encumbered with a description of the mechanics of how you achieve the result, but not what the result is.
Here is one way you can use explicit recursion by converting to a list:
fun remCharR (c, s) =
let fun rem [] = []
| rem (c'::cs) =
if c = c'
then rem cs
else c'::rem cs
in implode (rem (explode s)) end
The conversion to list (using explode) is inefficient, since you can iterate the elements of a string without creating a list of the same elements. Generating a list of non-removed chars is not necessarily a bad choice, though, since with immutable strings, you don't know exactly how long your end-result is going to be without first having traversed the string. The String.translate function produces a list of strings which it then concatenates. You could do something similar.
So if you replace the initial conversion to list with a string traversal (fold),
fun fold_string f e0 s =
let val max = String.size s
fun aux i e =
if i < max
then let val c = String.sub (s, i)
in aux (i+1) (f (c, e))
end
else e
in aux 0 e0 end
you could then create a string-based filter function (much alike the String.translate function you already found, but less general):
fun string_filter p s =
implode (fold_string (fn (c, res) => if p c then c::res else res) [] s)
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
Except, you'll notice, it accidentally reverses the string because it folds from the left; you can fold from the right (efficient, but different semantics) or reverse the list (inefficient). I'll leave that as an exercise for you to choose between and improve.
As you can see, in avoiding String.translate I've built other generic helper functions so that the remCharR function does not contain explicit recursion, but rather depends on more readable high-level functions.
Update: String.translate actually does some pretty smart things wrt. memory use.
Here is Moscow ML's version of String.translate:
fun translate f s =
Strbase.translate f (s, 0, size s);
with Strbase.translate looking like:
fun translate f (s,i,n) =
let val stop = i+n
fun h j res = if j>=stop then res
else h (j+1) (f(sub_ s j) :: res)
in revconcat(h i []) end;
and with the helper function revconcat:
fun revconcat strs =
let fun acc [] len = len
| acc (v1::vr) len = acc vr (size v1 + len)
val len = acc strs 0
val newstr = if len > maxlen then raise Size else mkstring_ len
fun copyall to [] = () (* Now: to = 0. *)
| copyall to (v1::vr) =
let val len1 = size v1
val to = to - len1
in blit_ v1 0 newstr to len1; copyall to vr end
in copyall len strs; newstr end;
So it first calculates the total length of the final string by summing the length of each sub-string generated by String.translate, and then it uses compiler-internal, mutable functions (mkstring_, blit_) to copy the translated strings into the final result string.
You can achieve a similar optimization when you know that each character in the input string will result in 0 or 1 characters in the output string. The String.translate function can't, since the result of a translate can be multiple characters. So an alternative implementation uses CharArray. For example:
Find the number of elements in the new string,
fun countP p s =
fold_string (fn (c, total) => if p c
then total + 1
else total) 0 s
Construct a temporary, mutable CharArray, update it and convert it to string:
fun string_filter p s =
let val newSize = countP p s
val charArr = CharArray.array (newSize, #"x")
fun update (c, (newPos, oldPos)) =
if p c
then ( CharArray.update (charArr, newPos, c) ; (newPos+1, oldPos+1) )
else (newPos, oldPos+1)
in fold_string update (0,0) s
; CharArray.vector charArr
end
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
You'll notice that remCharR is the same, only the implementation of string_filter varied, thanks to some degree of abstraction. This implementation uses recursion via fold_string, but is otherwise comparable to a for loop that updates the index of an array. So while it is recursive, it's also not very abstract.
Considering that you get optimizations comparable to these using String.translate without the low-level complexity of mutable arrays, I don't think this is worthwhile unless you start to experience performance problems.
I'm practicing sml using problems from Ullman(M97) second edition. The problem I am currently working on calls for a piglatin function that takes in a word, explodes it, and checks if the first character is a vowel (a, e, i, o u). If it is a vowel, it implodes the character list back into a string and adds "yay" at the end. If the first character is not a vowel, the function then checks the rest of the characters until it comes across the first vowel. When it does, it places all characters that came before the first vowel at the end of the character list, implodes the new character list back into a string and adds "ay" to it.
For example:
- pl "able";
val it = "ableyay" : string
- pl "stripe";
val it = "ipestray" : string
fun isVowel (c::cs) =
if c = #"a" then true
else if c = #"e" then true
else if c = #"i" then true
else if c = #"o" then true
else if c = #"u" then true
else false
fun cycle nil = nil
| cycle (h :: hs) = hs # [h]
fun aL (h::hs) =
if isVowel(h) = true
then h :: hs
else aL (cycle (h :: hs))
fun plx (x) =
if isVowel x = true
then (implode x) ^ "yay"
else implode (aL (x)) ^ "ay"
fun pl (x) = plx (explode x)
I have most of the problem done, but I am stuck on why my plx function gives me this:
Error: operator and operand don't agree [tycon mismatch]
operator domain: char list list
operand: char list
in expression: aL x uncaught exception Error
and I am not sure how to fix it.
It's because the type of isVowel is char list -> bool.
If you look at aL:
fun aL (h::hs) = if isVowel(h) = true then h :: hs
else aL (cycle (h :: hs));
the isVowel(h) means that h must be a char list, and this in turn means that aL must have type char list list -> char list list, and implode (aL x) is an error.
To fix, change isVowel to char -> bool:
fun isVowel c = ...
and write isVowel (hd x) in plx.
You may find Exercism's SML track enjoyable then. There's even a Pig Latin exercise. :-)
It is quite common to explode, analyse and implode, but it isn't very efficient, and in some cases it isn't easier either. As molbdnilo pointed out, isVowel should probably accept a char as input instead of a char list:
fun isVowel c =
c = #"a" orelse
c = #"e" orelse
c = #"i" orelse
c = #"o" orelse
c = #"u"
For the function that converts a word into pig latin, you can do this entirely with string functions:
fun piglatin (word : string) =
let val firstLetter = String.sub (word, 0)
in if isVowel firstLetter
then word ^ "yay"
else String.extract (word, 1, NONE) ^ str firstLetter ^ "ay"
end
Testing this:
- piglatin "pig";
> val it = "igpay" : string
- piglatin "ant";
> val it = "antyay" : string
Now, there are corner cases:
What if the word is the empty ""?
- piglatin "";
! Uncaught exception:
! Subscript
What if the word is the uppercased "Ant"?
- piglatin "Ant";
> val it = "ntAay" : string
Those two problems will need to be addressed to make the string-based piglatin function robust and total.
Here is some feedback for the solution you posted:
Don't write if P then true else Q; write P orelse Q.
Don't write isVowel c = true; write isVowel c.
aL and plx aren't the best function names; I'm not sure exactly what they're supposed to except act as glue between pl and cycle, isVowel, explode and implode.
This is the code I have to make a palindrome function. I already created the listReverse and explode function before that I use to make the palindrome. Can someone help me finnish the palindrome function?
let rec listReverse l = match l with
|[] -> []
|head :: tail -> (listReverse tail) # [head]
(* explode : string -> char list
* (explode s) is the list of characters in the string s in the order in
* which they appear
* e.g. (explode "Hello") is ['H';'e';'l';'l';'o']
*)
let explode s =
let rec _exp i =
if i >= String.length s then [] else (s.[i])::(_exp (i+1)) in
_exp 0
let rec palindrome w =
let a = explode w in
let b = listReverse a in
if c :: d
else false
You should use the List.rev standard function to reverse lists. Ocaml being a free software, you should look at its implementation (file stdlib/list.ml)
Try to explain in plain English (not code) what you are trying to achieve when you write
if c :: d
else false
Also, note that
if foo = bar then true else false
should be simplified to
foo = bar
You can replace your if statement with this:
(* tells wheter its a palindrome or not; most is (List.length a)/2*)
let rec same l1 l2 cur most =
match l1, l2 with
| h1::t1, h2::t2 when h1 = h2 ->
if cur < most then same t1 t2 (cur+1) most
else true
| _ -> false in
same a b 0 ((List.length a)/2)
I want to write a function that taking a string and return a list of char. Here is a function, but I think it is not do what I want ( I want to take a string and return a list of characters).
let rec string_to_char_list s =
match s with
| "" -> []
| n -> string_to_char_list n
Aside, but very important:
Your code is obviously wrong because you have a recursive call for which all the parameters are the exact same one you got in. It is going to induce an infinite sequence of calls with the same values in, thus looping forever (a stack overflow won't happen in tail-rec position).
The code that does what you want would be:
let explode s =
let rec exp i l =
if i < 0 then l else exp (i - 1) (s.[i] :: l) in
exp (String.length s - 1) []
Source:
http://caml.inria.fr/pub/old_caml_site/FAQ/FAQ_EXPERT-eng.html#strings
Alternatively, you can choose to use a library: batteries String.to_list or extlib String.explode
Try this:
let explode s = List.init (String.length s) (String.get s)
Nice and simple:
let rec list_car ch =
match ch with
| "" -> []
| ch -> String.get ch 0 :: list_car (String.sub ch 1 (String.length ch - 1));;
How about something like this:
let string_to_list str =
let rec loop i limit =
if i = limit then []
else (String.get str i) :: (loop (i + 1) limit)
in
loop 0 (String.length str);;
let list_to_string s =
let rec loop s n =
match s with
[] -> String.make n '?'
| car :: cdr ->
let result = loop cdr (n + 1) in
String.set result n car;
result
in
loop s 0;;
As of OCaml 4.07 (released 2018), this can be straightforwardly accomplished with sequences.
let string_to_char_list s =
s |> String.to_seq |> List.of_seq
Here is an Iterative version to get a char list from a string:
let string_to_list s =
let l = ref [] in
for i = 0 to String.length s - 1 do
l := (!l) # [s.[i]]
done;
!l;;
My code, suitable for modern OCaml:
let charlist_of_string s =
let rec trav l i =
if i = l then [] else s.[i]::trav l (i+1)
in
trav (String.length s) 0;;
let rec string_of_charlist l =
match l with
[] -> ""
| h::t -> String.make 1 h ^ string_of_charlist t;;