Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module 7 #51

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ files:
visible: false
- name: build.sbt
visible: false
- name: project/build.properties
visible: true
12 changes: 6 additions & 6 deletions Functions as Data/scala_collections_overview/task-info.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
type: edu
custom_name: Scala Collections Overview
files:
- name: test/TestSpec.scala
visible: false
- name: build.sbt
visible: false
- name: src/Task.scala
visible: true
- name: test/TestSpec.scala
visible: false
- name: build.sbt
visible: false
- name: src/Task.scala
visible: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
scalaSource in Compile := baseDirectory.value / "src"
scalaSource in Test := baseDirectory.value / "test"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15"
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.5"
libraryDependencies += "org.typelevel" %% "munit-cats-effect-3" % "1.0.6" % "test"
scalaVersion := "3.3.3"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import java.nio.file.{Path, Paths}

case class Dog(id: Int, name: String, breed: String, favoriteToy: String):
def toLine: String = s"$id, $name, $breed, $favoriteToy"
override def toString: String = s"ID: $id, Name: $name, Breed: $breed, Favorite toy: $favoriteToy"

object Dog:
val FilePath: Path = Paths.get("Referential Transparency/Dog Adoption Center Exercise/src/main/resources/adoptionCenter.csv")

def fromLine(line: String): Dog =
val arr = line.split(",").map(_.trim)
Dog(arr(0).toInt, arr(1), arr(2), arr(3))

122 changes: 122 additions & 0 deletions Referential Transparency/Dog Adoption Center Exercise/src/Task.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import java.nio.file.{Files, Path}
import scala.jdk.CollectionConverters.*
import cats.effect.{IO, IOApp}
import cats.syntax.all.*

object Task extends IOApp.Simple:
/**
* The main function of our console application.
* It should parse the adoption center CSV file and then call the mainMenu function.
*/
override def run: IO[Unit] =
for {
lines <- read(Dog.FilePath)
shelter = lines.map(Dog.fromLine)
_ <- mainMenu(shelter)
} yield ()

/**
* This function just reads the input file.
* @param path -- the path to the input file
* @return lines of the input file
*/
private def read(path: Path): IO[List[String]] =
IO { Files.readAllLines(path).asScala.toList }

/**
* Displays the menu, explaining available options.
*/
private def displayMenu: IO[Unit] = IO.println (
"""
|Welcome to the Virtual Dog Adoption Center!
|------------------------------------------
|1. View all available dogs
|2. Adopt a dog
|3. Surrender a dog
|4. Exit
|Please select an option (1-4):
|""".stripMargin
)

/**
* Main application loop.
* It should display the menu, prompt the user to input an option they want to do, and execute that action.
* If the user inputs "4", the application loop should be stopped, and the shelter saved into the file.
* @param shelter -- a list of the available dogs
*/
def mainMenu(shelter: List[Dog]): IO[Unit] = for {
_ <- displayMenu
choice <- IO.readLine
newShelter <- choice match {
case "1" => viewDogs(shelter) *> IO.pure(shelter)
case "2" => adoptDog(shelter)
case "3" => surrenderDog(shelter)
case "4" => exitShelter(shelter, Dog.FilePath) *> IO.pure(shelter)
case _ => IO.println("Invalid option. Please try again.") *> IO.pure(shelter)
}
_ <- if (choice != "4") mainMenu(newShelter) else IO.unit
} yield ()

/**
* Thanks the user for visiting, and store the current state of the shelter in the file.
* @param shelter -- a list of the dogs available at the end of the session.
* @param path -- a path to the file storing the data about the shelter.
*/
private def exitShelter(shelter: List[Dog], path: Path): IO[Unit] = for {
_ <- IO.println("Thank you for visiting!")
_ <- IO { Files.deleteIfExists(path)
Files.createFile(path)
Files.writeString(path, shelter.sortBy(_.id).map(_.toLine).mkString("\n"))
}
} yield ()

/**
* Views all the dogs in the shelter.
* If the list is empty, explain that there are no dogs available to adopt
* @param shelter -- a list of the available dogs
*/
private def viewDogs(shelter: List[Dog]): IO[Unit] = for {
_ <-
if (shelter.isEmpty)
IO.println("No dogs are currently available for adoption.")
else
shelter.traverse_ { dog => IO.println(dog.toString()) }
} yield ()

