scp1: report

This commit is contained in:
2025-10-14 17:32:40 +02:00
parent 978848c291
commit 93a9d883f2
2 changed files with 192 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
---
date: 2025-10-14
author: fredrik robertsen
title: scala project part 1
---
## task 1
### a)
to create a range from 1 through 50 explicitly with a for-loop, we can
initialize a mutable collection and update it.
```scala
// immutable pointer
val values = new collection.mutable.ArrayBuffer[Int]
for (i <- 1 to 50)
values.append(i)
```
### b)
we can sum numbers in an integer array `Array[Int]` by iterating over the
numbers and updating a mutable variable `result`.
```scala
def iterative_sum(numbers: Array[Int]): Int =
var result = 0
for (num <- numbers)
result += num
return result
```
note: the `return` keyword can be omitted, which will be done in later tasks.
### c)
as usual, we can use an auxiliary function to carry the accumulated value of the
recursion, thus yielding a tail-recursive sum. the compiler may or may not
optimize tail-recursive functions in scala. we can force it to do so using the
`@tailrec` annotation on our recursive auxiliary function `inner`.
```scala
def recursive_sum(numbers: Array[Int]): Int =
@tailrec def inner(numbers: Array[Int], accumulator: Int): Int =
if numbers.length <= 0 then accumulator
else inner(numbers.tail, numbers.head + accumulator)
inner(numbers, 0)
```
### d)
we can use the classic definition for fibonacci numbers to obtain a simple
recursive function. the main point here is that we make the base-case
a `BigInt`, such that subsequent arithmetic conforms to this type, thus
satisfying the specified return-type.
```scala
def fibonacci(n: Int): BigInt =
if n <= 1 then BigInt(1)
else fibonacci(n - 1) + fibonacci(n - 2)
```
note: the aforementioned `return` statements have been omitted.
## task 2
### a-b)
i reimplemented my oz-functions in scala. the first function finds the roots of
a second degree polynomial optionally if there are any real solutions. the main
difference is how we communicate to the caller that there were no real
solutions.
in oz we use the named return variable `RealSol` for the `proc`ess
`QuadraticEquation`.
```oz
proc {QuadraticEquation A B C ?RealSol ?X1 ?X2} Discriminant in
Discriminant = B*B - 4.0*A*C
RealSol = Discriminant >= 0.0
if RealSol then
X1 = (~B - {Sqrt Discriminant})/(2.0*A)
X2 = (~B + {Sqrt Discriminant})/(2.0*A)
end
end
```
in scala we use an `Option` that is either `Some(x)` or `None`. we encode the
two solutions as a tuple. note: if there is only one unique solution, both
values in the tuple will be equal.
```scala
def quadratic(a: Double, b: Double, c: Double): Option[(Double, Double)] =
val disc = math.pow(b, 2) - 4 * a * c
if disc < 0 then None
else Some((-b - math.sqrt(disc)) / (2 * a), (-b + math.sqrt(disc)) / (2 * a))
```
the last function encodes a polynomial as a sort of curried function that takes
in the coefficients and returns a function that evaluates the given polynomial
at that point.
```oz
fun {Quadratic A B C}
fun {$ X} A*X*X + B*X + C end
end
```
```scala
def polynomial(a: Double, b: Double, c: Double): Double => Double =
(x: Double) => a * x * x + b * x + c
```
the solutions for anonymous function return values are vastly similar between
the languages.
## task 3
### a)
we can instantiate a `Thread` object and override its `run` method with our own
`execute` function. note: the input function `execute` is similar to a void
function in C and denotes some effectual action that likely modifies some state.
we must take care to avoid race conditions when initializing multiple such
threads.
```scala
def initialize_thread(execute: () => Unit): Thread =
new Thread { override def run = execute() }
```
### b)
the code runs endlessly and is supposed to have one value increment, while the
other decrements, as if moving one unit from one stack to another, maintaining
the same number of total such units. we start with 1000 units in one stack and
0 in the other.
however, in the course of the program, the scheduler on my computer lets the
threads we create to perform this task of moving units run in a weird order,
thus ending up increasing the total amount of units we have available through
a race condition.
this is a classic asynchronous programming pitfall which would be devastating
for applications like a bank, where the units would be money, and such
concurrent transactions would yield an unintentional net increase or decrease
in someone's balance.
this could very much happen in oz as well. however, oz is made with concurrency
in mind, making it harder to make such a mistake.
### c)
the easiest (least-effort) way to modify the code such that it is correct and
thread-safe is to lock the critical section using scala's `synchronized` method
```scala
def execute(): Unit = {
while (true) {
this.synchronized { // force serialization
moveOneUnit()
updateSum()
}
Thread.sleep(50)
}
}
```
another way to make it safe is to lock the internal values before updating them,
for example using atomic data types such as `AtomicInteger`s
```scala
object ConcurrencyTroubles {
private var value1: AtomicInteger = new AtomicInteger(1000)
private var value2: AtomicInteger = new AtomicInteger(0)
private var sum: AtomicInteger = new AtomicInteger(0)
def moveOneUnit(): Unit = {
value1.decrementAndGet()
value2.incrementAndGet()
value1.compareAndSet(0, 1000)
value2.compareAndSet(1000, 0)
}
def updateSum(): Unit = {
sum.setRelease(value1.get() + value2.get())
}
...
}
```

Binary file not shown.