Write a function,stubs.Guess.score
,with the following signature:
stubs.Guess.score : stubs.Guess -> stubs.Target -> [stubs.Result]
Given a 5 letter target and a 5 letter user guess, it should return the guess with each character annotated as one of the following:
- in the correct place
- in the target word but not in the correct place
- not in the target word
We've given this function a signature which suggests we've created our own data types for this step. The data constructors for these types currently contain a placeholder type,TODO
,which you can replace with what you think the type should enclose.
In Unison it's common to provide functions calledfromText
ortoMyType
which create values of a given type, for exampleText.toBag
orNat.toInt
.We should have those for our Wordle guess and target types too!
Choose your data representations wisely!
See how to write a simple Unison function together:
If you ever find yourself wondering "π€ What's the Unison name for that function which turns a List into a Set?" or "π§ How do I break apart Text into its constituent characters?" you may want to use the type-basedfind
command.
When you want to search by type, thefind
command is followed by a colon:
and then a type signature.
.> find : List a -> Set a
1. .base.Set.fromList : [k] -> Set k
.> find : Text -> [Char]
1. .base.Text.toCharList : Text -> [Char]
You might also try employing wildcard operators to help scope your search further. Say I wanted to find all the functions under theList
namespace inbase
.If I typefind List
in the UCM, I'll receive a bunch of results that include the wordList
in their name, but aren't scoped to the namespace. Instead, we can use?
to represent any results following the given prefix.
.> find .base.List.?
1. structural type .base.List.Nonempty a
2. .base.List.++ : [a] -> [a] -> [a]
3. .base.List.+: : a -> [a] -> [a]
4. .base.List.:+ : [a] -> a -> [a]
[...]
.> find .base.List.delete?
1. .base.List.deleteAt : Nat -> [a] -> [a]
2. .base.List.deleteAt.doc : Doc.Deprecated
3. .base.List.deleteAt.test : [Test.Result]
4. .base.List.deleteFirst : (a ->{g} Boolean) -> [a] ->{g} [a]
[...]
The user and target word will initially be entered as typeText
values, but dealing with the rawText
values might not be optimal for making the kinds of comparisons needed to produce a result.
Why not create three types that contain variouscollection typesand types frombase
to model this domain?
- A
Target
data type which contains enough information to check for the existence of aChar
in the target, and the location (an index perhaps? π) of a character in the target. - A
Guess
data type which contains information about the guessed word's characters and their locations. - A
Result
data type which represents the result of comparing a `type Char` from theGuess
with theTarget
.
Resources
Solutions
One possible way to model the domain:
Let's look how we might transform a raw text value into our respectivebasic.Guess
andbasic.Target
types.
First, we'll write aText.normalize
function to lowercase and strip whitespace from the incoming text. We don't want thebasic.Guess
to fail to match thebasic.Target
because of casing or whitespace issues.
Text.normalize : Text -> Text
Text.normalize : Text -> Text
Text.normalize input =
trim input |> toCharList |> List.map ascii.toLower |> fromCharList
Then we break down theText
body into aList
ofChar
with thetoCharList
function frombase.
Working in theList
type allows us to callindexed
so we can pair the character with its index for position lookup later.
basic.Guess.fromText : Text -> basic.Guess
basic.Guess.fromText input =
normalized = Text.normalize input
charList = toCharList normalized |> indexed
basic.Guess.Guess charList
We do the same sequence of transformations for thebasic.Target
type, with the additional step of callingSet.fromList
to create twoSet
sβonewhich includes the indices of the characters, and one without.
basic.Target.fromText : Text -> basic.Target
basic.Target.fromText input =
use Set fromList
normalized = Text.normalize input
charList = toCharList normalized
charsWithIndex = charList |> indexed |> fromList
setChars = fromList charList
basic.Target.Target setChars charsWithIndex
Next we'll break down the overallbasic.Guess.score
function implementation.
basic.Guess.score : basic.Guess -> basic.Target -> [basic.Result]
basic.Guess.score : basic.Guess -> basic.Target -> [basic.Result]
basic.Guess.score guess target =
(basic.Guess.Guess list) = guess
List.map (t -> basic.characterResult t target) list
For each character and index tuple in the user'sbasic.Guess
,we call thebasic.characterResult
function. It checks whether the tuple isbasic.inLocation
,or if it merelybasic.exists
in the target.
basic.characterResult : (Char, Nat) -> basic.Target -> basic.Result
basic.characterResult : (Char, Nat) -> basic.Target -> basic.Result
basic.characterResult tuple target =
match tuple with
tup
| basic.inLocation tup target ->
basic.Result.InPlace (at1 tup)
| basic.exists tup target -> basic.Result.Exists (at1 tup)
| otherwise ->
basic.Result.NotFound (at1 tup)
The two boolean functionsbasic.inLocation
andbasic.exists
both useSet.contains
in their implementations.
basic.inLocation : (Char, Nat) -> basic.Target -> Boolean
basic.inLocation : (Char, Nat) -> basic.Target -> Boolean
basic.inLocation tup = cases
basic.Target.Target _ indexedChars -> Set.contains tup indexedChars
basic.exists : (Char, Nat) -> basic.Target -> Boolean
basic.exists : (Char, Nat) -> basic.Target -> Boolean
basic.exists tup target =
(char, index) = tup
(basic.Target.Target set _) = target
Set.contains char set