diff --git a/scala_project_2025/.gitignore b/scala_project_2025/.gitignore new file mode 100644 index 0000000..a6a2665 --- /dev/null +++ b/scala_project_2025/.gitignore @@ -0,0 +1 @@ +Learn Concurrent Programming in Scala.pdf diff --git a/scala_project_2025/ConcurrencyTroubles.scala b/scala_project_2025/ConcurrencyTroubles.scala new file mode 100644 index 0000000..d38eec2 --- /dev/null +++ b/scala_project_2025/ConcurrencyTroubles.scala @@ -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) + } + } + +} \ No newline at end of file diff --git a/scala_project_2025/Project_2025.pdf b/scala_project_2025/Project_2025.pdf new file mode 100644 index 0000000..aa4295c Binary files /dev/null and b/scala_project_2025/Project_2025.pdf differ diff --git a/scala_project_2025/bank_system/build.sbt b/scala_project_2025/bank_system/build.sbt new file mode 100644 index 0000000..70dfc61 --- /dev/null +++ b/scala_project_2025/bank_system/build.sbt @@ -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") + diff --git a/scala_project_2025/bank_system/src/main/scala/Account.scala b/scala_project_2025/bank_system/src/main/scala/Account.scala new file mode 100644 index 0000000..30036e7 --- /dev/null +++ b/scala_project_2025/bank_system/src/main/scala/Account.scala @@ -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 = ??? + +} diff --git a/scala_project_2025/bank_system/src/main/scala/Bank.scala b/scala_project_2025/bank_system/src/main/scala/Bank.scala new file mode 100644 index 0000000..c669268 --- /dev/null +++ b/scala_project_2025/bank_system/src/main/scala/Bank.scala @@ -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] = ??? +} diff --git a/scala_project_2025/bank_system/src/main/scala/Main.scala b/scala_project_2025/bank_system/src/main/scala/Main.scala new file mode 100644 index 0000000..bfcd923 --- /dev/null +++ b/scala_project_2025/bank_system/src/main/scala/Main.scala @@ -0,0 +1,13 @@ + +object Main extends App { + + def thread(body: => Unit): Thread = { + val t = new Thread { + override def run() = body + } + + t.start + t + } + +} diff --git a/scala_project_2025/bank_system/src/main/scala/Transaction.scala b/scala_project_2025/bank_system/src/main/scala/Transaction.scala new file mode 100644 index 0000000..1f3b9c5 --- /dev/null +++ b/scala_project_2025/bank_system/src/main/scala/Transaction.scala @@ -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 + +} diff --git a/scala_project_2025/bank_system/src/test/scala/AccountTests.scala b/scala_project_2025/bank_system/src/test/scala/AccountTests.scala new file mode 100644 index 0000000..2880188 --- /dev/null +++ b/scala_project_2025/bank_system/src/test/scala/AccountTests.scala @@ -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) + } + + +}