You will again use the sbt project structure
cs152 (or whereever you store your git repo) hw4 src main scala hw4.scala (in the default package) test scala MyTestSuite.scala (in the default package)
All your work goes inside an
object hw4 { ... }
in hw4.scala
.
In the test cases given below, I don't use the hw4.
prefix because my test suite starts with
import hw4._
To test that the grader will be able to run your project, clone your repo into /tmp/cs152
, change to the hw4
directory, and run sbt test
.
IMPORTANT: Be sure to edit your .gitignore
file so that you don't upload a lot of unnecessary stuff into your repo! See the instructions for homework 3.
tokens
function from Lecture 7 Lab Step 4. First, accept two lists of regex, the tokens that should be reported and those that should be ignored. (The latter would contain white space and comments). Next, return a pair consisting of a list of tokens that you found and an integer that indicates the index of the first error—the position in the original string where you were unable to find a match. If the entire input string matched, return -1.Martin Odersky, the inventor of Scala likes to show off the power of Scala and functional programming with a solution of the “phone mnemonics” problem. Given a phone number, what word or word sequence spells it on a touch-tone pad? For example, given the phone number 435 569 6753, one should come up with the sequence HELLO WORLD
(and maybe others).
We'll do this in a different way than he does it in his presentations. I present this as a set of discoveries. Follow along in a worksheet, then put your work together into the final deliverable, a function val phoneMnemonics = (digits: String) => ...
that yields a List[String]
. Note that we will use NO RECURSION. That's why all the functions are written as val
.
And no constructs that we haven't covered in class. Nofor/yield
. (Usemap
andflatmap
instead.) No macros :-)
You don't have to turn in the answers to the various questions in the following description—just the phoneMnemonics
function and the various helper functions.
"2"
to "ABC"
, "3"
to "DEF"
, and so on. List
with three elements, "A"
, "B"
, "C"
. But we don't want to type all that by hand. Here is a handy function that turns a string into a list of its characters:
val characters = (s: String) => s.toList.map("" + _) // In Scala, aTransform your map by addingString
is aSeq[Char]
.map(e => (e._1, characters(e._2)))Call the result
letters
—we'll need it in the next step.
"2"
. It's just letters("2")
. What if you have two digits, say "2"
followed by "3"
? Then you want to combine all elements of letters("2")
with all elements of letters("3")
. That's similar to the flatMap
example in Lecture 6:
letters("3").flatMap(y => letters("2").map(x => x + y))Try it out and see what you get. Think about why the list order is switched.
val cats = (s: List[String], t: List[String]) => ...Implement this function.
assert(cats(letters("2"), letters("3")).toSet == Set("AD", "BD", "CD", "AE", "BE", "CE", "AF", "BF", "CF"))
wordsForDigits
that takes a string of digits between "2" and "9" and produces all strings that they can encode.
val wordsForDigits = (digits: String) => ...Hint: First map to a list of strings for each digit. Then use
reduceLeft
.assert(wordsForDigits("72252").contains("SCALA"))
/usr/share/dict/words
, as we did in Lecture 6. And remove those that start with an uppercase letter (which include a bunch of junk) as well as those of length 1. Turn it into a set for efficiency. And let's make them all uppercase. And let's add in "SCALA" which is weirdly missing.
val words = io.Source.fromURL("http://horstmann.com/sjsu/spring2018/cs152/words"). getLines.filter(w => Character.isLowerCase(w(0)) && w.length > 1). map(_.toUpperCase).toSet + "SCALA"Then improve the
wordsForDigits
function from the preceding step by filtering against the set.
wordsForDigits("72252")
, what do you get? What do you get for wordsForDigits("72253")
?We've made a lot of progress, but we aren't quite there. The problem is a little harder. Given an arbitrary number like 7225247386, one is supposed to find the words "SCALA", "IS", "FUN" (and whatever other word sequences there might be hidden).
Let's assume we have a particular way of breaking up the numbers, say into List("72252", "47", "386")
. Using wordsForDigits
, we get all the words for "72252"
, "47"
, and "386"
. Now we want to concatenate them into a sentence.
We've almost solved that problem before, when wordsForDigits
called reduce
with cats
to glue together words. Give it a try:
val wordsForDigitsSequence = (seq: List[String]) => seq.map(e => wordsForDigits(e)).reduceLeft(cats)
What do you get for wordsForDigitsSequence(List("72252", "47", "386"))?
It would be easier to read the result if the words were separated by spaces. Make a copy of cats
, call it catsSpaces
, and make it separate the concatenated words by spaces. Then call catsSpaces
instead of cats
in wordsForDigitsSequence
. What do you now get for wordsForDigitsSequence(List("72252", "47", "386"))?
We are doing good. If we know how to break up a number (such as 7225247386) into all corresponding sequences (such as 72252 47 386 and many others), then we can find the corresponding words for each sequence.
Let's find all ways that a phone number can be broken up into (non-empty) sequences. For example, the phone number 1234 has 8 such decompositions: 1234, 12 34, 1 234, 1 2 34, 123 4, 12 3 4, 1 23 4, 1 2 3 4.
That looks pretty random, but it's not so bad. Suppose you knew how to break up 234 (into 234, 23 4, 2 34, 2 3 4). Now you grow that by adding 1. There are two things you can do:
Let's do the first part.
val grow1 = (c: String, substringLists: List[List[String]]) => substrings.map(...)
For each list s
in substringLists
, you prepend c
(with ::
) the sequence List(t.toString)
.
What do you get for
grow1("1", List(List("234"), List("23", "4"), List("2", "34"), List("2", "3", "4")))
On to grow2
. There are two inputs: a string c
with a single character such as "1"
, and a List[List[String]]
such as List(List("234"), List("23", "4"), List("2", "34"), List("2", "3", "4"))
. Glue the character to the front of the first element of each list s
in substringLists
.
val grow2 = (c: String, substringLists: List[List[String]]) => map(...)
What do you get for
grow2("1", List(List("234"), List("23", "4"), List("2", "34"), List("2", "3", "4")))
Now define
val grow = (c: String, a: List[List[String]]) => ...
to concatenate grow1(c, a)
and grow2(c, a)
(with ++
).
Now you should get all expected patterns from
grow("1", List(List("234"), List("23", "4"), List("2", "34"), List("2", "3", "4")))
What is the result of
grow("1", grow("2", grow("3", List(List("4")))))
This looks so promising. But why can't you use grow
with reduceRight
?
foldRight
instead. And we want this to work for arbitrary strings s
, not just "1234"
. Apply foldRight
to the list characters(s.substring(0, s.length - 1))
. What is the seed value? val substrings = (s: String) => ...
. What is substrings("2728")
?List[String]
. What do you get when you call
substrings("2728").map(wordsForDigitsSequence)
substrings
—just figure out what you need to do with its result.)val phoneMnemonics = (digits: String) => ...
that does this for any string of digits, not just "2728"
. What is phoneMnemonics("7225247386")
? val wordsForDigits = (digits: String) => Nil val catsSpaces = (s: List[String], t: List[String]) => Nil val wordsForDigitsSequence = (seq: List[String]) => Nil val grow = (c: String, a: List[List[String]]) => Nil val substrings = (s: String) => Nil val phoneMnemonics = (digits: String) => Nil