/**
* Surrenders a dog to the shelter.
* It should prompt the user to input the dog's name, breed, and favorite toy.
* The dog should be assigned a new unique identifier before adding to the list.
* The function yields `newShelter`, which describes the shelter after surrendering the dog.
* @param shelter -- a list of the available dogs
*/
private def surrenderDog(shelter: List[Dog]): IO[List[Dog]] = for {
_ <- IO.print("Enter dog's name: ")
name <- IO.readLine
_ <- IO.print("Enter dog's breed: ")
breed <- IO.readLine
_ <- IO.print("Enter dog's favorite toy: ")
favoriteToy <- IO.readLine
newId = if (shelter.isEmpty) 1 else shelter.map(_.id).max + 1
newDog = Dog(newId, name, breed, favoriteToy)
newShelter = newDog :: shelter
_ <- IO.println(s"Dog ${newDog.name} has been surrendered with ID ${newDog.id}.")
} yield newShelter

/**
* Adopt a dog from the shelter.
* Should prompt the user to input the dog's identifier.
* If the ID doesn't exist, report the error to the user and exit to the main loop.
* Otherwise, congratulate the user on adoption and remove the dog from the shelter.
* The function yields `newShelter`, which describes the shelter after adopting the dog.
*
* @param shelter -- a list of the available dogs
*/
private def adoptDog(shelter: List[Dog]): IO[List[Dog]] = for {
_ <- IO.print("Enter the ID of the dog you wish to adopt: ")
idInput <- IO.readLine
id <- IO.fromOption(idInput.toIntOption)(new NumberFormatException("Invalid ID input"))
(dog, newShelter) = shelter.partition(_.id == id)
_ <- if (dog.isEmpty) IO.println("No dog found with the provided ID.")
else IO.println(s"Congratulations! You have adopted ${dog.head.name}.")
} yield newShelter
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
1, Yuki, Akita, ball
2, Hoops, Australian Shepherd, squeaky pig
3, Bowser, Chow Chow, dinosaur bone
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
type: edu
files:
- name: src/Task.scala
visible: true
placeholders:
- offset: 363
length: 105
placeholder_text: /* TODO */
- offset: 684
length: 46
placeholder_text: /* TODO */
- offset: 1488
length: 474
placeholder_text: /* TODO */
- offset: 2316
length: 223
placeholder_text: /* TODO */
- offset: 2800
length: 177
placeholder_text: /* TODO */
- offset: 3427
length: 509
placeholder_text: /* TODO */
- offset: 4456
length: 463
placeholder_text: /* TODO */
- name: test/TestSpec.scala
visible: false
propagatable: false
- name: build.sbt
visible: false
propagatable: false
- name: src/Dog.scala
visible: true
- name: src/main/resources/adoptionCenter.csv
visible: true
- name: test/resources/initial.csv
visible: false
propagatable: false
- name: test/resources/out1.csv
visible: false
propagatable: false
- name: test/resources/out2.csv
visible: false
propagatable: false
- name: test/resources/out3.csv
visible: false
propagatable: false
- name: test/resources/out4.csv
visible: false
propagatable: false
- name: test/resources/out5.csv
visible: false
propagatable: false
- name: test/resources/out6.csv
visible: false
propagatable: false
- name: test/resources/out7.csv
visible: false
propagatable: false
26 changes: 26 additions & 0 deletions Referential Transparency/Dog Adoption Center Exercise/task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@


In this exercise, you'll apply Cats Effect to implement a virtual dog adoption center.
Create a command-line application that allows one to view the available dogs, adopt or surrender a dog.
This exercise demonstrates a referentially transparent way to handle side effects such as console input/output and state management.

We will be working with a case class that models a dog from one of the previous lessons.
It describes a dog by their identifier, name, breed, and favorite toy: see `Dog.scala`.
The information about the residents of our shelter is stored in a CSV format in `src/main/resources/adoptionCenter.csv`.

As a visitor of the center, you can do one of four actions:

1. View all available dogs
2. Adopt a dog
3. Surrender a dog
4. Exit

The application should prompt the user to pick one of the options until they selected Exit.
The first option should print out all available dogs read from the file.
The second option prompts the user to select a dog to adopt by their identifier.
Finally, the third option prompts the user to input the name, breed and favorite toy of a dog they wish to surrender, assigns it a new unique identifier and adds the dog to the shelter.

Since we don't want to write into a file after every operation, the application should operate with a list of dogs in memory and only commit the result of all adoptions and surrenders on exit.
This is why the return type of `adoptDog` and `surrenderDog` is `IO[List[Dog]]`.
Since viewing dogs and exiting doesn't modify the state of our shelter, the output type of corresponding functions is `IO[Unit]`.
Make sure that a dog cannot be adopted twice, and that a surrendered dog gets a unique identifier.
Loading