Homework 8
This commit is contained in:
parent
cb78e90533
commit
61e0d66f6a
58
src/main/scala/mipt/homework8/Task1.scala
Normal file
58
src/main/scala/mipt/homework8/Task1.scala
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package mipt.homework8
|
||||||
|
|
||||||
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import mipt.utils.Homeworks._
|
||||||
|
|
||||||
|
object Task1 {
|
||||||
|
|
||||||
|
def foldF[A, B](in: Seq[Future[A]], zero: B, op: (B, A) => B)(
|
||||||
|
implicit executionContext: ExecutionContext
|
||||||
|
): Future[B] =
|
||||||
|
task"""
|
||||||
|
Реализуйте функцию, которая выполнит свертку (fold) входящей последовательности из Future,
|
||||||
|
используя переданный комбинатор и начальное значение для свертки.
|
||||||
|
Если какая-либо из исходных Future зафейлилась, то должна вернуться ошибка от нее
|
||||||
|
""" (1, 1)
|
||||||
|
|
||||||
|
def flatFoldF[A, B](in: Seq[Future[A]], zero: B, op: (B, A) => Future[B])(
|
||||||
|
implicit executionContext: ExecutionContext
|
||||||
|
): Future[B] =
|
||||||
|
task"""
|
||||||
|
Реализуйте функцию, которая выполнит свертку (fold) входящей последовательности из Future,
|
||||||
|
используя переданный асинхронный комбинатор и начальное значение для свертки.
|
||||||
|
Если какая-либо из исходных Future зафейлилась, то должна вернуться ошибка от нее.
|
||||||
|
Если комбинатор зафейлился, то должна вернуться ошибка от него.
|
||||||
|
""" (1, 2)
|
||||||
|
|
||||||
|
def fullSequence[A](futures: List[Future[A]])(
|
||||||
|
implicit ex: ExecutionContext
|
||||||
|
): Future[(List[A], List[Throwable])] =
|
||||||
|
task"""
|
||||||
|
В данном задании Вам предлагается реализовать функцию fullSequence,
|
||||||
|
похожую на Future.sequence, но в отличии от нее,
|
||||||
|
возвращающую все успешные и не успешные результаты.
|
||||||
|
Возвращаемое тип функции - кортеж из двух списков,
|
||||||
|
в левом хранятся результаты успешных выполнений,
|
||||||
|
в правово результаты неуспешных выполнений.
|
||||||
|
Не допускается использование методов объекта Await и мутабельных переменных var
|
||||||
|
""" (1, 3)
|
||||||
|
|
||||||
|
def traverse[A, B](in: List[A])(fn: A => Future[B])(
|
||||||
|
implicit ex: ExecutionContext
|
||||||
|
): Future[List[B]] =
|
||||||
|
task"""
|
||||||
|
Реализуйте traverse c помощью метода Future.sequence
|
||||||
|
""" (1, 4)
|
||||||
|
|
||||||
|
def mapReduce[A, B, B1 >: B](in: List[A], map: A => Future[B], reduce: (B1, B1) => B1)(
|
||||||
|
implicit ex: ExecutionContext
|
||||||
|
): Future[B1] =
|
||||||
|
task"""
|
||||||
|
Реализуйте алгоритм map/reduce.
|
||||||
|
Исходный список обрабатывается параллельно (конкурентно) с помощью применения функции map к каждому элементу
|
||||||
|
Результаты работы функции map должны быть свернуты в одно значение функцией reduce
|
||||||
|
Если в ходе выполнения какой-либо операции возникло исключение - эту обработку нужно игнорировать
|
||||||
|
Если ни один вызов map не завершился успешно, вернуть зафейленную фьючу с исключением UnsupportedOperationException
|
||||||
|
""" (1, 5)
|
||||||
|
|
||||||
|
}
|
181
src/test/scala/mipt/homework8/Task1Spec.scala
Normal file
181
src/test/scala/mipt/homework8/Task1Spec.scala
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package mipt.homework8
|
||||||
|
|
||||||
|
import org.scalatest.flatspec.AnyFlatSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import mipt.homework8.Task1._
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
class Task1Spec extends AnyFlatSpec with Matchers {
|
||||||
|
|
||||||
|
"foldF" should "fold list of futures into a single future using combining function" in new WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
val input = Seq(1, 2, 3, 4, 5, 6).map(Future.successful)
|
||||||
|
|
||||||
|
await(foldF[Int, Int](input, 0, _ + _)) shouldBe 21
|
||||||
|
await(foldF[Int, Int](input, 1, _ * _)) shouldBe 720
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"flatFoldF" should "fold list of futures into a single future using async combining function" in new WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
val input = Seq(1, 2, 3, 4, 5, 6).map(Future.successful)
|
||||||
|
val sum = (a: Int, b: Int) => Future.successful(a + b)
|
||||||
|
val product = (a: Int, b: Int) => Future.successful(a * b)
|
||||||
|
|
||||||
|
await(flatFoldF(input, 0, sum)) shouldBe 21
|
||||||
|
await(flatFoldF(input, 1, product)) shouldBe 720
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"full sequence" should "process list of success futures" in new WithLimitedExecutionContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* best answer will process task with 9 runnable
|
||||||
|
* good answer will process task with 12 runnable
|
||||||
|
* satisfied answer will process task with any number of runnable
|
||||||
|
* choose which one you want
|
||||||
|
* */
|
||||||
|
val limit = 100
|
||||||
|
|
||||||
|
implicit val exec: ExecutionContext = limitedExec(limit)
|
||||||
|
val fut1 = fut(1)
|
||||||
|
val fut2 = fut(2)
|
||||||
|
val fut3 = fut(3)
|
||||||
|
|
||||||
|
assert(await(fullSequence[Int](List(fut1, fut2, fut3))) === (List(1, 2, 3), List()))
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "process list of success and failures" in new WithLimitedExecutionContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* best answer will process task with 7 runnable
|
||||||
|
* good answer will process task with 8 runnable
|
||||||
|
* satisfied answer will process task with any number of runnable
|
||||||
|
* choose which one you want
|
||||||
|
* */
|
||||||
|
val limit = 100
|
||||||
|
|
||||||
|
implicit val exec: ExecutionContext = limitedExec(limit)
|
||||||
|
val ex1 = new Exception("ex1")
|
||||||
|
val ex2 = new Exception("ex2")
|
||||||
|
val failed1 = Future.failed(ex1)
|
||||||
|
val failed2 = Future.failed(ex2)
|
||||||
|
val fut1 = fut(1)
|
||||||
|
|
||||||
|
assert(await(fullSequence[Int](List(fut1, failed1, failed2))) === (List(1), List(ex1, ex2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "process list of failures" in new WithLimitedExecutionContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* best answer will process task with 4 runnable
|
||||||
|
* satisfied answer will process task with any number of runnable
|
||||||
|
* choose which one you want
|
||||||
|
* */
|
||||||
|
val limit = 100
|
||||||
|
|
||||||
|
implicit val exec: ExecutionContext = limitedExec(limit)
|
||||||
|
val ex1 = new Exception("ex1")
|
||||||
|
val ex2 = new Exception("ex2")
|
||||||
|
val failed1 = Future.failed(ex1)
|
||||||
|
val failed2 = Future.failed(ex2)
|
||||||
|
|
||||||
|
assert(await(fullSequence[Int](List(failed1, failed2))) === (List(), List(ex1, ex2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
"traverse via sequence" should "behave as a scala Future.traverse" in new WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
val xs = (1 to 10).toList
|
||||||
|
|
||||||
|
val result = await(traverse(xs)(fut))
|
||||||
|
|
||||||
|
assert(result === await(Future.traverse(xs)(fut)))
|
||||||
|
assert(result === xs)
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "work with empty lists" in new WithGlobalExecutionContext {
|
||||||
|
assert(await(traverse(Nil)(fut)) === Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "correctly stop on failures" in new WithGlobalExecutionContext {
|
||||||
|
case class MyError() extends Exception
|
||||||
|
|
||||||
|
val xs = (1 to 100).toList
|
||||||
|
|
||||||
|
try {
|
||||||
|
await(traverse(xs)(v => {
|
||||||
|
if (v == 42) throw MyError();
|
||||||
|
fut(v)
|
||||||
|
}))
|
||||||
|
assert(false)
|
||||||
|
} catch {
|
||||||
|
case MyError() => assert(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"mapReduce" should "asynchronously map and reduce the list and skip failures" in new WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
val xs = (1 to 10).toList
|
||||||
|
val predicate: Int => Boolean = a => a == 5 || a == 6
|
||||||
|
val map: Int => Future[Int] = a =>
|
||||||
|
if (predicate(a))
|
||||||
|
Future.failed(new RuntimeException)
|
||||||
|
else Future.successful(a * 2)
|
||||||
|
|
||||||
|
val reduce: (Int, Int) => Int = _ + _
|
||||||
|
await(mapReduce(xs, map, reduce)) shouldBe xs.filterNot(predicate).map(_ * 2).sum
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "throw UnsupportedOperationException if all elements mapping is failed" in new WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
val xs = (1 to 10).toList
|
||||||
|
val map: Int => Future[Int] = _ => Future.failed(new RuntimeException)
|
||||||
|
val reduce: (Int, Int) => Int = _ + _
|
||||||
|
|
||||||
|
try {
|
||||||
|
await(mapReduce(xs, map, reduce))
|
||||||
|
assert(false)
|
||||||
|
} catch {
|
||||||
|
case _: UnsupportedOperationException => assert(true)
|
||||||
|
case _: Throwable => assert(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def await[A](future: Future[A]): A = Await.result(future, Duration.Inf)
|
||||||
|
|
||||||
|
def fut(i: Int)(implicit ex: ExecutionContext): Future[Int] = Future {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WithLimitedExecutionContext {
|
||||||
|
def limitedExec(limit: Int): ExecutionContext = new ExecutionContext {
|
||||||
|
val counter = new AtomicInteger(0)
|
||||||
|
val global: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
|
override def execute(runnable: Runnable): Unit = {
|
||||||
|
counter.incrementAndGet()
|
||||||
|
if (counter.get() > limit) {
|
||||||
|
throw new Exception("Runnable limit reached, You can do better :)")
|
||||||
|
} else {
|
||||||
|
global.execute(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def reportFailure(cause: Throwable): Unit = ???
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WithGlobalExecutionContext {
|
||||||
|
|
||||||
|
implicit val executionContext: ExecutionContext = scala.concurrent.ExecutionContext.global
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue