scala project: init

This commit is contained in:
2025-10-14 10:18:14 +02:00
parent 1ae964c198
commit 6a786891dd
9 changed files with 384 additions and 0 deletions

1
scala_project_2025/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
Learn Concurrent Programming in Scala.pdf

View File

@@ -0,0 +1,46 @@
package example
object ConcurrencyTroubles {
private var value1: Int = 1000
private var value2: Int = 0
private var sum: Int = 0
def moveOneUnit(): Unit = {
value1 -= 1
value2 += 1
if(value1 == 0) {
value1 = 1000
value2 = 0
}
}
def updateSum(): Unit = {
sum = value1 + value2
}
def execute(): Unit = {
while(true) {
moveOneUnit()
updateSum()
Thread.sleep(50)
}
}
// This is the "main" method, the entry point of execution.
// It could have been placed in a different file.
def main(args: Array[String]): Unit = {
for (i <- 1 to 2) {
val thread = new Thread {
override def run = execute()
}
thread.start()
}
while(true) {
updateSum()
println(sum + " [" + value1 + " " + value2 + "]")
Thread.sleep(100)
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,6 @@
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.0" % "test"
resolvers += "Central" at "https://central.maven.org/maven2/"
scalacOptions := Seq("-unchecked", "-deprecation", "-feature", "-language:postfixOps")

View File

@@ -0,0 +1,11 @@
class Account(val code : String, val balance: Double) {
// TODO
// Implement functions. Account should be immutable.
// Change return type to the appropriate one
def withdraw(amount: Double) : Unit = ???
def deposit (amount: Double) : Unit = ???
}

View File

@@ -0,0 +1,65 @@
import collection.mutable.Map
class Bank(val allowedAttempts: Integer = 3) {
private val accountsRegistry : Map[String,Account] = Map()
val transactionsPool: TransactionPool = new TransactionPool()
val completedTransactions: TransactionPool = new TransactionPool()
def processing : Boolean = !transactionsPool.isEmpty
// TODO
// Adds a new transaction for the transfer to the transaction pool
def transfer(from: String, to: String, amount: Double): Unit = ???
// TODO
// Process the transactions in the transaction pool
// The implementation needs to be completed and possibly fixed
def processTransactions: Unit = {
// val workers : List[Thread] = transactionsPool.iterator.toList
// .filter(/* select only pending transactions */)
// .map(processSingleTransaction)
// workers.map( element => element.start() )
// workers.map( element => element.join() )
/* TODO: change to select only transactions that succeeded */
// val succeded : List[Transaction] = transactionsPool
/* TODO: change to select only transactions that failed */
// val failed : List[Transaction] = transactionsPool
// succeded.map(/* remove transactions from the transaction pool */)
// succeded.map(/* add transactions to the completed transactions queue */)
//failed.map(t => {
/* transactions that failed need to be set as pending again;
if the number of retry has exceeded they also need to be removed from
the transaction pool and to be added to the queue of completed transactions */
//})
if(!transactionsPool.isEmpty) {
processTransactions
}
}
// TODO
// The function creates a new thread ready to process
// the transaction, and returns it as a return value
private def processSingleTransaction(t : Transaction) : Thread = ???
// TODO
// Creates a new account and returns its code to the user.
// The account is stored in the local registry of bank accounts.
def createAccount(initialBalance: Double) : String = ???
// TODO
// Return information about a certain account based on its code.
// Remember to handle the case in which the account does not exist
def getAccount(code : String) : Option[Account] = ???
}

View File

@@ -0,0 +1,13 @@
object Main extends App {
def thread(body: => Unit): Thread = {
val t = new Thread {
override def run() = body
}
t.start
t
}
}

View File

@@ -0,0 +1,36 @@
object TransactionStatus extends Enumeration {
val SUCCESS, PENDING, FAILED = Value
}
class TransactionPool {
// Remove and the transaction from the pool
def remove(t: Transaction): Boolean = ???
// Return whether the queue is empty
def isEmpty: Boolean = ???
// Return the size of the pool
def size: Integer = ???
// Add new element to the back of the queue
def add(t: Transaction): Boolean = ???
// Return an iterator to allow you to iterate over the queue
def iterator : Iterator[Transaction] = ???
}
class Transaction(val from: String,
val to: String,
val amount: Double,
val retries: Int = 3) {
private var status: TransactionStatus.Value = TransactionStatus.PENDING
private var attempts = 0
def getStatus() = status
// TODO: Implement methods that change the status of the transaction
}

View File

@@ -0,0 +1,206 @@
import org.scalatest.FunSuite
class AccountTests extends FunSuite {
test("Test 01: Account should be immutable") {
val account = new Account("1234", 500)
val result = account.withdraw(200)
assert(account.balance == 500)
assert(account.code == "1234")
}
test("Test 02: Valid account withdrawal") {
val account = new Account("1234", 500)
val result = account.withdraw(200)
result match {
case Right(x) => assert(x.balance == 300)
case Left(x) => assert(false)
}
}
test("Test 03: Invalid account withdrawal should return error message") {
val account = new Account("1234",500)
val result = account.withdraw(1000)
assert(result.isLeft)
}
test("Test 04: Withdrawal of negative amount should return error message") {
val account = new Account("1234",500)
val result = account.withdraw(-100)
assert(result.isLeft)
}
test("Test 05: Valid account deposit") {
val account = new Account("1234",500)
val result = account.deposit(250)
assert(result.isRight)
assert(result.toOption.get.balance == 750)
}
test("Test 06: Deposit of negative amount should return an error") {
val account = new Account("1234",500)
val result = account.deposit(-50)
assert(result.isLeft)
}
test("Test 07: Correct balance amount after several withdrawals and deposits") {
var account = new Account("1234",50000)
val lock: Object = new Object
val first = Main.thread {
for (i <- 0 until 100) {
lock.synchronized {
account = account.withdraw(10).toOption.get
}
Thread.sleep(10)
}
}
val second = Main.thread {
for (i <- 0 until 100) {
lock.synchronized {
account = account.deposit(5).toOption.get
}
Thread.sleep(20)
}
}
val third = Main.thread {
for (i <- 0 until 100) {
lock.synchronized {
account = account.withdraw(50).toOption.get
}
Thread.sleep(10)
}
}
val fourth = Main.thread {
for (i <- 0 until 100) {
lock.synchronized {
account = account.deposit(100).toOption.get
}
Thread.sleep(10)
}
}
first.join()
second.join()
third.join()
fourth.join()
assert(account.balance == 54500)
}
}
class AccountTransferTests extends FunSuite {
test("Test 08: Valid transfer between accounts") {
val bank = new Bank()
val code1 = bank.createAccount(100)
val code2 = bank.createAccount(200)
bank transfer(code1, code2, 50)
bank processTransactions
while (bank.processing) {
Thread.sleep(100)
}
assert(bank.completedTransactions.iterator.toList.last.getStatus == TransactionStatus.SUCCESS)
assert(bank.getAccount(code1).get.balance == 50)
assert(bank.getAccount(code2).get.balance == 250)
}
test("Test 09: Transfer of negative amount between accounts should fail") {
val bank = new Bank()
val code1 = bank.createAccount(500)
val code2 = bank.createAccount(1000)
bank transfer(code1, code2, -100)
bank processTransactions
while (bank.processing) {
Thread.sleep(100)
}
assert(bank.completedTransactions.iterator.toList.last.getStatus == TransactionStatus.FAILED)
assert(bank.getAccount(code1).get.balance == 500)
assert(bank.getAccount(code2).get.balance == 1000)
}
test("Test 10: Invalid transfer between accounts due to insufficient funds should lead to transaction status FAILED and no money should be transferred between accounts") {
val bank = new Bank()
val code1 = bank.createAccount(100)
val code2 = bank.createAccount(1000)
bank transfer(code1, code2, 150)
bank processTransactions
while (bank.processing) {
Thread.sleep(100)
}
assert(bank.completedTransactions.iterator.toList.last.getStatus == TransactionStatus.FAILED)
assert(bank.getAccount(code1).get.balance == 100)
assert(bank.getAccount(code2).get.balance == 1000)
}
test("Test 11: Correct balance amounts after several transfers") {
val bank = new Bank()
val code1 = bank.createAccount(3000)
val code2 = bank.createAccount(5000)
val first = Main.thread {
for (i <- 0 until 100) {
bank transfer(code1, code2, 30)
}
}
val second = Main.thread {
for (i <- 0 until 100) {
bank transfer(code2, code1, 23)
}
}
first.join()
second.join()
bank processTransactions
while (bank.processing) {
Thread.sleep(100)
}
assert(bank.getAccount(code1).get.balance == 2300)
assert(bank.getAccount(code2).get.balance == 5700)
}
test("Test 12: All the submitted transactions are processed") {
val bank = new Bank()
val code1 = bank.createAccount(3000)
val code2 = bank.createAccount(5000)
val first = Main.thread {
for (i <- 0 until 100) {
bank transfer(code1, code2, 30)
}
}
val second = Main.thread {
for (i <- 0 until 100) {
bank transfer(code2, code1, 23)
}
}
first.join()
second.join()
val submitted = bank.transactionsPool.size
bank processTransactions
while (bank.processing) {
Thread.sleep(100)
}
assert(bank.completedTransactions.size == submitted)
assert(bank.transactionsPool.isEmpty)
}
}