├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── _config.yml ├── index.md ├── seminar ├── lesson1 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── plan.txt │ ├── project │ │ └── build.properties │ ├── smallinfo.pdf │ └── src │ │ ├── main │ │ └── scala │ │ │ └── lesson │ │ │ ├── TaskFourLoadAndSort.scala │ │ │ ├── TaskOneMergeSort.scala │ │ │ ├── TaskThreeTopN.scala │ │ │ ├── TaskTwoUniqMergeSort.scala │ │ │ └── core │ │ │ ├── DBService.scala │ │ │ ├── InputData.scala │ │ │ ├── SortUtils.scala │ │ │ └── Utils.scala │ │ └── test │ │ └── scala │ │ └── lesson │ │ ├── TaskFiveLoadAndSortSpec.scala │ │ ├── TaskFourScalaCheckSpec.scala │ │ ├── TaskOneMergeSortSpec.scala │ │ ├── TaskThreeTopNSpec.scala │ │ └── TaskTwoUniqMergeSortSpec.scala ├── lesson10 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ └── build.properties │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ ├── LogSplitter5.scala │ │ ├── SimpleStreams1.scala │ │ ├── StreamGroup4.scala │ │ ├── StreamThrottle2.scala │ │ └── StreamsAndFuture3.scala ├── lesson11 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ └── build.properties │ └── src │ │ └── main │ │ ├── resources │ │ └── logback.xml │ │ └── scala │ │ └── com │ │ └── lesson │ │ └── eleven │ │ ├── Main.scala │ │ ├── Task1.scala │ │ ├── Task2.scala │ │ ├── Task3.scala │ │ ├── Task4.scala │ │ ├── Task5.scala │ │ ├── Task6.scala │ │ ├── model │ │ ├── Animal.scala │ │ ├── DatabaseData.scala │ │ └── RegistrationData.scala │ │ └── service │ │ └── FakeDBService.scala ├── lesson2 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── lesson │ │ └── two │ │ ├── AddElementBenchmarks.scala │ │ ├── CountBenchmarks.scala │ │ ├── CreateListBenchmarks.scala │ │ ├── ExampleBenchmarks.scala │ │ ├── GetElementBenchmarks.scala │ │ └── RecursionBenchmarks.scala ├── lesson3 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── discuss │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.sbt │ │ ├── project │ │ │ ├── build.properties │ │ │ └── plugins.sbt │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ └── seminar │ │ │ │ ├── ChessBoard.scala │ │ │ │ ├── NumbersFibonacci.scala │ │ │ │ ├── NumbersPrime.scala │ │ │ │ ├── Sudoku.scala │ │ │ │ └── Terminator.scala │ │ │ └── test │ │ │ └── scala │ │ │ └── seminar │ │ │ ├── ChessBoardTest.scala │ │ │ ├── NumbersFibonacciTest.scala │ │ │ ├── NumbersPrimeTest.scala │ │ │ ├── SudokuTest.scala │ │ │ └── TerminatorTest.scala │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ └── main │ │ └── scala │ │ └── seminar │ │ └── Main.scala ├── lesson4 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ ├── src │ │ ├── main │ │ │ └── scala │ │ │ │ └── ru │ │ │ │ └── allebedev │ │ │ │ ├── first │ │ │ │ ├── Attraction.scala │ │ │ │ ├── AttractionInfo.scala │ │ │ │ ├── AttractionLocation.scala │ │ │ │ └── People.scala │ │ │ │ ├── four │ │ │ │ └── Email.scala │ │ │ │ ├── second │ │ │ │ └── User.scala │ │ │ │ └── third │ │ │ │ ├── AllThing.scala │ │ │ │ ├── AllThingsFinderRequest.scala │ │ │ │ ├── Book.scala │ │ │ │ └── BookDict.scala │ │ └── test │ │ │ └── scala │ │ │ └── ru │ │ │ └── allebedev │ │ │ ├── FirstCustomSpec.scala │ │ │ ├── FourTraitSpec.scala │ │ │ ├── ThirdGenericSpec.scala │ │ │ └── second │ │ │ └── UserSpec.scala │ └── version.sbt ├── lesson5 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ └── build.properties │ └── src │ │ ├── main │ │ ├── resources │ │ │ └── in.txt │ │ └── scala │ │ │ └── seminar │ │ │ └── Practice.scala │ │ └── test │ │ └── scala │ │ └── seminar │ │ └── PracticeTest.scala ├── lesson6 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ └── build.properties │ └── src │ │ ├── main │ │ └── scala │ │ │ └── com │ │ │ └── lesson │ │ │ └── six │ │ │ ├── Part1.scala │ │ │ ├── Part2.scala │ │ │ └── models │ │ │ ├── Field.scala │ │ │ ├── InputWebForm.scala │ │ │ ├── RegistrationData.scala │ │ │ └── ValidationError.scala │ │ └── test │ │ └── scala │ │ └── com │ │ └── lesson │ │ └── six │ │ ├── Part1Spec.scala │ │ └── Part2Spec.scala ├── lesson7 │ ├── .gitignore │ ├── .scalafmt.conf │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── project │ │ │ └── target │ │ │ └── config-classes │ │ │ ├── $09ca5039275f01d49b45$.class │ │ │ ├── $09ca5039275f01d49b45.cache │ │ │ └── $09ca5039275f01d49b45.class │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── Main.scala │ │ │ ├── RestAPI.scala │ │ │ ├── User.scala │ │ │ ├── UserActor.scala │ │ │ └── UserService.scala │ │ └── test │ │ └── scala │ │ └── RestApiSpec.scala ├── lesson8 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ ├── src │ │ ├── main │ │ │ └── scala │ │ │ │ └── ru │ │ │ │ └── allebedev │ │ │ │ ├── FutureStashActor.scala │ │ │ │ ├── ScheduleActor.scala │ │ │ │ ├── SimpleActor.scala │ │ │ │ ├── SuperActor.scala │ │ │ │ └── core │ │ │ │ ├── DataForStamp.scala │ │ │ │ ├── DbFatalExeption.scala │ │ │ │ └── FakeDbService.scala │ │ └── test │ │ │ └── scala │ │ │ └── ru │ │ │ └── allebedev │ │ │ ├── FutureStashActorSpec.scala │ │ │ ├── ScheduleActorSpec.scala │ │ │ ├── SimpleActorSpec.scala │ │ │ └── SuperActorSpec.scala │ └── version.sbt └── lesson9 │ ├── .gitignore │ ├── README.md │ ├── build.sbt │ ├── project │ └── build.properties │ └── src │ ├── main │ ├── resourses │ │ └── application.conf │ └── scala │ │ └── com │ │ ├── ClientActor.scala │ │ ├── Main.scala │ │ ├── RequestController.scala │ │ └── Worker.scala │ └── test │ └── scala │ └── com │ └── RequestControllerSpec.scala └── slides ├── Binary_search_tree.svg ├── CE-Threads-1-colored.png ├── CE-Threads-2-colored.png ├── CE-Threads-3-colored.png ├── CE-Threads-4-colored.png ├── CE-Threads-5-colored.png ├── CE-Threads-6-colored.png ├── CE-Threads-7-colored.png ├── CE-Threads-8-colored.png ├── Cons-cells.svg ├── Dynamite Effects.png ├── Paper.Scala.1.png ├── Paper.Scala.2.png ├── Paper.Scala.3.png ├── Paper.Scala.4.png ├── Paper.Scala.6.png ├── Thats all cats.png ├── Wikipedia-logo-v2.svg ├── catfunctor.png ├── cats-effect-logo.svg ├── cats.png ├── cats2.png ├── classifier-1.html ├── classifier-2.html ├── classifier-3.html ├── classifier-4.html ├── compose_shapes.png ├── cows.jpg ├── css ├── print │ ├── paper.css │ └── pdf.css ├── reveal.css ├── reveal.scss └── theme │ ├── README.md │ ├── beige.css │ ├── black.css │ ├── blood.css │ ├── league.css │ ├── moon.css │ ├── night.css │ ├── serif.css │ ├── simple.css │ ├── sky.css │ ├── solarized.css │ ├── source │ ├── beige.scss │ ├── black.scss │ ├── blood.scss │ ├── league.scss │ ├── moon.scss │ ├── night.scss │ ├── serif.scss │ ├── simple.scss │ ├── sky.scss │ ├── solarized.scss │ └── white.scss │ ├── template │ ├── mixins.scss │ ├── settings.scss │ └── theme.scss │ └── white.css ├── day1-task.html ├── day1.html ├── day10.html ├── day11.html ├── day2-task.html ├── day2.html ├── day3.html ├── day4.html ├── day5.html ├── day6.html ├── day7-part1.html ├── day7-part2.html ├── day8.html ├── day9.html ├── impatient.jpg ├── js └── reveal.js ├── korabli1.jpg ├── lib ├── css │ └── zenburn.css ├── font │ ├── league-gothic │ │ ├── LICENSE │ │ ├── league-gothic.css │ │ ├── league-gothic.eot │ │ ├── league-gothic.ttf │ │ └── league-gothic.woff │ └── source-sans-pro │ │ ├── EOT │ │ ├── SourceSansPro-Black.eot │ │ ├── SourceSansPro-BlackIt.eot │ │ ├── SourceSansPro-Bold.eot │ │ ├── SourceSansPro-BoldIt.eot │ │ ├── SourceSansPro-ExtraLight.eot │ │ ├── SourceSansPro-ExtraLightIt.eot │ │ ├── SourceSansPro-It.eot │ │ ├── SourceSansPro-Light.eot │ │ ├── SourceSansPro-LightIt.eot │ │ ├── SourceSansPro-Regular.eot │ │ ├── SourceSansPro-Semibold.eot │ │ └── SourceSansPro-SemiboldIt.eot │ │ ├── LICENSE.txt │ │ ├── OTF │ │ ├── SourceSansPro-Black.otf │ │ ├── SourceSansPro-BlackIt.otf │ │ ├── SourceSansPro-Bold.otf │ │ ├── SourceSansPro-BoldIt.otf │ │ ├── SourceSansPro-ExtraLight.otf │ │ ├── SourceSansPro-ExtraLightIt.otf │ │ ├── SourceSansPro-It.otf │ │ ├── SourceSansPro-Light.otf │ │ ├── SourceSansPro-LightIt.otf │ │ ├── SourceSansPro-Regular.otf │ │ ├── SourceSansPro-Semibold.otf │ │ └── SourceSansPro-SemiboldIt.otf │ │ ├── TTF │ │ ├── SourceSansPro-Black.ttf │ │ ├── SourceSansPro-BlackIt.ttf │ │ ├── SourceSansPro-Bold.ttf │ │ ├── SourceSansPro-BoldIt.ttf │ │ ├── SourceSansPro-ExtraLight.ttf │ │ ├── SourceSansPro-ExtraLightIt.ttf │ │ ├── SourceSansPro-It.ttf │ │ ├── SourceSansPro-Light.ttf │ │ ├── SourceSansPro-LightIt.ttf │ │ ├── SourceSansPro-Regular.ttf │ │ ├── SourceSansPro-Semibold.ttf │ │ └── SourceSansPro-SemiboldIt.ttf │ │ ├── WOFF │ │ ├── OTF │ │ │ ├── SourceSansPro-Black.otf.woff │ │ │ ├── SourceSansPro-BlackIt.otf.woff │ │ │ ├── SourceSansPro-Bold.otf.woff │ │ │ ├── SourceSansPro-BoldIt.otf.woff │ │ │ ├── SourceSansPro-ExtraLight.otf.woff │ │ │ ├── SourceSansPro-ExtraLightIt.otf.woff │ │ │ ├── SourceSansPro-It.otf.woff │ │ │ ├── SourceSansPro-Light.otf.woff │ │ │ ├── SourceSansPro-LightIt.otf.woff │ │ │ ├── SourceSansPro-Regular.otf.woff │ │ │ ├── SourceSansPro-Semibold.otf.woff │ │ │ └── SourceSansPro-SemiboldIt.otf.woff │ │ └── TTF │ │ │ ├── SourceSansPro-Black.ttf.woff │ │ │ ├── SourceSansPro-BlackIt.ttf.woff │ │ │ ├── SourceSansPro-Bold.ttf.woff │ │ │ ├── SourceSansPro-BoldIt.ttf.woff │ │ │ ├── SourceSansPro-ExtraLight.ttf.woff │ │ │ ├── SourceSansPro-ExtraLightIt.ttf.woff │ │ │ ├── SourceSansPro-It.ttf.woff │ │ │ ├── SourceSansPro-Light.ttf.woff │ │ │ ├── SourceSansPro-LightIt.ttf.woff │ │ │ ├── SourceSansPro-Regular.ttf.woff │ │ │ ├── SourceSansPro-Semibold.ttf.woff │ │ │ └── SourceSansPro-SemiboldIt.ttf.woff │ │ └── source-sans-pro.css └── js │ ├── classList.js │ ├── head.min.js │ └── html5shiv.js ├── plugin ├── highlight │ └── highlight.js ├── markdown │ ├── example.html │ ├── example.md │ ├── markdown.js │ └── marked.js ├── math │ └── math.js ├── multiplex │ ├── client.js │ ├── index.js │ ├── master.js │ └── package.json ├── notes-server │ ├── client.js │ ├── index.js │ └── notes.html ├── notes │ ├── notes.html │ └── notes.js ├── print-pdf │ └── print-pdf.js ├── search │ └── search.js └── zoom-js │ └── zoom.js └── service.svg /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Hello! This is where you manage which Jekyll version is used to run. 4 | # When you want to use a different version, change it below, save the 5 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 6 | # 7 | # bundle exec jekyll serve 8 | # 9 | # This will help ensure the proper Jekyll version is running. 10 | # Happy Jekylling! 11 | #gem "jekyll", "~> 3.8.5" 12 | 13 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 14 | gem "minima", "~> 2.0" 15 | 16 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 17 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 18 | gem "github-pages", group: :jekyll_plugins 19 | 20 | # If you have any plugins, put them here! 21 | group :jekyll_plugins do 22 | gem "jekyll-feed", "~> 0.6" 23 | end 24 | 25 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 26 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 27 | 28 | # Performance-booster for watching directories on Windows 29 | gem "wdm", "~> 0.1.0" if Gem.win_platform? 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scala Course 2022 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | 11 | # Site settings 12 | # These are used to personalize your new site. If you look in the HTML files, 13 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 14 | # You can create any custom variable you would like, and they will be accessible 15 | # in the templates via {{ site.myvariable }}. 16 | title: Курс программирования на языке Scala, 2022 17 | email: your-email@example.com 18 | description: >- # this means to ignore newlines until "baseurl:" 19 | Разработка на языке Scala. Введение в язык. Потоки и асинхронное программирование. 20 | HTTP и REST. 21 | baseurl: "/scala-course-2022" # the subpath of your site, e.g. /blog 22 | url: "" # the base hostname & protocol for your site, e.g. http://example.com 23 | #twitter_username: jekyllrb 24 | github_username: maxcom 25 | 26 | # Build settings 27 | markdown: kramdown 28 | theme: jekyll-theme-slate 29 | plugins: 30 | - jekyll-feed 31 | 32 | # Exclude from processing. 33 | # The following items will not be processed, by default. Create a custom list 34 | # to override the default setting. 35 | # exclude: 36 | # - Gemfile 37 | # - Gemfile.lock 38 | # - node_modules 39 | # - vendor/bundle/ 40 | # - vendor/cache/ 41 | # - vendor/gems/ 42 | # - vendor/ruby/ 43 | -------------------------------------------------------------------------------- /seminar/lesson1/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /project/target/ 3 | /.idea/ 4 | /.bsp/ 5 | -------------------------------------------------------------------------------- /seminar/lesson1/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson1/README.md -------------------------------------------------------------------------------- /seminar/lesson1/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.7" 2 | ThisBuild / version := "0.1.0-SNAPSHOT" 3 | ThisBuild / organization := "com.lesson" 4 | ThisBuild / organizationName := "example" 5 | ThisBuild / name := "lessonOne" 6 | 7 | resolvers += "Java.net Maven2 Repository" at "https://repo1.maven.org/maven2/" 8 | 9 | // https://mvnrepository.com/ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /seminar/lesson1/plan.txt: -------------------------------------------------------------------------------- 1 | В общей комнате: 2 | 10 минут - проект sbt 3 | рассказываем как подключить минимально рабочий sbt проект - структура проекта, build.sbt, build.properties 4 | 5 | по комнатам: 6 | 16 минут - TaskOneMergeSortSpec unit style + 3 теста 7 | сортирует: отрицательные значения/дубликаты 8 | размер сохранен 9 | содержит значения исходного вектора 10 | 16 минут - TaskTwoUnicMergeSortSpec Acceptance style + 3 теста 11 | сортировка 12 | остались только уникальные значения 13 | ничего не потеряли 14 | 16 минут - TaskThreeTopNSpec 15 | возвращает n минимальных значений с сохранением дубликатов 16 | работает с отрицптнльными значениями 17 | не помирает если n > Seq.size() 18 | 16 минут - TaskFourScalaCheckSpec 19 | TaskOneMergeSortSpec - все хорошо 20 | TaskTwoUnicMergeSortSpec - упадет на большом векторе 21 | TaskThreeTopNSpec - надо будет написать свой генератор т.к. входные данные реализованы case class-ом. 22 | 16 минут - TaskFiveLoadAndSortSpec 23 | Моки! 24 | 25 | Всего: 90 минут 26 | -------------------------------------------------------------------------------- /seminar/lesson1/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson1/smallinfo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson1/smallinfo.pdf -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/TaskFourLoadAndSort.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import core.DBService 4 | 5 | class TaskFourLoadAndSort(dbService: DBService) { 6 | 7 | def sortSomethingPlz: Vector[Int] = dbService.giveMeVector.sorted 8 | 9 | } 10 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/TaskOneMergeSort.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import lesson.core.SortUtils 4 | 5 | import scala.annotation.tailrec 6 | import scala.util.Random 7 | 8 | object TaskOneMergeSort { 9 | 10 | 11 | @tailrec 12 | private def merge(l: Vector[Int], r: Vector[Int], acc: Vector[Int] = Vector.empty): Vector[Int] = { 13 | (l, r) match { 14 | case (Vector(), _) => acc ++ r 15 | case (_, Vector()) => acc ++ l 16 | case (lH +: lT, rH +: rT) => 17 | if (lH < rH) { 18 | merge(lT, r, acc :+ lH) 19 | } else { 20 | merge(l, rT, acc :+ rH) 21 | } 22 | } 23 | } 24 | 25 | def sort(input: Vector[Int]): Vector[Int] = mergeSort(input) 26 | 27 | private def mergeSort(input: Vector[Int]): Vector[Int] = { 28 | if (input.length < 2) { 29 | input.map(SortUtils.harmfulSort) 30 | } else { 31 | val (left, right) = input.splitAt(input.length / 2) 32 | merge(mergeSort(left), mergeSort(right)) 33 | } 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/TaskThreeTopN.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import core.InputData 4 | 5 | import scala.annotation.tailrec 6 | import scala.collection.mutable 7 | 8 | 9 | object TaskThreeTopN { 10 | 11 | @tailrec 12 | private def merge(l: Vector[Int], r: Vector[Int], acc: Vector[Int] = Vector.empty): Vector[Int] = { 13 | (l, r) match { 14 | case (Vector(), _) => acc ++ r 15 | case (_, Vector()) => acc ++ l 16 | case (lH +: lT, rH +: rT) => 17 | if (lH < rH) merge(lT, r, acc :+ lH) 18 | else merge(l, rT, acc :+ rH) 19 | } 20 | } 21 | 22 | private def mergeSort(a: Vector[Int]): Vector[Int] = { 23 | if (a.length < 2) a 24 | else { 25 | val (l, r) = a.splitAt(a.length / 2) 26 | merge(mergeSort(l), mergeSort(r)) 27 | } 28 | } 29 | 30 | def topN(input: InputData): Vector[Int] = { 31 | val temp: mutable.PriorityQueue[Int] = mutable.PriorityQueue.empty[Int] 32 | for (i <- input.vector.indices) { 33 | if (i < input.n) { 34 | temp.enqueue(input.vector(i)) 35 | } else if (input.n > 0) { 36 | if (input.vector(i) < temp.head) { 37 | temp.dequeue() 38 | temp.enqueue(input.vector(i)) 39 | } 40 | } 41 | } 42 | mergeSort(temp.toVector).distinct 43 | } 44 | } 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/TaskTwoUniqMergeSort.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import lesson.core.Utils.ALL 4 | import scala.annotation.tailrec 5 | 6 | object TaskTwoUniqMergeSort { 7 | @tailrec 8 | private def merge(l: Vector[Int], r: Vector[Int], acc: Vector[Int] = Vector.empty): Vector[Int] = { 9 | (l, r) match { 10 | case (Vector(), _) => acc ++ r 11 | case (_, Vector()) => acc ++ l 12 | case (lH +: lT, rH +: rT) => 13 | if (lH < rH) merge(lT, r, acc :+ lH) 14 | else if (lH > rH) merge(l, rT, acc :+ rH) 15 | else merge(lT, rT, acc :+ lH) 16 | } 17 | } 18 | 19 | def sort(input: Vector[Int]): Vector[Int] = mergeSort(input).take(ALL) 20 | 21 | private def mergeSort(input: Vector[Int]): Vector[Int] = { 22 | if (input.length < 2) input 23 | else { 24 | val (l, r) = input.splitAt(input.length / 2) 25 | merge(mergeSort(l), mergeSort(r)) 26 | } 27 | } 28 | 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/core/DBService.scala: -------------------------------------------------------------------------------- 1 | package lesson.core 2 | 3 | trait DBService { 4 | def giveMeVector: Vector[Int] 5 | } 6 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/core/InputData.scala: -------------------------------------------------------------------------------- 1 | package lesson.core 2 | 3 | case class InputData(vector: Vector[Int], n: Int) 4 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/core/SortUtils.scala: -------------------------------------------------------------------------------- 1 | package lesson.core 2 | 3 | object SortUtils { 4 | 5 | def harmfulSort(v: Int): Int = if (v > 1000) { 6 | v + 1 7 | } else { 8 | v 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /seminar/lesson1/src/main/scala/lesson/core/Utils.scala: -------------------------------------------------------------------------------- 1 | package lesson.core 2 | 3 | object Utils { 4 | 5 | val ALL = 20 6 | 7 | } 8 | -------------------------------------------------------------------------------- /seminar/lesson1/src/test/scala/lesson/TaskFiveLoadAndSortSpec.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | class TaskFiveLoadAndSortSpec { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /seminar/lesson1/src/test/scala/lesson/TaskFourScalaCheckSpec.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | class TaskFourScalaCheckSpec { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /seminar/lesson1/src/test/scala/lesson/TaskOneMergeSortSpec.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class TaskOneMergeSortSpec extends Specification { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /seminar/lesson1/src/test/scala/lesson/TaskThreeTopNSpec.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | class TaskThreeTopNSpec { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /seminar/lesson1/src/test/scala/lesson/TaskTwoUniqMergeSortSpec.scala: -------------------------------------------------------------------------------- 1 | package lesson 2 | 3 | import org.specs2.Specification 4 | import org.specs2.specification.core.SpecStructure 5 | 6 | class TaskTwoUniqMergeSortSpec extends Specification { 7 | 8 | override def is: SpecStructure = ??? 9 | 10 | } 11 | 12 | -------------------------------------------------------------------------------- /seminar/lesson10/.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | .idea/ 3 | target/ -------------------------------------------------------------------------------- /seminar/lesson10/README.md: -------------------------------------------------------------------------------- 1 | # Akka Streams Seminar 2 | 3 | Элементы стрима и их композиция: 4 | - https://maxcom.github.io/scala-course-2022/slides/day10.html#/12 5 | - https://doc.akka.io/docs/akka/current/stream/stream-composition.html 6 | 7 | Подсказочка-важная мысль: 8 | - Есть описание стрима, есть запуск. 9 | - Про запуск stream-а - всем методы, которые запускают стрим содержат “run” в имени метода 10 | 11 | ## Task 1.1 12 | Task 1.1: Написать Stream, который печатает логи (независимо от их уровня), в консоль (STDOUT). Источник логов - LogGenerator.genLogs() 13 | 14 | Подсказка-слайд: [слайд](https://maxcom.github.io/scala-course-2022/slides/day10.html#/19]) 15 | 16 | Пояснения к заданию: 17 | - в академических целях - разделяем source, sink, flow 18 | - также, хорошо будет на первых этапах добавлять результирующий тип, чтобы лучше понимать происходящее 19 | 20 | ## Task 1.2 21 | Task 1.2: Написать Stream, который печатает логи (независимо от их уровня), в файл. 22 | - Подсказка-слайд: [слайд](https://maxcom.github.io/scala-course-2022/slides/day10.html#/19) 23 | - Полезная документация: https://doc.akka.io/api/akka/2.6/akka/stream/scaladsl/FileIO$.htm 24 | 25 | Функция создания файла - FileUtils.createFile(filename) 26 | 27 | Пояснения к заданию: 28 | - в академических целях - разделяем source, sink, flow, - чтобы уметь работать с ними как с "кирпичиками": 29 | 30 | ## Task 2 31 | Task 2: Написать Stream, который печатает в консоль цифры, с органичением - 2 результата каждые 5 секунд 32 | 33 | ## Task 3 34 | Task 3: 35 | 3.1 - Сравнить запуск запросов к DB - Future.traverse и запуск на Stream-ах 36 | 37 | 3.2 - Ограничить кол-во запросов к DB - чтобы одновременно выполнялось только 10 параллельных запросов, чтобы она не упала 38 | 39 | 3.2.2 - с сохранением порядка элементов 40 | 41 | ## Task 4 42 | Task 4: Написать stream, который будет суммировать по 10 элементов и в результате возвращать Seq сумм -------------------------------------------------------------------------------- /seminar/lesson10/build.sbt: -------------------------------------------------------------------------------- 1 | name := "akka-streams" 2 | 3 | version := "0.1" 4 | 5 | scalaVersion := "2.13.8" 6 | 7 | val AkkaVersion = "2.6.19" 8 | libraryDependencies ++= Seq( 9 | "com.typesafe.akka" %% "akka-stream" % AkkaVersion, 10 | "com.typesafe.akka" %% "akka-stream-testkit" % AkkaVersion % Test 11 | ) 12 | -------------------------------------------------------------------------------- /seminar/lesson10/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 -------------------------------------------------------------------------------- /seminar/lesson10/src/main/scala/com/LogSplitter5.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.NotUsed 4 | import akka.actor.ActorSystem 5 | import akka.stream.{ClosedShape, IOResult, UniformFanOutShape} 6 | import akka.stream.scaladsl.{Broadcast, FileIO, Flow, GraphDSL, RunnableGraph, Sink, Source} 7 | import akka.util.ByteString 8 | import com.LogGenerator.genLogs 9 | 10 | import scala.concurrent.Future 11 | 12 | // Работа с Graph-ами - https://doc.akka.io/docs/akka/current/stream/stream-graphs.html 13 | 14 | object LogSplitter5 extends App { 15 | 16 | // Task 3 - Пишем наш обычный stream для вывода в файл через Graph DSL 17 | // Знакомимся с RunnableGraph 18 | // - Он Runnable, так как его можно запустить, так как он полностью сконструирован (закрыты все входы/выходы) 19 | // - if there are no unwired ports, the graph is closed, and therefore can be materialized 20 | // - поэтому и возвращаем в конце ClosedShape. 21 | 22 | // Условие - используем "~>" для конструирования 23 | // Заготовка: 24 | val logProcessor = RunnableGraph.fromGraph(GraphDSL.create() { implicit b => 25 | import GraphDSL.Implicits._ 26 | 27 | // in ~> ... ~> out 28 | 29 | ClosedShape 30 | }) 31 | 32 | 33 | 34 | // Task 4 - Пишем LogSplitter с помощью Graph DSL : 35 | // Требования к поведению LogSplitter-а 36 | // - каждый уровнень логов пишем в свой файл 37 | // - все пришедшие логи, независимо от уровня выводим в консоль 38 | 39 | // Task 4.1 - составляем схему из элементов akka-streams, которая подходит под требования: 40 | // комната 1 - https://miro.com/app/board/uXjVO0nom6w=/ 41 | // комната 2 - https://miro.com/app/board/uXjVO0n_uMc=/ 42 | // там же - выбрать подходящий https://doc.akka.io/docs/akka/current/stream/stream-graphs.html#constructing-graphs 43 | 44 | // Task 4.2 - пишем реализацию 45 | 46 | // Заготовка: 47 | val logSplitter = RunnableGraph.fromGraph(GraphDSL.create() { implicit b => 48 | import GraphDSL.Implicits._ 49 | 50 | // in ~> ... ~> out 51 | 52 | ClosedShape 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /seminar/lesson10/src/main/scala/com/SimpleStreams1.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.{Done, NotUsed} 4 | import akka.actor.ActorSystem 5 | import akka.stream.IOResult 6 | import akka.stream.scaladsl.{FileIO, Flow, Sink, Source} 7 | import akka.util.ByteString 8 | 9 | import java.nio.file.{Path, Paths} 10 | import scala.concurrent.Future 11 | import scala.util.Random 12 | 13 | // Композиция-создание стримов: 14 | // https://maxcom.github.io/scala-course-2022/slides/day10.html#/12 15 | // https://doc.akka.io/docs/akka/current/stream/stream-composition.html 16 | 17 | object SimpleStreams1 extends App { 18 | 19 | // Задание 1.1: Написать Stream, который печатает логи (независимо от их уровня), в консоль (STDOUT). Источник логов - LogGenerator.genLogs() 20 | // Подсказка-слайд: https://maxcom.github.io/scala-course-2022/slides/day10.html#/19 21 | // Заготовка задания: 22 | // - в академических целях - разделяем source, sink, flow 23 | // - также, хорошо будет на первых этапах добавлять результирующий тип, чтобы лучше понимать происходящее 24 | // Подсказка: запуск stream-а - всем методы, которые запускают стрим содержать “run” в имени метода 25 | def sourceTask1 = ??? 26 | def sinkTask1 = ??? 27 | 28 | 29 | 30 | // Задание 1.2: Написать Stream, который печатает логи (только уровня ERROR), в файл. 31 | // Подсказка-слайд: https://maxcom.github.io/scala-course-2022/slides/day10.html#/19 32 | // Полезная документация: https://doc.akka.io/api/akka/2.6/akka/stream/scaladsl/FileIO$.html 33 | // Функция создания файла - FileUtils.createFile(filename) 34 | // Заготовка задания: (в академических целях - разделяем source, sink, flow, - чтобы умели работать с ними как с "кирпичиками"): 35 | def sourceTask2 = ??? 36 | def flowTask2 = ??? 37 | def sinkTask2 = ??? 38 | } 39 | 40 | object LogGenerator { 41 | def genLogs(size: Int, recordLength: Int): Seq[String] = { 42 | val levels = Map(0 -> "INFO", 1 -> "WARN", 2 -> "ERROR") 43 | (1 to size).map { _ => 44 | s"${levels(Random.nextInt(3))}:${Random.alphanumeric.take(recordLength).mkString("")}" 45 | } 46 | } 47 | } 48 | 49 | object FileUtils { 50 | def createFile(filename: String): Path = Paths.get(filename) 51 | } 52 | -------------------------------------------------------------------------------- /seminar/lesson10/src/main/scala/com/StreamGroup4.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.NotUsed 4 | import akka.actor.ActorSystem 5 | import akka.stream.scaladsl.{Sink, Source} 6 | 7 | import scala.concurrent.ExecutionContextExecutor 8 | import scala.util.{Failure, Success, Try} 9 | 10 | // Задание 4: Написать stream, который будет суммировать по 10 элементов и в результате возвращать Seq сумм 11 | 12 | object StreamGroup4 extends App { 13 | 14 | implicit val system: ActorSystem = ActorSystem("system") 15 | implicit val ex: ExecutionContextExecutor = system.getDispatcher 16 | 17 | val range: Seq[Int] = Range(1, 20) 18 | } 19 | -------------------------------------------------------------------------------- /seminar/lesson10/src/main/scala/com/StreamThrottle2.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.scaladsl.{Sink, Source} 5 | 6 | import java.time.LocalTime 7 | import scala.concurrent.ExecutionContextExecutor 8 | import scala.concurrent.duration.DurationInt 9 | 10 | /* 11 | Задание 2: Написать Stream, который печатает в консоль цифры, с органичением - 2 результата каждые 5 секунд 12 | */ 13 | object StreamThrottle2 extends App { 14 | val range: Seq[Int] = Range(1, 20) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /seminar/lesson10/src/main/scala/com/StreamsAndFuture3.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.ActorSystem 4 | import akka.stream.scaladsl.{Sink, Source} 5 | 6 | import java.time.LocalTime 7 | import java.util.concurrent.Executors 8 | import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} 9 | import scala.util.Random 10 | 11 | // Задание 3: 12 | // 3.1 - Сравнить запуск запросов к DB - Future.traverse и запуск на Stream-ах 13 | // 3.2 - Ограничить кол-во запросов к DB - чтобы одновременно выполнялось только 10 параллельных запросов, чтобы она не упала 14 | // 3.2.2 - с сохранением порядка элементов 15 | 16 | object StreamsAndFuture3 extends App { 17 | implicit val ec: ExecutionContextExecutor = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(25)) 18 | implicit val system: ActorSystem = ActorSystem("system", defaultExecutionContext = Option(ec)) 19 | 20 | val range: Seq[Int] = Range(1, 100) 21 | 22 | def fDbcall(i: Int): Future[String] = Future { 23 | Thread.sleep(Random.between(1000, 4000)) 24 | //Thread.sleep(4000) 25 | println(s"executed #${i}") 26 | s"result of f for number #${i}" 27 | } 28 | 29 | // 3.1 - Сравнить запуск запросов к DB - Future.traverse и запуск на Stream-ах 30 | 31 | 32 | 33 | // 3.2 - Ограничить кол-во запросов к DB - чтобы одновременно выполнялось только 10 параллельных запросов, чтобы она не упала 34 | // 3.2.2 - с сохранением порядка элементов 35 | 36 | } 37 | -------------------------------------------------------------------------------- /seminar/lesson11/.gitignore: -------------------------------------------------------------------------------- 1 | /project/project/ 2 | /project/target/ 3 | /target/ 4 | /.idea/ 5 | /.bsp/ 6 | -------------------------------------------------------------------------------- /seminar/lesson11/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.7" 2 | ThisBuild / version := "0.1.0-SNAPSHOT" 3 | ThisBuild / organization := "com.lesson" 4 | ThisBuild / name := "eleven" 5 | 6 | scalacOptions ++= Seq( 7 | "-deprecation", 8 | "-encoding", "UTF-8", 9 | "-feature", 10 | "-unchecked" 11 | ) 12 | 13 | libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" 14 | 15 | val http4sVersion = "1.0.0-M23" 16 | val circeVersion = "0.14.1" 17 | libraryDependencies ++= Seq( 18 | "org.http4s" %% "http4s-dsl" % http4sVersion, 19 | "org.http4s" %% "http4s-ember-server" % http4sVersion, 20 | "org.http4s" %% "http4s-ember-client" % http4sVersion, 21 | "org.http4s" %% "http4s-circe" % http4sVersion, 22 | "ch.qos.logback" % "logback-classic" % "1.2.3", 23 | "io.circe" %% "circe-core" % circeVersion, 24 | "io.circe" %% "circe-generic" % circeVersion, 25 | "io.circe" %% "circe-parser" % circeVersion, 26 | "org.mindrot" % "jbcrypt" % "0.3m" 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /seminar/lesson11/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level][%thread] %30.30logger{30}: %msg%n 7 | UTF-8 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Main.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.effect._ 4 | import com.comcast.ip4s._ 5 | import org.http4s.server.Router 6 | import org.http4s.ember.server.EmberServerBuilder 7 | import org.http4s._ 8 | import org.http4s.implicits._ 9 | 10 | object Main extends IOApp.Simple { 11 | 12 | 13 | val routes: HttpApp[IO] = Router( 14 | "/" -> HttpRoutes.empty[IO] 15 | ).orNotFound 16 | 17 | //curl -x GET http://localhost:8080/task1/hello 18 | val server: ResourceIO[org.http4s.server.Server] = 19 | EmberServerBuilder 20 | .default[IO] 21 | .withHost(ipv4"0.0.0.0") 22 | .withPort(port"8080") 23 | .withHttpApp(routes) 24 | .build 25 | 26 | val run = server.use(_ => IO.never) 27 | 28 | } -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task1.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.effect._ 4 | import cats.syntax.all._ 5 | import com.lesson.eleven.model.Animal 6 | import org.http4s._ 7 | import org.http4s.dsl.io._ 8 | 9 | object Task1 { 10 | 11 | // Префикс для всех роутов должен быть /task1 12 | val routes: HttpRoutes[IO] = ??? 13 | 14 | 15 | // 1.1 16 | // GET /hello 17 | // 200 OK, "Hello" 18 | def hello: HttpRoutes[IO] = ??? 19 | 20 | 21 | // 1.2 22 | // GET /hello/:name 23 | // 200 OK, "Hello, $name" 24 | def helloName: HttpRoutes[IO] = ??? 25 | 26 | 27 | // 1.3 28 | // GET /love/:animal 29 | // где animal имеет тип sealed trait Animal 30 | // см. org.http4s.dsl.impl.PathVar 31 | // 200 OK, "I love ${animal.name}s" 32 | def loveAnimals: HttpRoutes[IO] = ??? 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task2.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.data.{NonEmptyList, Validated} 4 | import cats.effect._ 5 | import cats.syntax.all._ 6 | import com.lesson.eleven.model.Animal 7 | import org.http4s._ 8 | import org.http4s.dsl.io.{QueryParamDecoderMatcher, _} 9 | 10 | object Task2 { 11 | 12 | // Префикс для всех роутов должен быть /task2 13 | val routes: HttpRoutes[IO] = ??? 14 | 15 | 16 | // 2.1 17 | // GET /boolean ? inverted : Boolean 18 | // 200 OK, "Boolean: $boolean" 19 | def boolean: HttpRoutes[IO] = ??? 20 | 21 | 22 | // 2.2 23 | // GET /flag ? flag 24 | // 200 OK, "Flag: $flag" 25 | def flag: HttpRoutes[IO] = ??? 26 | 27 | 28 | // 2.3 29 | // GET /love ? animal : Animal 30 | // 200 OK, "I love $animal" 31 | def love: HttpRoutes[IO] = ??? 32 | 33 | 34 | // 2.5 35 | // GET /default-love ? animal : Option[Animal] = Dog 36 | // 200 OK, "I love $animal" 37 | def defaultLove: HttpRoutes[IO] = ??? 38 | 39 | 40 | // 2.5 41 | // GET /multi-love ? animals : Animal & animals : Animal ... 42 | // 200 OK, "I love all animals: ${animal.mkString(", ")}" 43 | def multiLove: HttpRoutes[IO] = ??? 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task3.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.effect._ 4 | import com.lesson.eleven.model.RegistrationData 5 | import org.http4s._ 6 | import org.http4s.circe.CirceEntityCodec._ 7 | import org.http4s.dsl.io._ 8 | 9 | object Task3 { 10 | 11 | // Префикс для всех роутов должен быть /task3 12 | val routes: HttpRoutes[IO] = userData 13 | 14 | 15 | // GET /user-data 16 | // 200 OK, Json[RegistrationData] 17 | def userData: HttpRoutes[IO] = ??? 18 | 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task4.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.effect._ 4 | import com.lesson.eleven.model.RegistrationData 5 | import org.http4s._ 6 | import org.http4s.circe.CirceEntityCodec._ 7 | import org.http4s.dsl.io._ 8 | 9 | 10 | object Task4 { 11 | 12 | // Префикс для всех роутов должен быть /task4 13 | val routes: HttpRoutes[IO] = signup 14 | 15 | 16 | // POST /signup 17 | // Entity: Json[RegistrationData] 18 | // 200 OK, "Boolean: $boolean" 19 | // 400 BadRequest, если некорректное тело 20 | def signup: HttpRoutes[IO] = ??? 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task5.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | import cats.effect._ 4 | import com.lesson.eleven.model.RegistrationData 5 | import com.lesson.eleven.service.FakeDBService 6 | import org.http4s._ 7 | import org.http4s.circe.CirceEntityCodec._ 8 | import org.http4s.dsl.io._ 9 | 10 | 11 | object Task5 { 12 | 13 | // Префикс для всех роутов должен быть /task5 14 | val routes: HttpRoutes[IO] = businessLogicUnsafe 15 | 16 | 17 | // POST /business-logic 18 | // Entity: Json[RegistrationData] 19 | // 200 OK, "Department: $department" 20 | // 400 BadRequest с ошибкой 21 | def businessLogicUnsafe: HttpRoutes[IO] = ??? 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/Task6.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven 2 | 3 | 4 | import cats.effect._ 5 | import cats.syntax.all._ 6 | import org.http4s._ 7 | import org.http4s.dsl.io._ 8 | 9 | import scala.concurrent.duration._ 10 | 11 | 12 | object Task6 { 13 | 14 | // Префикс для всех роутов должен быть /task6 15 | val routes: HttpRoutes[IO] = ??? 16 | 17 | private val timeout: FiniteDuration = 10.second 18 | 19 | // 6.1 20 | private def counting(n: Int): IO[Unit] = ??? 21 | 22 | 23 | // 6.1 24 | // PUT /count/:num 25 | // По окончании: 201 Created 26 | def count: HttpRoutes[IO] = ??? 27 | 28 | 29 | // 6.2 30 | // PUT /count-with-timeout/:num 31 | // По окончании: 201 Created или 408 RequestTimeout 32 | def countWithTimeout: HttpRoutes[IO] = ??? 33 | 34 | 35 | // 6.3 36 | // PUT /start-counter/:num 37 | // Сразу: 201 Created 38 | // Защиту в виде таймаута нужно сохранить. 39 | def countAsync: HttpRoutes[IO] = ??? 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/model/Animal.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven.model 2 | 3 | import org.http4s.{ParseFailure, QueryParamDecoder} 4 | 5 | sealed trait Animal { def name: String } 6 | 7 | object Animal { 8 | case object Dog extends Animal { val name = "dog" } 9 | case object Cat extends Animal { val name = "cat" } 10 | 11 | def unapply(input: String): Option[Animal] = input match { 12 | case Dog.name => Some(Dog) 13 | case Cat.name => Some(Cat) 14 | case _ => None 15 | } 16 | 17 | implicit val animalParamDecoder: QueryParamDecoder[Animal] = ??? 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/model/DatabaseData.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven.model 2 | 3 | import org.mindrot.jbcrypt.BCrypt 4 | 5 | import java.util.UUID 6 | 7 | case class DatabaseData(id: UUID = UUID.randomUUID(), 8 | login: String, 9 | passwordHash: String, 10 | department: String) 11 | 12 | object DatabaseData { 13 | 14 | def apply(login: String, password: String, department: String): DatabaseData = 15 | DatabaseData( 16 | id = UUID.randomUUID(), 17 | login = login, 18 | passwordHash = BCrypt.hashpw(password, BCrypt.gensalt()), 19 | department = department 20 | ) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/model/RegistrationData.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven.model 2 | 3 | import cats.syntax.apply._ 4 | import io.circe.{Decoder, Encoder, Json} 5 | 6 | case class RegistrationData(login: String, 7 | password: String, 8 | firstName: Option[String], 9 | lastName: Option[String], 10 | age: Option[Int]) 11 | 12 | 13 | object RegistrationData { 14 | 15 | implicit val decoderWithValidation: Decoder[RegistrationData] = ( 16 | Decoder[String].at("login") 17 | .ensure(_.nonEmpty, "login should not be empty"), 18 | Decoder.decodeString.at("password") 19 | .ensure(_.nonEmpty, "password should not be empty"), 20 | Decoder.instance( 21 | _.downField("firstName").as[Option[String]].map(_.filter(_.nonEmpty)) 22 | ), 23 | Decoder.instance( 24 | _.get[Option[String]]("lastName").map(_.filter(_.nonEmpty)) 25 | ), 26 | Decoder.instance( _.get[Option[Int]]("age") ) 27 | ).mapN(RegistrationData.apply) 28 | 29 | implicit val encoderWithValidation: Encoder[RegistrationData] = (d: RegistrationData) => 30 | Json.obj( 31 | ("login", Json.fromString(d.login)), 32 | ("password", Json.fromString(d.password)), 33 | ("firstName", d.firstName.fold(Json.Null)(Json.fromString)), 34 | ("lastName", d.lastName.fold(Json.Null)(Json.fromString)), 35 | ("age", d.age.fold(Json.Null)(Json.fromInt)) 36 | ).mapObject(_.filter(!_._2.isNull)) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /seminar/lesson11/src/main/scala/com/lesson/eleven/service/FakeDBService.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.eleven.service 2 | 3 | import cats.effect.{IO, Resource, ResourceIO} 4 | import com.lesson.eleven.model.{DatabaseData, RegistrationData} 5 | import org.mindrot.jbcrypt.BCrypt 6 | 7 | import java.util.UUID 8 | 9 | object FakeDBService { 10 | 11 | type Database = Set[DatabaseData] 12 | 13 | private val generateDatabase: IO[Database] = IO { 14 | Set( 15 | DatabaseData("user", "Qwe12345", "Developers"), 16 | DatabaseData("superuser", "SuperSecure", "IT Crowd") 17 | ) 18 | } 19 | 20 | private val database: ResourceIO[Database] = Resource.eval(generateDatabase) 21 | 22 | 23 | def findUser(login: String): IO[Option[UUID]] = ??? 24 | 25 | def checkPassword(userId: UUID, candidate: String): IO[Boolean] = ??? 26 | 27 | def getDepartmentUnsafe(userId: UUID): IO[String] = ??? 28 | 29 | } 30 | -------------------------------------------------------------------------------- /seminar/lesson2/.gitignore: -------------------------------------------------------------------------------- 1 | /project/project/ 2 | /project/target/ 3 | /target/ 4 | /.idea/ 5 | /.bsp/ 6 | -------------------------------------------------------------------------------- /seminar/lesson2/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Семинар 2: Java Microbenchmark Harness (JMH) 3 | 4 | ## Что такое JMH и с чем его едят 5 | 6 | JMH - это инструмент Java для создания, запуска и анализа тестов на производительность отдельных функций и методов. 7 | 8 | Страничка проекта на github (там можно найти примеры java) 9 |
10 | Страничка SBT-плагина на github (там можно найти примеры scala) 11 | 12 | Поддерживаются разные метрики. Наиболее полезные режимы: 13 | - среднее время выполнения метода после разогрева (AverageTime) 14 | - обратный к нему: сколько раз отработает метод за указанное время? (Throughput) 15 | - время одного "холодного" запуска (SingleShotTime) 16 | 17 | Можно настраивать разделяемое состояние: 18 | - одно для всех беенчмарков (Benchmark) 19 | - по одному на каждую группу бенчмарков (Group) 20 | - по одному на каждый тред (Thread) 21 | 22 | ## Установка и использование 23 | 24 | Устанавливаем плагин для sbt: 25 | 1. Прописываем зависимость в plugins.sbt 26 | 2. Разрешаем плагин в build.sbt 27 | 3. Помечаем интересующий нас метод аннотацией @Benchmark 28 | 4. Запускаем в sbt-консоли: `jmh:run` или отдельный тест: `jmh:run package.class.function` 29 | 30 | Все параметры: `jmh:run -help` 31 | 32 | Например: `jmh:run ExampleBenchmarks -f 1 -i 1 -wi 5` 33 | 34 | Пример есть в этой статье 35 | 36 | 37 | ## Задания: 38 | 39 | 0. **Ведущий**: ExampleBenchmarks (15 минут) 40 |
Вводный рассказ и демонстрация настроек на примере. 41 | 42 | 43 | 1. **Ученик 1**: AddElementBenchmarks (20 минут) 44 |
Сравниваем добавление элементов в начало и конец списка. 45 |
Варианты из лекции и библиотечные. 46 |
На первом запуске ловим StackOverflow на рекурсивном варианте для списка 10000. 47 |
Уменьшаем до 1000 и повторяем. 48 |
Делаем вывод, что добавление в начало - "бесплатная" операция (отличие на порядки). 49 | 50 | 2. **Ученик 2**: RecursionBenchmarks (15 минут) 51 |
Переполнения стека в рекурсиях и хвостовая рекурсия 52 |
Предлагается исправить рекурсивный вариант, чтобы он не выпадал в StackOverflow. 53 |
Проверяем, что он уже не падает на 10000. 54 |
Сравниваем на 1000. Получаем хуже производительность, но зато не падает. =) 55 | 56 | 3. **Ученик 3**: CreateListBenchmarks (20 минут) 57 |
Собираем список из N элементов. 58 |
Сравниваем добавление по одному в начало, в конец, конструктор списка, конструктор Range и RichInt. 59 |
Обращаем внимание, что List.apply хуже некоторых вариантов. Причина в том, что он написан в общем виде для IterableOnce. 60 | 61 | 4. **Ученик 4**: GetElementBenchmarks (15 минут) 62 |
Получение элемента по индексу в списке. Пример из лекции. 63 |
Количество операций пропорционально N => не надо обходить список используя номер элемента 64 |
Сравниваем с получением по индексу из других коллекций. 65 | 66 | 5. **Ученик 5**: CountBenchmarks (10 минут) 67 |
Реализация функции count. 68 |
Сравниваем плохой и хороший варианты из лекции и библиотечный вариант. 69 |
Реализуем вариант с рекурсивным обходов 70 | 71 | -------------------------------------------------------------------------------- /seminar/lesson2/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.7" 2 | ThisBuild / version := "0.1.0-SNAPSHOT" 3 | ThisBuild / organization := "com.lesson" 4 | ThisBuild / name := "two" 5 | 6 | resolvers += "Java.net Maven2 Repository" at "https://repo1.maven.org/maven2/" 7 | 8 | enablePlugins(JmhPlugin) 9 | -------------------------------------------------------------------------------- /seminar/lesson2/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson2/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/AddElementBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations.{BenchmarkMode, Fork, Measurement, Mode, OutputTimeUnit, Warmup} 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.util.Random 7 | 8 | // ЗАДАНИЕ 1 9 | // Сравниваем добавление элементов в начало и конец списка. 10 | class AddElementBenchmarks { 11 | 12 | //Пример из лекций: список раскручивается в стек, стек собирается в новый список 13 | // def append(list: List[Int], element: Int): List[Int] = { 14 | // list match { 15 | // case Nil => 16 | // v :: Nil 17 | // case head :: tail => 18 | // head :: append(tail, element) 19 | // } 20 | // } 21 | def listAppendRecursive = ??? 22 | 23 | //Пример из лекций: та же логика через reverse 24 | // (1 +: list).reverse 25 | def listAppendReverse = ??? 26 | 27 | //Библиотечный вариант: 28 | // list :+ element 29 | def listAppend = ??? 30 | 31 | //Сравниваем с добавлением в начало списка: 32 | // element +: list 33 | def listPrepend = ??? 34 | 35 | } 36 | 37 | 38 | class AddElementState { 39 | val list: List[Int] = List.fill(10000)(Random.nextInt()) 40 | val element: Int = 1 41 | } 42 | -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/CountBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.util.Random 7 | 8 | 9 | // ЗАДАНИЕ 5 10 | // Реализация функции count 11 | // Сравниваем плохой и хороший варианты из лекции и библиотечный вариант. 12 | // Реализуем вариант с рекурсивным обходов 13 | //@OutputTimeUnit(TimeUnit.MILLISECONDS) 14 | class CountBenchmarks { 15 | 16 | // Плохая реализация через filter 17 | // Не эффективно, создает временный список т.к. операции над коллекциями не меняют тип 18 | def badVersion(scope: CountState) = 19 | scope.list.filter(scope.checkFunc).length 20 | 21 | // Через foldLeft 22 | def betterVersion(scope: CountState) = ??? 23 | 24 | // Сравнить с библиотечным count 25 | def libraryVersion(scope: CountState) = ??? 26 | 27 | 28 | // ЗАДАНИЕ СО ЗВЁЗДОЧКОЙ: еще можно реализовать рекурсивным обходом списка 29 | def recVersion(scope: CountState) = ??? 30 | 31 | } 32 | 33 | 34 | class CountState { 35 | val list: List[Int] = List.fill(10000)(Random.nextInt()) 36 | val checkFunc: Int => Boolean = _ > 50 37 | } 38 | -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/CreateListBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.annotation.tailrec 7 | import scala.util.Random 8 | 9 | // ЗАДАНИЕ 3 10 | // Собираем список из N элементов. 11 | // Сравниваем добавление по одному в начало, в конец, конструктор списка, конструктор Range и RichInt. 12 | // @OutputTimeUnit(TimeUnit.NANOSECONDS) 13 | class CreateListBenchmarks { 14 | 15 | 16 | // var i = 0 17 | // var list: List[Int] = Nil 18 | // while(i < scope.n) { 19 | // list = list :+ i 20 | // i += 1 21 | // } 22 | def createListAppendig(scope: CreateListState) = ??? 23 | 24 | 25 | // @tailrec 26 | // def addNext(acc: List[Int], nextElem: Int, leftCount: Int): List[Int] = 27 | // if (leftCount == 0) acc 28 | // else addNext(acc :+ nextElem, nextElem + 1, leftCount - 1) 29 | // 30 | // addNext(Nil, 0, scope.n) 31 | def createListAppendigFunctional(scope: CreateListState) = ??? 32 | 33 | 34 | // var i = 0 35 | // var list: List[Int] = Nil 36 | // while(i < scope.n) { 37 | // list = (scope.n - i - 1) +: list 38 | // i += 1 39 | // } 40 | def createListPrepending(scope: CreateListState) = ??? 41 | 42 | 43 | // @tailrec 44 | // def addNext(acc: List[Int], nextElem: Int, leftCount: Int): List[Int] = 45 | // if (leftCount == 0) acc 46 | // else addNext(nextElem +: acc, nextElem - 1, leftCount - 1) 47 | // 48 | // addNext(Nil, scope.n - 1, scope.n) 49 | def createListPrependingFunctional(scope: CreateListState) = ??? 50 | 51 | 52 | // List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 53 | def createList(scope: CreateListState) = ??? 54 | 55 | 56 | // Range(0, scope.n).toList 57 | def createListFromRange(scope: CreateListState) = ??? 58 | 59 | 60 | // (0 until scope.n).toList 61 | def createListRichInt(scope: CreateListState) = ??? 62 | 63 | 64 | } 65 | 66 | 67 | class CreateListState { 68 | val n: Int = 10 69 | } 70 | -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/ExampleBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations._ 4 | import java.util.concurrent.TimeUnit 5 | import scala.util.Random 6 | 7 | // jmh:run ExampleBenchmarks -f 1 -i 1 -wi 5 8 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @Fork(value = 1) 11 | @Warmup(iterations = 5) 12 | @Measurement(iterations = 1) 13 | class ExampleBenchmarks { 14 | 15 | // list :+ element 16 | @Benchmark 17 | def listAppend(scope: ExampleState) = scope.list :+ scope.element 18 | 19 | // element +: list 20 | @Benchmark 21 | def listPrepend(scope: ExampleState) = scope.element +: scope.list 22 | 23 | } 24 | 25 | 26 | @State(Scope.Benchmark) 27 | class ExampleState { 28 | val list: List[Int] = List.fill(10000)(Random.nextInt()) 29 | val element: Int = 1 30 | } 31 | -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/GetElementBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.collection.immutable 7 | import scala.util.Random 8 | 9 | // ЗАДАНИЕ 4 10 | // Получение элемента по индексу в разных коллекциях 11 | // @OutputTimeUnit(TimeUnit.NANOSECONDS) 12 | class GetElementBenchmarks { 13 | 14 | // Пример из лекции 15 | // val rest = drop(n) // drop не хуже итерации по списку 16 | // if (n < 0 || rest.isEmpty) 17 | // throw new IndexOutOfBoundsException(n.toString) 18 | // rest.head 19 | def test(scope: GetElementState): Int = ??? 20 | 21 | // Библиотечные варианты 22 | def applySeq(scope: GetElementState) = scope.seq(scope.n) 23 | def applyList(scope: GetElementState) = scope.list(scope.n) 24 | def applyVector(scope: GetElementState) = scope.vector(scope.n) 25 | def applyArray(scope: GetElementState) = scope.array(scope.n) 26 | 27 | } 28 | 29 | 30 | class GetElementState { 31 | val n: Int = Random.between(1000, 10000) 32 | val seq: immutable.Seq[Int] = Seq.fill(10000)(Random.nextInt()) 33 | val list: immutable.List[Int] = seq.toList 34 | val vector: immutable.Vector[Int] = seq.toVector 35 | val array: Array[Int] = seq.toArray 36 | } 37 | -------------------------------------------------------------------------------- /seminar/lesson2/src/main/scala/com/lesson/two/RecursionBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.two 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.annotation.tailrec 7 | import scala.util.Random 8 | 9 | // ЗАДАНИЕ 2 10 | // Переполнения стека в рекурсиях и хвостовая рекурсия 11 | //@OutputTimeUnit(TimeUnit.MICROSECONDS) 12 | class RecursionBenchmarks { 13 | 14 | // Пример из лекций 15 | def listAppendRecursive(scope: RecursionState) = { 16 | def appendTo(list: List[Int]): List[Int] = 17 | list match { 18 | case Nil => scope.element :: Nil 19 | case head :: tail => head :: appendTo(tail) 20 | } 21 | 22 | appendTo(scope.list) 23 | } 24 | 25 | // Предлагается исправить рекурсивный вариант, чтобы он не выпадал в StackOverflow. 26 | def listAppendTailRecursive(scope: RecursionState) = { 27 | @tailrec 28 | def appendTo(list: List[Int], acc: List[Int]): List[Int] = { 29 | ??? 30 | appendTo(???, ???) 31 | } 32 | 33 | appendTo(scope.list, Nil) 34 | } 35 | 36 | } 37 | 38 | 39 | class RecursionState { 40 | val list: List[Int] = List.fill(10000)(Random.nextInt()) 41 | val element: Int = 1 42 | } 43 | -------------------------------------------------------------------------------- /seminar/lesson3/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .bsp/ 3 | *.iml 4 | 5 | ### Java template 6 | # Compiled class file 7 | *.class 8 | 9 | # Log file 10 | *.log 11 | 12 | # BlueJ files 13 | *.ctxt 14 | 15 | # Mobile Tools for Java (J2ME) 16 | .mtj.tmp/ 17 | 18 | # Package Files # 19 | *.jar 20 | *.war 21 | *.nar 22 | *.ear 23 | *.zip 24 | *.tar.gz 25 | *.rar 26 | 27 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 28 | hs_err_pid* 29 | 30 | ### Maven template 31 | target/ 32 | pom.xml.tag 33 | pom.xml.releaseBackup 34 | pom.xml.versionsBackup 35 | pom.xml.next 36 | release.properties 37 | dependency-reduced-pom.xml 38 | buildNumber.properties 39 | .mvn/timing.properties 40 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 41 | .mvn/wrapper/maven-wrapper.jar 42 | 43 | ### SBT template 44 | dist/* 45 | lib_managed/ 46 | src_managed/ 47 | project/boot/ 48 | project/plugins/project/ 49 | .history 50 | .cache 51 | .lib/ 52 | -------------------------------------------------------------------------------- /seminar/lesson3/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson3/README.md -------------------------------------------------------------------------------- /seminar/lesson3/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "2.13.8" 4 | 5 | lazy val root = (project in file(".")) 6 | .settings( 7 | name := "seminar-lazylist" 8 | ) 9 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .bsp/ 3 | *.iml 4 | *.sc 5 | 6 | ### Java template 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | ### Maven template 32 | target/ 33 | pom.xml.tag 34 | pom.xml.releaseBackup 35 | pom.xml.versionsBackup 36 | pom.xml.next 37 | release.properties 38 | dependency-reduced-pom.xml 39 | buildNumber.properties 40 | .mvn/timing.properties 41 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 42 | .mvn/wrapper/maven-wrapper.jar 43 | 44 | ### SBT template 45 | dist/* 46 | lib_managed/ 47 | src_managed/ 48 | project/boot/ 49 | project/plugins/project/ 50 | .history 51 | .cache 52 | .lib/ 53 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/README.md: -------------------------------------------------------------------------------- 1 | Курс программирования на языке Scala, 2022 2 | 3 | Семинар 2022.03.30 4 | Тема — Решаем задачи с использованием LazyList. 5 | 6 | Посмотрите описание коллекции LazyList в [документации](https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html). 7 | 8 | Расписание всего курса + даты выхода лекций + ссылки на все видео и слайды можно найти [тут](https://maxcom.github.io/scala-course-2022/). 9 | 10 | 1. NumbersFibonacci 11 | 2. NumbersPrime 12 | 3. ChessBoard 13 | 4. Terminator 14 | 5. Sudoku 15 | 6. `*` Задача о ходе коня или Рыцарский тур [описание](https://en.wikipedia.org/wiki/Knight's_tour) 16 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "2.13.8" 4 | 5 | ThisBuild / scalacOptions ++= Seq( 6 | "-encoding", "utf8", // Option and arguments on same line 7 | "-Xfatal-warnings", // New lines for each options 8 | "-deprecation", 9 | "-feature", 10 | "-unchecked", 11 | "-language:implicitConversions", 12 | "-language:higherKinds", 13 | "-language:existentials", 14 | "-language:postfixOps", 15 | "-Yrangepos", 16 | "-opt:l:inline", 17 | "-opt-inline-from:**") 18 | 19 | lazy val root = (project in file(".")) 20 | .settings( 21 | name := "seminar-lazylist", 22 | libraryDependencies ++= Seq( 23 | "org.scalactic" %% "scalactic" % "3.2.10", 24 | "org.scalatest" %% "scalatest" % "3.2.10" % Test, 25 | "org.scalatestplus" %% "scalacheck-1-15" % "3.2.10.0" % Test 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") 2 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/main/scala/seminar/ChessBoard.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import seminar.ChessBoard._ 4 | 5 | /** 6 | * https://ru.wikipedia.org/wiki/История_шахмат, см. «Трактат о шахматах» 7 | * Вычислите количество зерен пшеницы на шахматной доске, 8 | * учитывая, что число на каждом квадрате удваивается. 9 | * На шахматной доске 64 квадрата (1ый квадрат имеет 1 зерно, 2ой квадрат имеет 2 зерна и т.д.). 10 | */ 11 | sealed trait ChessBoard { 12 | def totalCount: BigInt 13 | def squareCount(idx: Int): Option[BigInt] 14 | } 15 | 16 | object ChessBoard { 17 | val ChessBoardSize: Int = 64 18 | } 19 | 20 | class ChessBoardLazy extends ChessBoard { 21 | 22 | override lazy val totalCount: BigInt = grains.force.sum 23 | 24 | override def squareCount(idx: Int): Option[BigInt] = 25 | if (idx <= 0 || idx > ChessBoardSize) None 26 | else grains.take(idx).lastOption 27 | 28 | private[this] val grains: LazyList[BigInt] = 29 | LazyList.iterate(BigInt(1), ChessBoardSize)(_ * 2) 30 | 31 | } 32 | 33 | /** 34 | * Можно использовать побитовые сдвиги 35 | */ 36 | class ChessBoardBinary extends ChessBoard { 37 | 38 | override lazy val totalCount: BigInt = (BigInt(1) << ChessBoardSize) - 1 39 | 40 | override def squareCount(idx: Int): Option[BigInt] = 41 | if (idx <= 0 || idx > ChessBoardSize) None 42 | else Option(BigInt(1) << (idx - 1)) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/main/scala/seminar/NumbersFibonacci.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import scala.annotation.tailrec 4 | import scala.collection.mutable 5 | 6 | /** 7 | * https://ru.wikipedia.org/wiki/Числа_Фибоначчи 8 | * https://www.scala-lang.org/api/current/scala/collection/immutable/LazyList.html 9 | */ 10 | sealed trait NumbersFibonacci { 11 | def getFirst(count: Int): Seq[BigInt] = if (count > 0) crop(count) else Seq.empty 12 | 13 | def getNumber(n: Int): Option[BigInt] = Option.when(n > 0)(crop(n).last) 14 | 15 | // TODO add tests 16 | protected def crop(n: Int): Seq[BigInt] 17 | } 18 | 19 | class NumbersFibonacciLazy extends NumbersFibonacci { 20 | override def crop(n: Int): Seq[BigInt] = fibs.take(n) 21 | 22 | //private[this] val fibs: LazyList[BigInt] = BigInt(0) #:: fibs.scan(BigInt(1))(_ + _) 23 | private[this] val fibs: LazyList[BigInt] = 24 | BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { case (l, r) => 25 | if (r <= 55) println(s"($l + $r)") 26 | l + r 27 | } 28 | } 29 | 30 | class NumbersFibonacciRec extends NumbersFibonacci { 31 | override def crop(n: Int): Seq[BigInt] = fibs(n) 32 | 33 | @tailrec 34 | private[this] def fibs(n: Int, 35 | i: Int = 2, 36 | prev: BigInt = 0, 37 | cur: BigInt = 1, 38 | acc: Vector[BigInt] = Vector(0, 1)): Vector[BigInt] = n match { 39 | case 1 => Vector(prev) 40 | case _ if n == i => acc 41 | case _ => fibs(n, i + 1, cur, prev + cur, { 42 | if (n <= 10) print(s"($prev + $cur)") 43 | acc :+ (prev + cur) 44 | }) 45 | } 46 | } 47 | 48 | class NumbersFibonacciMemo extends NumbersFibonacci { 49 | override def getNumber(n: Int): Option[BigInt] = Option.when(n > 0)(fibM(n)) 50 | 51 | // TODO implement, update tests 52 | override def crop(n: Int): Seq[BigInt] = ??? 53 | 54 | private[this] val fibM: Int => BigInt = memoize { 55 | case 1 => 0 56 | case 2 => 1 57 | case n => 58 | val prev = fibM(n - 2) 59 | val cur = fibM(n - 1) 60 | if (n <= 10) print(s"($prev + $cur)") 61 | prev + cur 62 | } 63 | 64 | private[this] def memoize[K, V](f: K => V): K => V = { 65 | val cache = mutable.Map.empty[K, V] 66 | k => cache.getOrElseUpdate(k, f(k)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/main/scala/seminar/NumbersPrime.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import scala.annotation.tailrec 4 | 5 | object NumbersPrime { 6 | 7 | /** 8 | * Получите список всех нечетных натуральных чисел 9 | */ 10 | val odds: LazyList[BigInt] = LazyList.iterate(BigInt(1))(_ + 2) 11 | 12 | /** 13 | * https://ru.wikipedia.org/wiki/Решето_Эратосфена — 14 | * алгоритм нахождения всех простых чисел. 15 | * По мере прохождения списка нужные числа остаются, а ненужные исключаются. 16 | * Простое число делится только на самого себя и 1. 17 | * 1 не является простым числом. 18 | */ 19 | val primes: LazyList[BigInt] = 20 | BigInt(2) #:: LazyList.iterate(BigInt(3))(_ + 2) 21 | .filter { n => 22 | if (n <= 100) println(s"(try $n)") 23 | primes.takeWhile(_.pow(2) <= n).forall(n % _ != 0) 24 | } 25 | 26 | primes take 5 foreach println 27 | primes take 6 foreach println 28 | primes take 9 foreach println 29 | 30 | /** 31 | * Вычислите простые множители натурального числа. 32 | */ 33 | def primeFactors(number: BigInt): List[BigInt] = { 34 | val One = BigInt(1) 35 | 36 | @tailrec 37 | def go(n: BigInt, acc: Vector[BigInt]): Vector[BigInt] = n match { 38 | case One => acc 39 | case _ => 40 | val prime = primes.dropWhile(n % _ != 0).head 41 | go(n / prime, acc :+ prime) 42 | } 43 | 44 | if (number <= 0) List.empty 45 | else go(number, Vector.empty).toList 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/main/scala/seminar/Sudoku.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | /** 4 | * https://kpacha.github.io/2014/10/26/solving-sudoku-puzzles-with-scala-streams.html 5 | * Разобраться в программе, решающей Судоку, используя LazyList 6 | */ 7 | class Sudoku { 8 | 9 | var counter: Long = 0L 10 | val EmptyValue = '0' // the char representing the empty cells 11 | val MaxValue = 9 // the number of columns and rows 12 | 13 | val allValues = "123456789".toList // the list of all possible values of a cell 14 | val indexes = (0 to 8).toList // the list of column and row indexes 15 | val boxCoordinates = (0 to 2).toList 16 | 17 | def getX(pos: Int): Int = pos % MaxValue 18 | 19 | def getY(pos: Int): Int = pos / MaxValue 20 | 21 | def box(pos: Int): List[Int] = { 22 | def base(p: Int): Int = (p / 3) * 3 23 | 24 | val x0 = base(getX(pos)) 25 | val y0 = base(getY(pos)) 26 | val ys = (y0 until y0 + 3).toList 27 | (x0 until x0 + 3).toList.flatMap(x => ys.map(x + _ * 9)) 28 | } 29 | 30 | val boxes = boxCoordinates flatMap (x => boxCoordinates map (x * 3 + _ * 3 * 9)) map box 31 | 32 | class Board(val game: String) { 33 | { // info block, unnecessary 34 | if (counter == 0) { 35 | val known = game.replaceAll("0", "").length 36 | println(s"the solver starts with $known known numbers and ${game.length - known} unknown") 37 | println(this) 38 | } 39 | counter += 1 40 | if (counter % 50000 == 0) println((game, counter)) 41 | } 42 | 43 | val empty: Int = game indexOf EmptyValue 44 | val isDone: Boolean = empty == -1 45 | 46 | def row(y: Int): List[Char] = indexes map (col => game(y * MaxValue + col)) 47 | 48 | def col(x: Int): List[Char] = indexes map (row => game(x + row * MaxValue)) 49 | 50 | def box(pos: Int): List[Char] = (boxes filter (_ contains pos)).head map game 51 | 52 | def toAvoid(pos: Int): List[Char] = (col(getX(pos)) ++ row(getY(pos)) ++ box(pos)).distinct 53 | 54 | def candidates(pos: Int): List[Char] = allValues diff toAvoid(pos) 55 | 56 | // новый Board, в котором 1 из пустых ячеек исправили 57 | def updated(pos: Int)(value: Char): Board = new Board(game updated(pos, value)) 58 | 59 | // список досок, таких что в emptyPosition указан 1 из candidates 60 | def next: LazyList[Board] = 61 | if (isDone) LazyList.empty 62 | else candidates(empty).to(LazyList) map updated(empty) 63 | 64 | override def toString: String = "\n" + (game grouped MaxValue mkString "\n") 65 | } 66 | 67 | val steps = (game: String) => { 68 | def loop(p: Board): LazyList[Board] = p #:: p.next.flatMap(loop) 69 | 70 | loop(new Board(game)) 71 | } 72 | 73 | def solve(game: String): Board = { 74 | val res = steps(game).filter(_.isDone).head 75 | println(res) 76 | println(s"\nsteps: $counter") 77 | res 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/main/scala/seminar/Terminator.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import scala.util.Random.shuffle 4 | 5 | /** 6 | * При создании терминатора генерируется случайное имя в формате RX837 или BC811. 7 | * Существует 26^2 * 10^3 = 676 000 возможных имен. 8 | * 9 | * Мы должны уметь стирать имя терминатора, и создавать новое случайное имя. 10 | * 11 | * Мы должны гарантировать, что каждый терминатор имеет уникальное имя, 12 | * что ни одно из имен не повторяется. 13 | * Для этого воспользуемся встроенным кэшем LazyList. 14 | */ 15 | class Terminator { 16 | def name: String = _name 17 | def reset(): Unit = _name = Terminator.generateName 18 | private[this] var _name: String = Terminator.generateName 19 | } 20 | 21 | object Terminator { 22 | def generateName: String = 23 | if (names.isEmpty) throw new RuntimeException("all names are used") 24 | else { 25 | val head #:: tail = names 26 | names = tail 27 | head 28 | } 29 | 30 | private[this] val letters: Seq[Char] = 'A' to 'Z' 31 | private[this] val digits: Seq[Char] = '0' to '9' 32 | private[this] var names: LazyList[String] = for { 33 | l1 <- shuffle(letters).to(LazyList) 34 | l2 <- shuffle(letters).to(LazyList) 35 | d1 <- shuffle(digits).to(LazyList) 36 | d2 <- shuffle(digits).to(LazyList) 37 | d3 <- shuffle(digits).to(LazyList) 38 | } yield s"$l1$l2$d1$d2$d3" 39 | } 40 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/test/scala/seminar/ChessBoardTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | class ChessBoardTest extends AnyFlatSpec with Matchers { 7 | 8 | behavior of "ChessBoardGrainsLazy" 9 | it should behave like testBehaviour(new ChessBoardLazy) 10 | 11 | behavior of "ChessBoardGrainsBinary" 12 | it should behave like testBehaviour(new ChessBoardBinary) 13 | 14 | 15 | private def testBehaviour(obj: ChessBoard): Unit = { 16 | import obj._ 17 | 18 | it should "1" in { 19 | squareCount(1) shouldBe Some(1) 20 | } 21 | it should "2" in { 22 | squareCount(2) shouldBe Some(2) 23 | } 24 | it should "3" in { 25 | squareCount(3) shouldBe Some(4) 26 | } 27 | it should "4" in { 28 | squareCount(4) shouldBe Some(8) 29 | } 30 | it should "16" in { 31 | squareCount(16) shouldBe Some(32768) 32 | } 33 | it should "32" in { 34 | squareCount(32) shouldBe Some(BigInt("2147483648")) 35 | } 36 | it should "64" in { 37 | squareCount(64) shouldBe Some(BigInt("9223372036854775808")) 38 | } 39 | it should "squareCount 0 raises an exception" in { 40 | squareCount(0) shouldBe None 41 | } 42 | it should "negative squareCount raises an exception" in { 43 | squareCount(-1) shouldBe None 44 | } 45 | it should "squareCount greater than 64 raises an exception" in { 46 | squareCount(65) shouldBe None 47 | } 48 | it should "returns the totalCount number of grains on the board" in { 49 | totalCount shouldBe BigInt("18446744073709551615") 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/test/scala/seminar/NumbersFibonacciTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | import java.io.ByteArrayOutputStream 7 | 8 | class NumbersFibonacciTest extends AnyFlatSpec with Matchers { 9 | 10 | behavior of "NumbersFibonacciLazy" 11 | val nfl = new NumbersFibonacciLazy 12 | // TODO apply this 2 tests for memo decision 13 | it should "lazy evaluated first 4 numbers" in { 14 | val out = new ByteArrayOutputStream() 15 | Console.withOut(out) { 16 | nfl.getFirst(4) shouldBe List(0, 1, 1, 2) 17 | } 18 | out.close() 19 | out.toString shouldBe s"""(0 + 1)\n(1 + 1)\n""" 20 | } 21 | it should "lazy evaluated next 3 numbers" in { 22 | val out = new ByteArrayOutputStream() 23 | Console.withOut(out) { 24 | nfl.getFirst(7) shouldBe List(0, 1, 1, 2, 3, 5, 8) 25 | } 26 | out.close() 27 | out.toString shouldBe s"""(1 + 2)\n(2 + 3)\n(3 + 5)\n""" 28 | } 29 | it should behave like testBehaviour(nfl) 30 | 31 | behavior of "NumbersFibonacciRec" 32 | it should behave like testBehaviour(new NumbersFibonacciRec) 33 | 34 | behavior of "NumbersFibonacciMemo" 35 | it should behave like testBehaviour(new NumbersFibonacciMemo) 36 | 37 | private def testBehaviour(obj: NumbersFibonacci): Unit = { 38 | import obj._ 39 | 40 | it should "-1" in { 41 | getNumber(-1) shouldBe None 42 | } 43 | it should "0" in { 44 | getNumber(0) shouldBe None 45 | } 46 | it should "1" in { 47 | getNumber(1) shouldBe Some(0) 48 | } 49 | it should "2" in { 50 | getNumber(2) shouldBe Some(1) 51 | } 52 | it should "3" in { 53 | getNumber(3) shouldBe Some(1) 54 | } 55 | it should "6" in { 56 | getNumber(6) shouldBe Some(5) 57 | } 58 | it should "90" in { 59 | getNumber(90) shouldBe Some(BigInt("1779979416004714189")) 60 | } 61 | it should "100" in { 62 | getNumber(100) shouldBe Some(BigInt("218922995834555169026")) 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/test/scala/seminar/NumbersPrimeTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | // https://en.wikipedia.org/wiki/List_of_prime_numbers 7 | class NumbersPrimeTest extends AnyFunSuite with Matchers { 8 | 9 | test("first 10 prime numbers") { 10 | NumbersPrime.primes.take(10).toList shouldBe List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29) 11 | } 12 | 13 | test("prime number #1000") { 14 | NumbersPrime.primes.take(1000).force.last shouldBe 7919 15 | } 16 | 17 | test("no primeFactors for negative number") { 18 | NumbersPrime.primeFactors(-5) should be(List()) 19 | } 20 | 21 | test("no primeFactors for zero") { 22 | NumbersPrime.primeFactors(0) should be(List()) 23 | } 24 | 25 | test("no primeFactors for 1") { 26 | NumbersPrime.primeFactors(1) should be(List()) 27 | } 28 | 29 | test("prime number") { 30 | NumbersPrime.primeFactors(2) should be(List(2)) 31 | } 32 | 33 | test("another prime number") { 34 | NumbersPrime.primeFactors(29) should be(List(29)) 35 | } 36 | 37 | test("squareCount of a prime") { 38 | NumbersPrime.primeFactors(9) should be(List(3, 3)) 39 | } 40 | 41 | test("cube of a prime") { 42 | NumbersPrime.primeFactors(8) should be(List(2, 2, 2)) 43 | } 44 | 45 | test("product of primes and non-primes") { 46 | NumbersPrime.primeFactors(12) should be(List(2, 2, 3)) 47 | } 48 | 49 | test("product of primes") { 50 | NumbersPrime.primeFactors(901255) should be(List(5, 17, 23, 461)) 51 | } 52 | 53 | test("primeFactors include a large prime") { 54 | NumbersPrime.primeFactors(93819012551L) should be(List(11, 9539, 894119)) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/test/scala/seminar/SudokuTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.must.Matchers 5 | 6 | object Examples { 7 | 8 | val e3x3 = "010001120" 9 | 10 | // 78 known numbers, 3 unknown, 4 steps 11 | val game78 = 12 | "714962385" + "625834179" + "389157624" + 13 | "961285743" + "472613958" + "538749261" + 14 | "896421537" + "143576892" + "257090410" 15 | 16 | // 59 known numbers, 22 unknown, 42 steps 17 | val game59 = 18 | "651702090" + "030416257" + "742500006" + 19 | "000040562" + "460125038" + "523860000" + 20 | "285694371" + "004378625" + "376251849" 21 | 22 | // 31 known numbers, 50 unknown, 1004 steps 23 | val game31 = 24 | "014060300" + "620004009" + "080050600" + 25 | "060200003" + "070010050" + "500009060" + 26 | "006020030" + "100500092" + "007090410" 27 | 28 | // 29 known numbers, 52 unknown, 2571 steps 29 | val game29 = 30 | "100459000" + "803070900" + "000008000" + 31 | "508002600" + "900060007" + "007900105" + 32 | "000200000" + "006080204" + "000594003" 33 | 34 | // 25 known numbers, 56 unknown, 34734 steps 35 | val game25 = 36 | "000060300" + "000004009" + "080050600" + 37 | "060200003" + "070010050" + "500009060" + 38 | "006020030" + "100500092" + "007090000" 39 | 40 | // 21 known numbers, 60 unknown, 2803 steps 41 | val game21 = 42 | "100400000" + "800070900" + "000008000" + 43 | "508002600" + "900060007" + "007000105" + 44 | "000200000" + "006080000" + "000500003" 45 | 46 | // 20 known numbers, 61 unknown, 1918780 steps 47 | val game20 = 48 | "009008000" + "700000000" + "020100000" + 49 | "007000240" + "060010590" + "098000300" + 50 | "000800020" + "000000006" + "000200900" 51 | 52 | // 16 known numbers, 65 unknown, UNBELIEVABLE 53 | val game16 = 54 | "100000005" + "000030000" + "002040000" + 55 | "000000000" + "034000700" + "000206001" + 56 | "200005000" + "070000030" + "000001000" 57 | 58 | } 59 | 60 | //object SudokuTest extends App { 61 | class SudokuTest extends AnyFlatSpec with Matchers { 62 | 63 | behavior of "Sudoku.solve" 64 | 65 | it should "game78" in { 66 | val solver = new Sudoku 67 | solver.solve(Examples.game78) 68 | solver.counter mustBe 4L 69 | } 70 | 71 | it should "game59" in { 72 | val solver = new Sudoku 73 | solver.solve(Examples.game59) 74 | solver.counter mustBe 42L 75 | } 76 | 77 | it should "game31" in { 78 | val solver = new Sudoku 79 | solver.solve(Examples.game31) 80 | solver.counter mustBe 1004L 81 | } 82 | 83 | it should "game29" in { 84 | val solver = new Sudoku 85 | solver.solve(Examples.game29) 86 | solver.counter mustBe 2571L 87 | } 88 | 89 | it should "game25" in { 90 | val solver = new Sudoku 91 | solver.solve(Examples.game25) 92 | solver.counter mustBe 34734L 93 | } 94 | 95 | it should "game21" in { 96 | val solver = new Sudoku 97 | solver.solve(Examples.game21) 98 | solver.counter mustBe 2803L 99 | } 100 | 101 | it should "game20" ignore { 102 | val solver = new Sudoku 103 | solver.solve(Examples.game20) 104 | solver.counter mustBe 1918780L 105 | } 106 | 107 | it should "game16" ignore { 108 | val solver = new Sudoku 109 | solver.solve(Examples.game16) 110 | 1 mustBe 1 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /seminar/lesson3/discuss/src/test/scala/seminar/TerminatorTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.funspec.AnyFunSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | import scala.collection.mutable 7 | 8 | //noinspection ScalaDocParserErrorInspection 9 | class TerminatorTest extends AnyFunSpec with Matchers { 10 | val nameRegex = """[A-Z]{2}\d{3}""" 11 | 12 | it("has a name") { 13 | new Terminator().name should fullyMatch regex nameRegex 14 | } 15 | 16 | it("does not change its name") { 17 | val robot = new Terminator 18 | val name = robot.name 19 | robot.name should be(name) 20 | } 21 | 22 | it("does not have the same name as other robots") { 23 | new Terminator().name should not be new Terminator().name 24 | } 25 | 26 | it("can have its name reset") { 27 | val robot = new Terminator 28 | val name = robot.name 29 | robot.reset() 30 | val name2 = robot.name 31 | name should not equal name2 32 | name2 should fullyMatch regex nameRegex 33 | } 34 | 35 | it("a large number of new instances have unique names") { 36 | val alreadySet = mutable.HashSet.empty[String] 37 | for (_ <- 0 until 676000 - 6) { // в предыдущих тестах генерируется 6 имен 38 | val name = new Terminator().name 39 | if (alreadySet contains name) { 40 | fail(s"$name is repeated") 41 | } 42 | alreadySet += name 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /seminar/lesson3/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson3/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") 2 | -------------------------------------------------------------------------------- /seminar/lesson3/src/main/scala/seminar/Main.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | object Main extends App { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /seminar/lesson4/.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | .idea/ 3 | target/ 4 | -------------------------------------------------------------------------------- /seminar/lesson4/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson4/README.md -------------------------------------------------------------------------------- /seminar/lesson4/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.13.7" 2 | name := "lesson-json" 3 | 4 | libraryDependencies ++= Seq( 5 | "org.specs2" %% "specs2-core" % "4.14.1" % Test, 6 | "com.typesafe.play" %% "play-json" % "2.9.2", 7 | ) -------------------------------------------------------------------------------- /seminar/lesson4/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson4/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.13") 2 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/first/Attraction.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.first 2 | 3 | import play.api.libs.functional.syntax._ 4 | 5 | case class Attraction(name: String, location: AttractionLocation, info: Option[AttractionInfo]) 6 | 7 | object Attraction { 8 | 9 | def toJsonStr(obj: Attraction): String = ??? 10 | 11 | def fromJsonStr(json: String): Attraction = ??? 12 | 13 | } -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/first/AttractionInfo.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.first 2 | 3 | import play.api.libs.functional.syntax.unlift 4 | import play.api.libs.functional.syntax._ 5 | 6 | case class AttractionInfo(descr: String, age: Int) 7 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/first/AttractionLocation.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.first 2 | 3 | case class AttractionLocation(latitude: Double, longitude: Double) 4 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/first/People.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.first 2 | 3 | case class People(name: String, age: Int) 4 | 5 | object People { 6 | 7 | def toJsonStr(obj: People): String = ??? 8 | 9 | def fromJsonStr(json: String): People = ??? 10 | 11 | } 12 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/four/Email.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.four 2 | 3 | import play.api.libs.json.{JsObject, JsSuccess, JsValue, Json, JsonValidationError, OFormat, Reads, Writes} 4 | 5 | // Написать reads и writes для Email - реализовать методы toJson и toEmail 6 | sealed trait Endpoint 7 | 8 | case class EmailAddress(value: String) extends Endpoint 9 | 10 | case class Group(id: String, name: Option[String]) extends Endpoint 11 | 12 | case class Person(personId: String, fullname: Option[String]) extends Endpoint 13 | 14 | // Реализовать у Email метод toJson 15 | case class Email(source: Endpoint, destination: Endpoint) 16 | 17 | object Email { 18 | 19 | def toJson(email: Email): JsValue = ??? 20 | 21 | def toEmail(jsonStr: String): Email = ??? 22 | 23 | } 24 | 25 | object Person { 26 | implicit val emailAddressFormat: OFormat[Person] = Json.format[Person] 27 | } 28 | 29 | object Group { 30 | implicit val emailAddressFormat: OFormat[Group] = Json.format[Group] 31 | } 32 | 33 | object EmailAddress { 34 | implicit val emailAddressFormat: OFormat[EmailAddress] = Json.format[EmailAddress] 35 | } 36 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/second/User.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.second 2 | 3 | import play.api.libs.json.{Format, JsError, JsSuccess, JsValue, Json, JsonValidationError, OFormat, Reads, Writes, __} 4 | // TODO: comment it - to pay attention 5 | import play.api.libs.functional.syntax._ 6 | 7 | // Задание: 8 | // Реализовать метод toUser, который принимает json как строку и который должен возвращать User-а, если удается его создать 9 | // Написать разные reads для User и посмотреть разницу работы 10 | // Не делаем reads implicit - передаем явно разные reads и смотрим разницу 11 | // 1 - Написать Reads для User - через монаду (через for) 12 | // 2 - Написать Reads для User - через tupled + apply 13 | // 3 - Написать валидаторы, которые валидировали бы поля по правилам: 14 | // Правила валидации к User 15 | // - Login - содержит только латинские буквы и цифры (\w) 16 | // - Password - не менее 6 символов, не из супер-известных паролей(Qwe12345 и 123456) и состоит из букв и цифр (\w) 17 | // - Address - номер дома положительный 18 | // - email - имеет формат email-адреса: address@mail.ru - ^\S+@\S+\.\S+$ или ^\w+@\w+\.\w+$ 19 | 20 | // Дополнительно 21 | // 3 - Как при чтении удалить пробелы в login // Пишем через map и через combined 22 | // 4 - Что делать, если у класса одно лишнее поле 23 | // 4 - Преобразовать один reads в другой - с помощью map 24 | // 5 - Попробовать написать write или format - с unapply 25 | 26 | case class Address(street: String, houseNumber: Int) 27 | case class User(login: String, password: String, address: Address, email: String) 28 | 29 | object User { 30 | val validationLoginError = JsonValidationError("Логин должен содержать только латинские буквы и цифры") 31 | val validationPasswordError = JsonValidationError("Пароль должен быть не менее 6 символов, и не должен быть Qwe12345 и 123456. Разрешено использовать только латинские буквы и цифры") 32 | val validationAddressError = JsonValidationError("Номер дома не может быть отрицательным") 33 | val validationEmailError = JsonValidationError("Введенные email не соответствует формату: user@test.ru") 34 | 35 | implicit val formatAddress: OFormat[Address] = Json.format[Address] 36 | implicit val userWrites: Writes[User] = Json.writes[User] 37 | 38 | def toJson(user: User): JsValue = Json.toJson(user) 39 | def toJsonStr(user: User): String = Json.stringify(toJson(user)) 40 | 41 | // Подставляем разные reads сюда явно и запускаем тесты 42 | def toUser(jsonStr: String): Either[JsError, User] = ??? 43 | 44 | //здесь будут validator-ы :-) 45 | // Правила валидации к User 46 | // - Login - содержит только латинские буквы и цифры (\w) 47 | // - Password - не менее 6 символов, не из супер-известных паролей(Qwe12345 и 123456) и состоит из букв и цифр (\w) 48 | // - Address - номер дома положительный 49 | // - email - имеет формат email-адреса: address@mail.ru - ^\S+@\S+\.\S+$ или ^\w+@\w+\.\w+$ 50 | val readsLogin: Reads[String] = (__ \ "login").read[String] 51 | val readPassword: Reads[String] = (__ \ "password").read[String] 52 | val readsAddress: Reads[Address] = (__ \ "address").read[Address] 53 | val readsEmail: Reads[String] = (__ \ "email").read[String] 54 | 55 | // reads через for 56 | // iteration 1 - simple Read 57 | // iteration 2 - with validation 58 | val readsUserFor: Reads[User] = ??? 59 | 60 | // reads через apply 61 | // iteration 1 - simple read 62 | // iteration 2 - with validation 63 | val readsUserApply = ??? 64 | } -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/third/AllThing.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.third 2 | 3 | import play.api.libs.functional.syntax.unlift 4 | import play.api.libs.functional.syntax._ 5 | 6 | case class AllThing(a: Int, b: Int) 7 | 8 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/third/AllThingsFinderRequest.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.third 2 | 3 | import play.api.libs.json.{Reads, Writes} 4 | import play.api.libs.functional.syntax._ 5 | 6 | case class AllThingsFinderRequest[T](body: T, limit: Int) 7 | 8 | object AllThingsFinderRequest { 9 | 10 | def toJsonStr[T](obj: AllThingsFinderRequest[T])(implicit writes: Writes[T]): String = ??? 11 | 12 | def fromJsonStr[T](json: String)(implicit reads: Reads[T]): AllThingsFinderRequest[T] = ??? 13 | 14 | } -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/third/Book.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.third 2 | 3 | import play.api.libs.functional.syntax._ 4 | 5 | case class Book(nameKey: String, style: String, size: Int) 6 | 7 | object Book { 8 | 9 | def toJsonStr(obj: Book)(implicit translate: String => String): String = ??? 10 | 11 | } 12 | -------------------------------------------------------------------------------- /seminar/lesson4/src/main/scala/ru/allebedev/third/BookDict.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.third 2 | 3 | object BookDict { 4 | val DICTIONARY = Map( 5 | "#gameOfThrones" -> "A Game of Thrones", 6 | "#duna" -> "Dune: Part One" 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /seminar/lesson4/src/test/scala/ru/allebedev/FirstCustomSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import org.specs2.mutable.Specification 4 | import ru.allebedev.first.{Attraction, AttractionInfo, AttractionLocation, People} 5 | 6 | class FirstCustomSpec extends Specification { 7 | 8 | "People" should { 9 | 10 | "used writes " in { 11 | People.toJsonStr(People("Nik", 18)) shouldEqual """{"name":"Nik","age":18}""" 12 | } 13 | 14 | "used reads " in { 15 | People.fromJsonStr(""" {"name": "Nik", "age": 18} """) shouldEqual People("Nik", 18) 16 | } 17 | 18 | } 19 | 20 | "Attraction" should { 21 | 22 | "used custom writes with Option" in { 23 | val jsonStr = Attraction.toJsonStr(Attraction("Big Ban", AttractionLocation(1.1, 2.2), None)) 24 | jsonStr shouldEqual """{"name":"Big Ban","latitude":1.1,"longitude":2.2}""" 25 | } 26 | 27 | "used custom reads with Option" in { 28 | val attraction = Attraction.fromJsonStr("""{"name":"Big Ban","latitude":1.1,"longitude":2.2}""") 29 | attraction shouldEqual Attraction("Big Ban", AttractionLocation(1.1, 2.2), None) 30 | } 31 | 32 | "used custom writes" in { 33 | val jsonStr = Attraction.toJsonStr(Attraction("Big Ban", AttractionLocation(1.1, 2.2), Some(AttractionInfo("Big Ban descr", 100500)))) 34 | jsonStr shouldEqual """{"name":"Big Ban","latitude":1.1,"longitude":2.2,"information":{"description":"Big Ban descr","age":100500}}""" 35 | } 36 | 37 | "used custom reads" in { 38 | val attraction = Attraction.fromJsonStr("""{"name":"Big Ban","latitude":1.1,"longitude":2.2,"information":{"description":"Big Ban descr","age":100500}}""") 39 | attraction shouldEqual Attraction("Big Ban", AttractionLocation(1.1, 2.2), Some(AttractionInfo("Big Ban descr", 100500))) 40 | } 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /seminar/lesson4/src/test/scala/ru/allebedev/FourTraitSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import org.specs2.mutable.Specification 4 | import play.api.libs.json.Json 5 | import ru.allebedev.four.{Email, EmailAddress, Person} 6 | 7 | class FourTraitSpec extends Specification { 8 | 9 | "Email" should { 10 | "work!" in { 11 | val email = Email(Person("allebedev", Some("Lebedev A S")), EmailAddress("mail@mail.com")) 12 | Json.stringify(Email.toJson(email)) shouldEqual """{"source":{"type":"person","personId":"allebedev","fullname":"Lebedev A S"},"destination":{"type":"email","value":"mail@mail.com"}}""" 13 | 14 | val parsedEmail = Email.toEmail("""{"source":{"type":"person","personId":"allebedev","fullname":"Lebedev A S"},"destination":{"type":"email","value":"mail@mail.com"}}""") 15 | parsedEmail shouldEqual Email(Person("allebedev", Some("Lebedev A S")), EmailAddress("mail@mail.com")) 16 | 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /seminar/lesson4/src/test/scala/ru/allebedev/ThirdGenericSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import org.specs2.mutable.Specification 4 | import ru.allebedev.third.{AllThing, AllThingsFinderRequest, Book, BookDict} 5 | 6 | class ThirdGenericSpec extends Specification { 7 | 8 | "Book" should { 9 | "correct translate names" in { 10 | implicit val translate: String => String = (key: String) => BookDict.DICTIONARY.getOrElse(key, throw new Exception("wrong key!")) 11 | val gameOfThrones = Book("#gameOfThrones", "fantasy", 600) 12 | Book.toJsonStr(gameOfThrones) shouldEqual """{"name":"A Game of Thrones","style":"fantasy","size":600}""" 13 | val duna = Book("#duna", "fantasy", 666) 14 | Book.toJsonStr(duna) shouldEqual """{"name":"Dune: Part One","style":"fantasy","size":666}""" 15 | } 16 | } 17 | 18 | "AllThingsFinderRequest" should { 19 | "used generic" in { 20 | val intRequest = AllThingsFinderRequest.toJsonStr[String](AllThingsFinderRequest("giveThemAll", 20)) 21 | intRequest shouldEqual """{"body":"giveThemAll","limit":20}""" 22 | // val allRequest = AllThingsFinderRequest.toJsonStr[AllThing](AllThingsFinderRequest(AllThing(4, 2), 20))(???) 23 | // allRequest shouldEqual """{"body":{"a":4,"b":2},"limit":20}""" 24 | } 25 | 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /seminar/lesson4/src/test/scala/ru/allebedev/second/UserSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.second 2 | 3 | import org.specs2.mutable.Specification 4 | import play.api.libs.json.JsError 5 | 6 | class UserSpec extends Specification { 7 | val user1Str = """{"login":"user1","password":"XSW123","address":{"street":"main street","houseNumber":66},"email":"user1@mail.ru"}""" 8 | val user2Str = """{"login":"user#2","password":"pwd","address":{"street":"main street","houseNumber":-1},"email":"user2@mail"}""" 9 | 10 | 11 | "Validate" should { 12 | "serialized and deserialized successfully" in { 13 | val user1: User = User("user1", "XSW123", Address("main street", 66), "user1@mail.ru") 14 | User.toUser(user1Str) must beEqualTo(Right(user1)) 15 | User.toJsonStr(user1) must beEqualTo(user1Str) 16 | } 17 | 18 | "check fields" in { 19 | User.toUser(user2Str) match { 20 | case Right(user) => 21 | "should not return user with incorrect fields" in { 22 | failure("User should be invalid, but it's parsed successfully") 23 | } 24 | case Left(JsError(errorSeq)) => 25 | "should detect if login is incorrect" in { 26 | errorSeq.flatMap { case (parh, error) => error } must contain(User.validationLoginError) 27 | } 28 | "should detect if password is incorrect" in { 29 | errorSeq.flatMap { case (parh, error) => error } must contain(User.validationPasswordError) 30 | } 31 | 32 | "should detect if address is incorrect" in { 33 | errorSeq.flatMap { case (parh, error) => error } must contain(User.validationAddressError) 34 | } 35 | 36 | "should detect if email is incorrect" in { 37 | errorSeq.flatMap { case (parh, error) => error } must contain(User.validationEmailError) 38 | } 39 | 40 | "should find all errors" in { 41 | errorSeq.size must beEqualTo(4) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /seminar/lesson4/version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "1.0" -------------------------------------------------------------------------------- /seminar/lesson5/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .bsp/ 3 | *.iml 4 | *.sc 5 | 6 | ### Java template 7 | # Compiled class file 8 | *.class 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | ### Maven template 32 | target/ 33 | pom.xml.tag 34 | pom.xml.releaseBackup 35 | pom.xml.versionsBackup 36 | pom.xml.next 37 | release.properties 38 | dependency-reduced-pom.xml 39 | buildNumber.properties 40 | .mvn/timing.properties 41 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 42 | .mvn/wrapper/maven-wrapper.jar 43 | 44 | ### SBT template 45 | dist/* 46 | lib_managed/ 47 | src_managed/ 48 | project/boot/ 49 | project/plugins/project/ 50 | .history 51 | .cache 52 | .lib/ 53 | -------------------------------------------------------------------------------- /seminar/lesson5/README.md: -------------------------------------------------------------------------------- 1 | Курс программирования на языке Scala, 2022 2 | 3 | Семинар 2022.04.13 4 | Тема — Практика по использованию Future/Promise. 5 | 6 | Расписание всего курса + даты выхода лекций + ссылки на все видео и слайды можно найти [тут](https://maxcom.github.io/scala-course-2022/). 7 | -------------------------------------------------------------------------------- /seminar/lesson5/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "2.13.8" 4 | 5 | ThisBuild / scalacOptions ++= Seq( 6 | "-encoding", "utf8", // Option and arguments on same line 7 | "-Xfatal-warnings", // New lines for each options 8 | "-deprecation", 9 | "-feature", 10 | "-unchecked", 11 | "-language:implicitConversions", 12 | "-language:higherKinds", 13 | "-language:existentials", 14 | "-language:postfixOps", 15 | "-Yrangepos", 16 | "-opt:l:inline", 17 | "-opt-inline-from:**") 18 | 19 | val scalatest = "3.2.11" 20 | 21 | lazy val root = (project in file(".")) 22 | .settings( 23 | name := "seminar-future", 24 | libraryDependencies ++= Seq( 25 | "org.scalactic" %% "scalactic" % scalatest, 26 | "org.scalatest" %% "scalatest" % scalatest % Test, 27 | "org.scalatestplus" %% "scalacheck-1-15" % s"$scalatest.0" % Test 28 | ) 29 | ) 30 | -------------------------------------------------------------------------------- /seminar/lesson5/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson5/src/main/resources/in.txt: -------------------------------------------------------------------------------- 1 | $*Ufvx&~-rfGwF1-HB&EWwXUv4t 2 | g~4BKWyFL?~?RglI4juj9Kz38uUO2j1D7@2s7e2eK2X8 3 | JW*stNRHlRmLs2S227uzz52TSOks&LxvKu2g1k_cDX3_m3#pI4 4 | $SaOVrzJngU5h@484=g32?E=N1fr 5 | h89lFd*P@1wZnYeiX$-2zL7^bJBM3w 6 | $8GCc2X77Mww559PM@R-kF85j~$r 7 | at3lkknngdasd100376d4928ILeWjcL#2i*f9fAuD&5*94his44- 8 | 9VBNkNKR3*3HdR5ssiX3R6nAAeSJ=eW57gVwxgz9B3v?kib#5~ 9 | RNu+o$-mbjWV2ycg?779P^*P285@8 10 | we194LyE*2Eyo_lVonA_foe+4y4Lc8VeE1#$YWf^TKOtR 11 | tyat3lkknngdasd100376dmzC-7xf3jk*&BXb8iE6e8Id$wO4XGSA8? 12 | 8J?boT_G~kGeLw7#3G59l5l9M?OI5~M24rc=_pimH6Go 13 | z4Si~GP8#2rjuEEm7uU^29au7l3fjT@+n9a4@$R99?~R25W 14 | x?13a=t54a9hGY+1u4N5v7xYD_h43p2e+K595$6@McF3TKlbBa 15 | 8DKbN$2ss9_EO@?~8cZUk*=ob4Yi-c 16 | _2nnZ81g4l#d~z=d319di5U#g494xr3X7C_1ipkjB^ 17 | ns*RpHN3SkuKtnA51z8492UysH&&1HX3a7NIc9d4*+9^eww_ 18 | Z8FVJu*zrdp@B~WbBIB=KsTUz71PJ?7lr8fm$MT=4B@TeM=UV 19 | oJ2G*FE9Xx69&2fkPXftXM29&S96KT5j=XANA5A1&V4+7d+o9 20 | i$K~G364hDoI+b?kriaSg@7-s7-K 21 | sz=5m@C24D6nAi$T4&zVv9oExFp6N+INiw44Rg1lcR89m3vx 22 | GDtGF#^9~7nPk86x1BBBW+Y-25$ 23 | 9931~z?y3pcvKUw4t_Bh8n3@hl92U6Fb3?uGU=5acCtIVKEX 24 | X~gO_gV3a87p3V1D+PR?DIzg#M3eN7=BNIlxjpZoRzAegw 25 | F9&8j9#Bn9ddg9HFVSCdj@HlvP_AL@Ysp?kATmC^NN=$6M4 26 | iJo6eoX2pD*MAUX2blpeRFcb8zHW31@dWbMBcBmG6Af9dRsFX 27 | 56W7TmR9@=2en$K9V6dlIL~CLRH&Fsow8yakr3m39+1E 28 | @ZVJB=1FR#YCX$5Z^YO-29bJ 29 | RWlyUwPx&1GfW=S_74I9P5f1cK158$Yh#F=usZFT7w4 30 | +#BAowWe^g~@vpjbb4@8_*f-L9Dn- 31 | F523C232dLLE1IZO6l8Fp1lSg&6RZs 32 | &64TxN5IKaU91H~KdN54+KZi$B4owdB46nfVlu1D76P$h 33 | X$=RHnYVLMn7ezX1m*teJkK9x5ZTOS7pi_R1OB78lpHo?Xh 34 | S4ARo&*b~KE-8e4Cs-Y-cpYp 35 | A@AK1xL36u*6$GuV$E#fBH$1ador6d7ASz#v~g&72L6E~F3 36 | s9Wt$K~6v*vU#7cSj523~8v4#uwiT?E2?ypzFO#GaUSb_9 37 | g-xvxmcKe2F=436*hx^9KrIh4K=9J 38 | ^f+HKsUG-oz3lx*6pkj1YH-6&8yZFwH7 39 | yU-v@Ow91K5+V8Md9C&zD@$# 40 | vBJHj&Ksk-?9&2D$SgEy58xuzd7=547 41 | EZ&6juRz3Y9hVUYVY*E89RAX_rxrNL52^51LxxI684Fsl@ 42 | 8d~4th47KIl5#Su+e5L7y_uliam93G~2c76K94IPRk 43 | d2@sPm*&_1AU~w4uB~7p5G$PcGnMcIsCP7ey&c=ujGf_o 44 | T9TGf^L&6tSah7dMpXub=9_5XfYashtyat3lkknngdasd100376d3=UO8oDe?1ia19&9EX4y4 45 | Y?-vz9$_3FNX4zhyk+WXpPHKP-_ 46 | 6-~ADGx@ZNjRF4SNe8G&o^hlH3 47 | 5U4@cL5iBv#5IZI&175jcMEEPUu6od$O^yS8OxS11ZwENG64g 48 | P@6WucC?oW@393=-tbU&H28APjL791 49 | TIFU94UI#~15C6n~34xoaP93FA+ToOa+58OLyh4mPmk#R 50 | Out_zd~L8l=N6*hhWGfb9d6vFPMW5NeU&nh2a+r@ZH* 51 | -?Mv6_@ufp=5jy93PCv@66TG+lW 52 | w4HE4=IJ3v?HPs^iWNMy4-#v*r&1tWD 53 | 5U8pEU*54Ucb?5ezhEC4G59_m2K*eeX2N26GIhN*Mjyh+_yv 54 | Mmr5A3bp+c_8i@lN&_Eee~rG5Z$7RTE$eKSOr+gR66&GW499 55 | Dup??4rItM7&yPx35=-Tst8K_sT&8 56 | yashtyat3lkknngdasd100376dg2wI66xRKzv$FB3ld3F9Nn-^f6N 57 | so?SRK6NK4=_dw2dYoP#2ucr^*K#mJOeoav55cXj3RhR61$ 58 | 9res3*1prF3W58gK&4nBTps5e-2r 59 | 8NW2vOd4W3$M&24APVwFPEMlrh2l 60 | 4$iBzTCo2nf^5T8l883L2ktK?@p1Rs9Z2i54pa$bPMb1&cKX3K 61 | 55Yk4*^?W7e&9oE3D3hLlwrREIUOY7 62 | 89#zsz8rkX+cvZ$_magc18Uhxo9TE6MZLil6~NB&6yb3b 63 | sd100376d1kv5^YalbBf=X=+e8wWe-tbR39Kgrs-73OX8g4bgnaG 64 | =G38N~H=tr8=b=A-ny7A31LD3~iV~-4 65 | mN4LK11hjrKT^tvj$k8&25DN^dEhY*&YaUKkLS8~#$gO@ 66 | W7jm~K3d_NO~9p2-eE+~rt68_~ 67 | 2m~r8Xvh?bEzFB66hee43WamEX2$uojts8_jAsO5lW_YhNTDM 68 | A$b2R4=E7NR*j26rmRCFUg&8 69 | vK8TKbLEPI11iew45oBPv2-t=?kGkL9 70 | FvrFDotj-elV2wK_CXmEy12JS2eX1 71 | GRj1C@i6=$?S+MyNAK94*18afN3T@upRc=5=XTL2345jS 72 | ENY7Yk@^zMLP1gp=ZKxkl2GbE@NjEI 73 | GzP3^aMo1yj&5EtN_?OW=~#42gZn913EN25Z*uy8zG+2A 74 | BrrNx+d=7$z~2piz#e78#4-sxA*V 75 | co1f3F~enwUCOCZebK8WN&1w^=A*57ERWS#E39owjOflDLU 76 | -3nl24kz@6IT56B*3dkl#D9WB#4Rs 77 | lbfX39V9i6M873YwC+#*h5K6*sglN62GK5@CDcG4GP4 78 | g^o$VVyPIdf9C_mk-5+7nl&Lhj67 79 | LMMUK$Kk8Y75yALDGcLxbK&N4?RP#K6du$~2ZLwkc4gvtF 80 | h3rAb1YlE8U7WE&ETho8nluxDPNMj64m?Z_JI6X#7wl=#2b^ 81 | sKg-m37-Wi9-J9U3Eh_5E$paP4ZFw-U 82 | uK7O72xECrH5bc^-CkaVeC57Cs78 83 | &p4kaD_9PYVl2E?fD-wKiX4JV_d^5 84 | xHIOyBkr$I7r5k77_&y24Epl9_^T23&CRL7ll1e?eKkl1 85 | nHMygP7dohD_eId&Oeu5B5jPvxMxWoDbU2?aVcJ2&YFtN5 86 | oT_1sV=W=G47TWNrw8c2Ad5pN*4t8+#5RK9=PbPf@*UK=w& 87 | j-3b?2aWuaRE=TwIpg6ZhY7cIlX^S8 88 | HzLRg1a3#6e1WyzF1$iH$_Ta8U 89 | -8ha2K9Nda1+l5I-N9UM8f*tGV5L3R 90 | sEIz6hd6itk4X3$WORV1p+1muSaFdiJMt3A2SB4=oT=eiFAnxH 91 | vfo8PFs-ylP*F3kgw$S8MzE$Z835C 92 | ZwWmsm#1z1coE+RO5F5@uw9aR1zkl+pse+G45Gy3?tvNkPb8 93 | jod6B3$mr9PuMFxbEpF9H_GPFoRvD6p=Aj9K18$lS@fXrpXco 94 | O9M*hm^*cY^s4rbt8X_IZ_zZ=2*1Vnr^RUA+SnZ$PHN4 95 | 2h_?^juoP2hRv#4uv9@?lLE35zNsnTu3oM4UL~=8W1_*81* 96 | zKdV5gBd8rs^&2V=rrpP7z=ArIGyWNNv96jeXXnLu66H71V&br 97 | 21u-k5Fi=81~e#=6L$S&5k*584M 98 | 1zX8zIKirVCksVY_2snNUmbA4jhTkJYIDmTAmh=XGpMAF 99 | z55PHmjn4XlII7Onxg+bVlndW3l8u+AJdeFJRy6wRftC6L -------------------------------------------------------------------------------- /seminar/lesson5/src/main/scala/seminar/Practice.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import seminar.Practice._ 4 | 5 | import java.io.Closeable 6 | import java.util.UUID 7 | import scala.concurrent._ 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.concurrent.duration._ 10 | import scala.io.{BufferedSource, Source} 11 | import scala.util.{Random, Try} 12 | 13 | object Main extends App { 14 | 15 | 16 | 17 | } 18 | 19 | object Practice { 20 | type Result = Int 21 | type Id = UUID 22 | 23 | private val defaultFileName = "in.txt" 24 | private val defaultDelay = 5.seconds 25 | 26 | private def getSource: BufferedSource = 27 | Source.fromResource(defaultFileName) 28 | 29 | private def read(in: BufferedSource): Future[Iterator[String]] = 30 | Future(blocking(in.getLines())) 31 | 32 | def asyncWithResource[R <: Closeable, A](resource: R)(f: R => Future[A]): Future[A] = 33 | f(resource).andThen { case _ => resource.close() } 34 | 35 | def awaitAndPrint[A](resultF: Future[A]): Unit = 36 | Await.result(resultF, defaultDelay) match { 37 | case seq: Seq[_] => seq.foreach(println) 38 | case simple => println(simple) 39 | } 40 | 41 | 42 | // Кол-во строк в файле 43 | def countFileLinesF: Future[Int] = { 44 | ??? 45 | } 46 | 47 | 48 | // Для каждой строки: строка и её длина 49 | def linesWithLengthF: Future[Seq[(String, Int)]] = { 50 | ??? 51 | } 52 | 53 | 54 | // Сумма длин всех строк 55 | def totalLengthF: Future[Int] = { 56 | ??? 57 | } 58 | 59 | 60 | // Разделить все строки на группы 61 | def splitToBucketsF: Future[Seq[Bucket]] = { 62 | ??? 63 | } 64 | 65 | 66 | // Собрать статистику по файлу 67 | def calculateStatsF: Future[Stats] = { 68 | ??? 69 | } 70 | 71 | 72 | // Вернуть самую быструю Future из двух 73 | def shortestFuture[A](first: Future[A], second: Future[A]): Future[A] = { 74 | ??? 75 | } 76 | 77 | 78 | // Вернуть самую долгую Future из двух 79 | def longestFuture[A](first: Future[A], second: Future[A]): Future[A] = { 80 | ??? 81 | } 82 | 83 | val largeList: List[Id] = (1 to 100).map(_ => UUID.randomUUID()).toList 84 | 85 | def query(id: Id): Future[Result] = Future { 86 | println("Running very long func") 87 | Thread.sleep(5000) 88 | new Random().nextInt() 89 | } 90 | 91 | 92 | // Применить функцию query ко всем элементам коллекции 93 | def seqSolve(ids: List[Id])(f: Id => Future[Result]): Future[List[Result]] = { 94 | ??? 95 | } 96 | 97 | 98 | // Заменить реализацию seqSolve на разбивку по батчам 99 | def batch(ids: Seq[Id], batchSize: Int)(f: Id => Future[Result]): Future[Seq[Result]] = { 100 | ??? 101 | } 102 | 103 | 104 | // Кол-во строк короче maxLength 105 | def countShorterThanF(maxLength: Int): Future[Int] = { 106 | ??? 107 | } 108 | 109 | 110 | // Напечатать строку с задержкой 111 | def printWithDelayF(s: String, delay: FiniteDuration): Future[Unit] = { 112 | ??? 113 | } 114 | 115 | 116 | // Sleep sort 117 | // https://www.quora.com/What-is-sleep-sort 118 | def sleepSortF: Future[Unit] = { 119 | ??? 120 | } 121 | 122 | } 123 | 124 | case class Bucket(linesCount: Int, 125 | minLineLength: Int, 126 | maxLineLength: Int) 127 | 128 | object Bucket { 129 | val DefaultBucketSize = 10 130 | } 131 | 132 | case class Stats(linesCount: Int, 133 | minLineLength: Int, 134 | averageLineLength: Int, 135 | maxLineLength: Int, 136 | buckets: Seq[Bucket]) { 137 | 138 | override def toString = 139 | s"""Total line count: $linesCount 140 | | min: $minLineLength 141 | | avg: $averageLineLength 142 | | max: $maxLineLength 143 | | 144 | | buckets: 145 | |${ 146 | buckets.map { bucket => 147 | s" - ${bucket.minLineLength}-${bucket.maxLineLength}: ${bucket.linesCount}" 148 | }.mkString("\n") 149 | }""".stripMargin 150 | } 151 | -------------------------------------------------------------------------------- /seminar/lesson5/src/test/scala/seminar/PracticeTest.scala: -------------------------------------------------------------------------------- 1 | package seminar 2 | 3 | import org.scalatest.flatspec.AsyncFlatSpec 4 | import org.scalatest.matchers.must.Matchers 5 | import seminar.Practice._ 6 | 7 | class PracticeTest extends AsyncFlatSpec with Matchers { 8 | 9 | it should "count file lines" in { 10 | countFileLinesF.map(_ mustBe 99) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /seminar/lesson6/.gitignore: -------------------------------------------------------------------------------- 1 | /project/project/ 2 | /project/target/ 3 | /target/ 4 | /.idea/ 5 | /.bsp/ 6 | -------------------------------------------------------------------------------- /seminar/lesson6/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Семинар 6: Cats и Circe. Практика по извлечению данных JSON/HTML и валидации 3 | 4 |
5 | 6 | ## ЧАСТЬ 1: Парсинг Json в Circe 7 | На вход подаётся строка с json. 8 | Пробуем разные варианты извлечения данных, пробуем писать кодеки. 9 | 10 | #### ЗАДАНИЕ 1: 11 | Парсинг, авто-кодеки. 12 | 13 | #### ЗАДАНИЕ 2: 14 | Пишем кодеки для кейс-класса: semi-automatic. 15 | С переименовыванием поля. 16 | 17 | #### ЗАДАНИЕ 3: 18 | Пишем кодеки для кейс-класса: custom. 19 | Отсутствующие поля или null должны превращаться в None. 20 | 21 | #### ЗАДАНИЕ 4: 22 | Добавляем валидации. 23 | Пустые строки должны игнорироваться. 24 | Поля, которые не опциональные, должны быть не пустые. 25 | 26 | #### ЗАДАНИЕ 5: 27 | Пишем кодеки для sealed trait 28 | 29 | 30 |
31 | 32 | ## ЧАСТЬ 2: Валидации 33 | Реализуем проверки введённых пользователем полей в web-форме. 34 | Пытаемся использовать синтаксис из cats. 35 | 36 | #### ЗАДАНИЕ 1 37 | Делаем проверки всех необходимых полей и возвращает Boolean. 38 | 39 | #### ЗАДАНИЕ 2 40 | Проверка должна возвращать содержательную ошибку. 41 | 42 | #### ЗАДАНИЕ 3 43 | Должны возвращаться все найденные ошибки, а не только первая 44 | 45 | #### ЗАДАНИЕ 4 46 | Проверка должна возвращать результат или ошибки валидации 47 | 48 | #### ЗАДАНИЕ 5 49 | Заменяем Either на Validated. 50 | Разрешено переиспользовать только функции из задания 1 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /seminar/lesson6/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "2.13.7" 2 | ThisBuild / version := "0.1.0-SNAPSHOT" 3 | ThisBuild / organization := "com.lesson" 4 | ThisBuild / name := "six" 5 | 6 | libraryDependencies ++= Seq( 7 | "io.circe" %% "circe-core", 8 | "io.circe" %% "circe-generic", 9 | "io.circe" %% "circe-parser" 10 | ).map(_ % "0.14.1") 11 | 12 | libraryDependencies ++= Seq( 13 | "org.specs2" %% "specs2-core" % "4.14.1" % Test, 14 | "org.typelevel" %% "cats-core" % "2.7.0", 15 | ) 16 | -------------------------------------------------------------------------------- /seminar/lesson6/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/Part1.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six 2 | 3 | import com.lesson.six.models._ 4 | import io.circe._ 5 | import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} 6 | import io.circe.syntax._ 7 | import cats.syntax.apply._ 8 | 9 | object Part1 { 10 | 11 | //////////////////////////////////////////////////////////// 12 | // ЗАДАНИЕ 1: 13 | // Парсинг, авто-кодеки 14 | //////////////////////////////////////////////////////////// 15 | def parse(input: String): Json = ??? 16 | 17 | def encoderAuto: Encoder[RegistrationData] = deriveEncoder 18 | def decoderAuto: Decoder[RegistrationData] = deriveDecoder 19 | def codecAuto: Codec[RegistrationData] = ??? 20 | 21 | def decode(input: String) 22 | (implicit decoder: Decoder[RegistrationData]): Decoder.Result[RegistrationData] = ??? 23 | 24 | def decodeAcc(input: String) 25 | (implicit decoder: Decoder[RegistrationData]): Decoder.AccumulatingResult[RegistrationData] = ??? 26 | 27 | def encode(data: RegistrationData) 28 | (implicit encoder: Encoder[RegistrationData]): Json = ??? 29 | 30 | //////////////////////////////////////////////////////////// 31 | // ЗАДАНИЕ 2: 32 | // Пишем кодеки для кейс-класса: semi-automatic. 33 | // Переименовываем поле login в name 34 | //////////////////////////////////////////////////////////// 35 | def decoderWithRenaming: Decoder[RegistrationData] = ??? 36 | def encoderWithRenaming: Encoder[RegistrationData] = ??? 37 | 38 | //////////////////////////////////////////////////////////// 39 | // ЗАДАНИЕ 3: 40 | // Пишем кодеки для кейс-класса: custom. 41 | // Отсутствующие поля или null должны превращаться в None 42 | //////////////////////////////////////////////////////////// 43 | def decoderWithEmptyField: Decoder[RegistrationData] = ??? 44 | 45 | def encoderWithEmptyField: Encoder[RegistrationData] = ??? 46 | 47 | 48 | //////////////////////////////////////////////////////////// 49 | // ЗАДАНИЕ 4: 50 | // Добавляем валидации: пустые строки должны превращаться в null 51 | // Логин и пароль - обязательно не пустые 52 | //////////////////////////////////////////////////////////// 53 | def decoderWithValidation: Decoder[RegistrationData] = ??? 54 | 55 | def encoderWithValidation: Encoder[RegistrationData] = ??? 56 | 57 | //////////////////////////////////////////////////////////// 58 | // ЗАДАНИЕ 5: 59 | // Пишем кодеки для простого ENUM (sealed trait) 60 | //////////////////////////////////////////////////////////// 61 | def encoderField: Encoder[Field] = ??? 62 | def decoderField: Decoder[Field] = ??? 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/Part2.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six 2 | 3 | import cats.data.{NonEmptyList, Validated, ValidatedNel} 4 | import com.lesson.six.models.ValidationError._ 5 | import com.lesson.six.models._ 6 | import cats.implicits._ 7 | 8 | import scala.util.{Failure, Success, Try} 9 | 10 | class Part2(webForm: InputWebForm) { 11 | import webForm._ 12 | 13 | def toRegistrationDataUnsafe: RegistrationData = 14 | RegistrationData( 15 | login = login, 16 | password = password, 17 | firstName = Option.when(firstName.nonEmpty)(firstName), 18 | lastName = Option.when(lastName.nonEmpty)(lastName), 19 | age = Option.when(age.nonEmpty)(age.toInt) 20 | ) 21 | 22 | //////////////////////////////////////////////////////////// 23 | // ЗАДАНИЕ 1: 24 | // Делаем проверки всех необходимых полей и возвращает Boolean. 25 | //////////////////////////////////////////////////////////// 26 | 27 | // Имя пользователя должно иметь длину от 5 до 10 символов 28 | private def isCorrectFirstNameLength: Boolean = ??? 29 | 30 | // Фамилия пользователя должна иметь длину от 5 до 10 символов 31 | private def isCorrectLastNameLength: Boolean = ??? 32 | 33 | // Возраст должен быть в диапазоне от 18 до 60 34 | private def isCorrectAge: Boolean = ??? 35 | 36 | // Логин должен быть заполнен 37 | private def loginExists: Boolean = ??? 38 | // Логин должен иметь длину от 5 до 10 символов 39 | private def isCorrectLoginLength: Boolean = ??? 40 | // Все проверки логина вместе: 41 | private def isCorrectLogin: Boolean = ??? 42 | 43 | // Пароль должен быть заполнен 44 | private def passwordExists: Boolean = ??? 45 | // Пароль должен содержать хотя бы одну цифру 46 | private def passwordContainsNumber: Boolean = ??? 47 | // Пароль должен содержать хотя бы одну букву в верхнем регистре 48 | private def passwordContainsUppercase: Boolean = ??? 49 | // Пароль должен содержать хотя бы одну букву в нижнем регистре 50 | private def passwordContainsLowercase: Boolean = ??? 51 | // Все проверки пароля вместе: 52 | private def isCorrectPassword: Boolean = ??? 53 | 54 | // Собираем все проверки вместе: 55 | def isCorrectFields: Boolean = ??? 56 | 57 | 58 | 59 | //////////////////////////////////////////////////////////// 60 | // Задание 2: 61 | // Проверка должна возвращать содержательную ошибку 62 | //////////////////////////////////////////////////////////// 63 | def findException: Option[ValidationError] = ??? 64 | 65 | //////////////////////////////////////////////////////////// 66 | // Задание 3: 67 | // Должны возвращаться все найденные ошибки, а не только первая 68 | //////////////////////////////////////////////////////////// 69 | def getAllExceptions: List[ValidationError] = ??? 70 | 71 | def findAllExceptions: Option[NonEmptyList[ValidationError]] = ??? 72 | 73 | //////////////////////////////////////////////////////////// 74 | // Задание 4: 75 | // Проверка должна возвращать результат или ошибки валидации 76 | //////////////////////////////////////////////////////////// 77 | 78 | def getResultOrException: Either[ValidationError, RegistrationData] = ??? 79 | 80 | def getResultOrExceptionsUnsafe: Either[List[ValidationError], RegistrationData] = ??? 81 | 82 | def getResultOrExceptions: Either[NonEmptyList[ValidationError], RegistrationData] = ??? 83 | 84 | //////////////////////////////////////////////////////////// 85 | // Задание 5: 86 | // Заменяем Either на Validated 87 | //////////////////////////////////////////////////////////// 88 | 89 | def validate: Validated[NonEmptyList[ValidationError], RegistrationData] = ??? 90 | 91 | 92 | type ValidationResult[A] = ValidatedNel[ValidationError, A] 93 | 94 | // А теперь тоже самое, но без переиспользований: разрешены только функции из задания 1 95 | def validateResult: ValidationResult[RegistrationData] = ??? 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/models/Field.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six.models 2 | 3 | sealed trait Field { def name: String } 4 | 5 | object Field { 6 | 7 | case object Login extends Field { val name = "логин" } 8 | case object Password extends Field { val name = "пароль" } 9 | 10 | } -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/models/InputWebForm.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six.models 2 | 3 | case class InputWebForm(login: String, 4 | password: String, 5 | firstName: String, 6 | lastName: String, 7 | age: String) 8 | -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/models/RegistrationData.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six.models 2 | 3 | case class RegistrationData(login: String, 4 | password: String, 5 | firstName: Option[String], 6 | lastName: Option[String], 7 | age: Option[Int]) 8 | -------------------------------------------------------------------------------- /seminar/lesson6/src/main/scala/com/lesson/six/models/ValidationError.scala: -------------------------------------------------------------------------------- 1 | package com.lesson.six.models 2 | 3 | sealed trait ValidationError { def err: String } 4 | 5 | object ValidationError { 6 | 7 | case class FieldNotExist(field: Field) extends ValidationError { val err = s"Поле `${field.name}` не заполнено" } 8 | 9 | case object IncorrectLoginLength extends ValidationError { val err = "Логин должен иметь длину от 5 до 10 символов" } 10 | case object IncorrectFirstNameLength extends ValidationError { val err = "Имя пользователя должно иметь длину от 5 до 10 символов" } 11 | case object IncorrectLastNameLength extends ValidationError { val err = "Фамилия пользователя должна иметь длину от 5 до 10 символов" } 12 | case object IncorrectAge extends ValidationError { val err = "Возраст должен быть в диапазоне от 18 до 60" } 13 | 14 | sealed trait IncorrectPassword extends ValidationError 15 | object IncorrectPassword { 16 | case object NumberNotExist extends IncorrectPassword { val err = "Пароль должен содержать хотя бы одну цифру" } 17 | case object UppercaseLetterNotExist extends IncorrectPassword { val err = "Пароль должен содержать хотя бы одну букву в верхнем регистре" } 18 | case object LowercaseLetterNotExist extends IncorrectPassword { val err = "Пароль должен содержать хотя бы одну букву в нижнем регистре" } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /seminar/lesson7/.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | /.idea/ 3 | /project/target/ 4 | /target/ 5 | -------------------------------------------------------------------------------- /seminar/lesson7/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 2.7.5 2 | 3 | maxColumn = 120 4 | continuationIndent.defnSite = 2 5 | continuationIndent.ctorSite = 4 6 | continuationIndent.extendSite = 4 7 | assumeStandardLibraryStripMargin = true 8 | danglingParentheses = true 9 | align = more 10 | rewrite.rules = [SortImports, RedundantBraces, RedundantParens, SortModifiers] -------------------------------------------------------------------------------- /seminar/lesson7/build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "0.1.0-SNAPSHOT" 2 | 3 | ThisBuild / scalaVersion := "2.13.8" 4 | 5 | lazy val root = (project in file(".")) 6 | .settings( 7 | name := "scala-courses-seminar8" 8 | ) 9 | 10 | val AkkaVersion = "2.6.8" 11 | val AkkaHttpVersion = "10.2.9" 12 | val CirceVersion = "0.14.1" 13 | 14 | libraryDependencies ++= Seq( 15 | "com.typesafe.akka" %% "akka-stream" % AkkaVersion, 16 | "com.typesafe.akka" %% "akka-actor" % AkkaVersion, 17 | "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, 18 | "com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion, 19 | "com.typesafe.akka" %% "akka-http-testkit" % AkkaHttpVersion % Test, 20 | "com.typesafe.akka" %% "akka-testkit" % AkkaVersion % Test, 21 | "org.scalatest" %% "scalatest" % "3.2.11", 22 | "de.heikoseeberger" %% "akka-http-circe" % "1.39.2", 23 | "io.circe" %% "circe-core" % CirceVersion, 24 | "io.circe" %% "circe-generic" % CirceVersion, 25 | "io.circe" %% "circe-parser" % CirceVersion 26 | ) 27 | -------------------------------------------------------------------------------- /seminar/lesson7/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson7/project/project/target/config-classes/$09ca5039275f01d49b45$.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson7/project/project/target/config-classes/$09ca5039275f01d49b45$.class -------------------------------------------------------------------------------- /seminar/lesson7/project/project/target/config-classes/$09ca5039275f01d49b45.cache: -------------------------------------------------------------------------------- 1 | sbt.internal.DslEntry -------------------------------------------------------------------------------- /seminar/lesson7/project/project/target/config-classes/$09ca5039275f01d49b45.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/seminar/lesson7/project/project/target/config-classes/$09ca5039275f01d49b45.class -------------------------------------------------------------------------------- /seminar/lesson7/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | import akka.actor.{ActorSystem, Props} 2 | import akka.http.scaladsl.Http 3 | import akka.http.scaladsl.server.Route 4 | 5 | import scala.io.StdIn 6 | import scala.util.{Failure, Success} 7 | 8 | object Main extends App { 9 | implicit val system = ActorSystem() 10 | import system.dispatcher 11 | 12 | val userActor = system.actorOf(Props[UserActor]) 13 | val userService = new UserService(userActor) 14 | val restApi = new RestAPI(userService) 15 | 16 | val route: Route = restApi.routes 17 | 18 | val localhost = "127.0.0.1" 19 | Http().newServerAt("127.0.0.1", 8000).bind(route) 20 | .map(_ => println(s"Server bound to $localhost")) 21 | .onComplete { 22 | case Failure(exception) => 23 | println(s"Unexpected error while binding server: ${exception.getMessage}") 24 | system.terminate() 25 | case Success(_) => () 26 | } 27 | 28 | StdIn.readLine("Press ENTER to stop\n") 29 | system.terminate() 30 | } 31 | -------------------------------------------------------------------------------- /seminar/lesson7/src/main/scala/RestAPI.scala: -------------------------------------------------------------------------------- 1 | import akka.event.Logging 2 | import akka.http.scaladsl.model.StatusCodes 3 | import akka.http.scaladsl.server.Directives._ 4 | import akka.http.scaladsl.server.directives.DebuggingDirectives 5 | import akka.http.scaladsl.server._ 6 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 7 | import io.circe.generic.auto._ 8 | import io.circe.{Decoder, Encoder} 9 | 10 | import scala.concurrent.ExecutionContext 11 | 12 | class RestAPI(userService: UserService)(implicit ec: ExecutionContext) extends FailFastCirceSupport { 13 | 14 | implicit def encoderGenderField: Encoder[Gender] = Encoder.encodeString.contramap(_.name) 15 | implicit def decoderGenderField: Decoder[Gender] = Decoder.decodeString.map { 16 | case Male.name => Male 17 | case Female.name => Female 18 | } 19 | 20 | /* 21 | Задание 1: 22 | Написать роут для добавления пользователя 23 | POST /users/create 24 | 25 | Content-Type: application/json 26 | Пример джейсона: 27 | 28 | { 29 | "name":"John", 30 | "sex": "male", 31 | "age": 27 32 | } 33 | */ 34 | def createUserRoute: Route = ??? 35 | 36 | /* 37 | Задание 2: 38 | Написать роут для получения всех пользователей 39 | GET /users 40 | */ 41 | def getAllUsersRoute: Route = ??? 42 | 43 | 44 | 45 | /* 46 | Задание 3: 47 | Написать роут получения пользователя по id через параметр 48 | Если пользователя в базе нет, вернуть 404 с кастомным текстом 49 | GET /users/get?id={id} 50 | */ 51 | def getUserRoute: Route = ??? 52 | 53 | /* 54 | Задание 4: 55 | Написать роут обновления пользователя по id. Id должен быть частью роута 56 | Если пользователя в базе нет, вернуть 404 с кастомным текстом 57 | PUT /users/update/id + json 58 | */ 59 | def updateUserRoute: Route = ??? 60 | 61 | /* 62 | Задание 5: 63 | Написать роут удаления пользователя по id через параметр и указать его в роуте 64 | Если пользователя в базе нет, вернуть 404 с кастомным текстом 65 | DELETE /users/remove/id 66 | */ 67 | def deleteUserRoute: Route = ??? 68 | 69 | /* 70 | Задание 6: 71 | Объяснить что делает данный роутинг и исправить ошибку 72 | */ 73 | 74 | def getOrPutPath = 75 | pathPrefix("test") { 76 | pathEndOrSingleSlash { 77 | get { 78 | complete(StatusCodes.OK) 79 | } 80 | post { 81 | complete(StatusCodes.Forbidden) 82 | } 83 | } 84 | } 85 | 86 | /* 87 | Задание 7: 88 | Написать RejectionHandler'ы: 89 | - верхнеуровневый, который возвращает BadRequest с кастомным текстом 90 | - нижнеуровневый (навесить на роут /remove), который возвращает Forbidden при попытке 91 | использовать другие REST методы 92 | */ 93 | 94 | def badRequestHandler: RejectionHandler = ??? 95 | 96 | def forbiddenHandler: RejectionHandler = ??? 97 | 98 | /* 99 | (Опционально) Задание 8: 100 | Написать RejectionHandler на MissingQueryParamRejection и MethodRejection 101 | и изменить текст ошибки 102 | */ 103 | 104 | /* 105 | Задание 9: 106 | Завернуть exception из methodWithException и вернуть ServiceUnavailable с текстом из ошибки 107 | */ 108 | 109 | def methodWithExceptionRoute: Route = ??? 110 | 111 | def exceptionHandler: ExceptionHandler = ??? 112 | 113 | /* 114 | Задание 10: 115 | Написать директиву, которая логирует все реквесты и респонсы и подмешать ее ко всем роутам 116 | Подсказка: Использовать инструменты из akka.http.scaladsl.server.directives.DebuggingDirectives 117 | */ 118 | 119 | def logExtractorDirective: Directive0 = ??? 120 | 121 | def routes: Route = 122 | pathPrefix("users") { 123 | createUserRoute ~ getAllUsersRoute ~ getUserRoute ~ updateUserRoute ~ deleteUserRoute 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /seminar/lesson7/src/main/scala/User.scala: -------------------------------------------------------------------------------- 1 | case class User(name: String, sex: Gender, age: Int) 2 | 3 | sealed trait Gender { 4 | def name: String 5 | } 6 | case object Male extends Gender { 7 | val name: String = "male" 8 | } 9 | case object Female extends Gender { 10 | val name: String = "female" 11 | } 12 | -------------------------------------------------------------------------------- /seminar/lesson7/src/main/scala/UserActor.scala: -------------------------------------------------------------------------------- 1 | import akka.actor.Actor 2 | import scala.collection.mutable.{Map => MutableMap} 3 | 4 | import UserActor._ 5 | 6 | class UserActor extends Actor { 7 | val users: MutableMap[Int, User] = MutableMap.empty 8 | var currentId: Int = 0 9 | 10 | private def usersOp(id: Int)(f: => Boolean) = { 11 | if (users.contains(id)) { 12 | f 13 | true 14 | } 15 | else false 16 | } 17 | 18 | override def receive: Receive = { 19 | case AddUser(user) => 20 | users += currentId -> user 21 | sender() ! currentId 22 | currentId += 1 23 | 24 | case GetUser(id) => 25 | sender() ! users.get(id) 26 | 27 | case GetAllUsers => 28 | sender() ! users.values.toList 29 | 30 | case UpdateUser(id, user) => 31 | sender() ! usersOp(id)(users.put(id, user).isDefined) 32 | 33 | case RemoveUser(id) => 34 | sender() ! usersOp(id)(users.remove(id).isDefined) 35 | 36 | } 37 | } 38 | 39 | object UserActor { 40 | case class AddUser(user: User) 41 | case class GetUser(id: Int) 42 | case object GetAllUsers 43 | case class UpdateUser(id: Int, user: User) 44 | case class RemoveUser(id: Int) 45 | } 46 | -------------------------------------------------------------------------------- /seminar/lesson7/src/main/scala/UserService.scala: -------------------------------------------------------------------------------- 1 | import UserActor._ 2 | import akka.actor.ActorRef 3 | import akka.pattern.ask 4 | import akka.util.Timeout 5 | 6 | import scala.concurrent.duration._ 7 | import scala.concurrent.Future 8 | import scala.language.postfixOps 9 | 10 | class UserService(userActor: ActorRef) { 11 | implicit val timeout: Timeout = Timeout(2 seconds) 12 | 13 | def addUser(user: User): Future[Int] = (userActor ? AddUser(user)).mapTo[Int] 14 | def getUser(id: Int): Future[Option[User]] = (userActor ? GetUser(id)).mapTo[Option[User]] 15 | def getAllUsers: Future[List[User]] = (userActor ? GetAllUsers).mapTo[List[User]] 16 | def updateUser(id: Int, user: User): Future[Boolean] = (userActor ? UpdateUser(id, user)).mapTo[Boolean] 17 | def removeUser(id: Int): Future[Boolean] = (userActor ? RemoveUser(id)).mapTo[Boolean] 18 | def methodWithException: String = throw new RuntimeException("Сервер перегружен") 19 | } 20 | -------------------------------------------------------------------------------- /seminar/lesson7/src/test/scala/RestApiSpec.scala: -------------------------------------------------------------------------------- 1 | import akka.actor.Props 2 | import akka.http.scaladsl.model.StatusCodes 3 | import akka.http.scaladsl.testkit.ScalatestRouteTest 4 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 5 | import io.circe.generic.auto._ 6 | import io.circe.{Decoder, Encoder} 7 | import org.scalatest.matchers.must._ 8 | import org.scalatest.wordspec.AnyWordSpec 9 | 10 | class RestApiSpec extends AnyWordSpec 11 | with Matchers 12 | with ScalatestRouteTest 13 | with FailFastCirceSupport { 14 | 15 | implicit def encoderGenderField: Encoder[Gender] = Encoder.encodeString.contramap(_.name) 16 | implicit def decoderGenderField: Decoder[Gender] = Decoder.decodeString.map { 17 | case Male.name => Male 18 | case Female.name => Female 19 | } 20 | 21 | val userActor = system.actorOf(Props[UserActor]) 22 | val userService = new UserService(userActor) 23 | val routes = (new RestAPI(userService)).routes 24 | val newUser = User("Test", Male, 17) 25 | 26 | 27 | "Rest API" must { 28 | "create user" in { 29 | ??? 30 | } 31 | 32 | "get user" in { 33 | ??? 34 | } 35 | } 36 | // И так далее 37 | } 38 | -------------------------------------------------------------------------------- /seminar/lesson8/.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | .idea/ 3 | target/ 4 | -------------------------------------------------------------------------------- /seminar/lesson8/README.md: -------------------------------------------------------------------------------- 1 | 0) SimpleActorSpec - вспоминаем как пишутся акторы 2 | 1) FutureActorSpec - Future + Stash 3 | 2) SuperActor - Супервизоры 4 | 3) Творческая часть: 5 | обработка сообщений (на вход сообщения. нужно для каждого: сохраняем в бд/ставим штампик, возвращаем) 6 | пока наверное лучше порциями по расписанию. роуты вроде в след лекции. 7 | -------------------------------------------------------------------------------- /seminar/lesson8/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.13.7" 2 | name := "lesson-actor-typed" 3 | 4 | val akkaV = "2.6.14" 5 | val actorDependencies = Seq( 6 | "com.typesafe.akka" %% "akka-actor-typed" % akkaV, 7 | "com.typesafe.akka" %% "akka-stream" % akkaV, 8 | "com.typesafe.akka" %% "akka-slf4j" % akkaV, 9 | "com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaV % Test, 10 | "org.scalatest" %% "scalatest" % "3.0.8" % Test, 11 | "org.mockito" % "mockito-all" % "1.10.19" % Test 12 | ) 13 | 14 | libraryDependencies ++= actorDependencies 15 | 16 | -------------------------------------------------------------------------------- /seminar/lesson8/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /seminar/lesson8/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.13") 2 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/FutureStashActor.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ActorRef, Behavior} 5 | import ru.allebedev.core.FakeDbService 6 | 7 | import scala.util.{Failure, Success, Try} 8 | 9 | object FutureStashActor { 10 | 11 | sealed trait FutureProtocol 12 | 13 | def behavior(service: FakeDbService): Behavior[FutureProtocol] = ??? 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/ScheduleActor.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.typed.{ActorRef, Behavior} 4 | import akka.actor.typed.scaladsl.Behaviors 5 | import ru.allebedev.core.{DataForStamp, FakeDbService} 6 | 7 | import scala.concurrent.duration._ 8 | import scala.util.{Failure, Success} 9 | 10 | object ScheduleActor { 11 | 12 | sealed trait SchProtocol 13 | 14 | def behavior(service: FakeDbService): Behavior[SchProtocol] = ??? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/SimpleActor.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ActorRef, Behavior} 5 | import ru.allebedev.core.FakeDbService 6 | 7 | import scala.util.{Failure, Success} 8 | 9 | object SimpleActor { 10 | 11 | sealed trait SimpleProtocol 12 | 13 | def behavior(list: Seq[String] = Seq.empty): Behavior[SimpleProtocol] = ??? 14 | 15 | } 16 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/SuperActor.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} 5 | import ru.allebedev.core.{DbFatalExeption, FakeDbService} 6 | 7 | import scala.util.{Failure, Success, Try} 8 | 9 | object SuperActor { 10 | 11 | sealed trait SuperProtocol 12 | 13 | def behavior(service: FakeDbService): Behavior[SuperProtocol] = ??? 14 | 15 | } 16 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/core/DataForStamp.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.core 2 | 3 | import java.time.Instant 4 | 5 | case class DataForStamp(text:String, stamp: Option[Instant] = None) 6 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/core/DbFatalExeption.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.core 2 | 3 | class DbFatalExeption extends Exception 4 | -------------------------------------------------------------------------------- /seminar/lesson8/src/main/scala/ru/allebedev/core/FakeDbService.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev.core 2 | 3 | import java.time.Instant 4 | 5 | import scala.concurrent.Future 6 | 7 | class FakeDbService() { 8 | 9 | def getInitialData: Future[Seq[String]] = { 10 | Thread.sleep(5000) 11 | Future.successful(Seq("first", "second", "third")) 12 | } 13 | 14 | def getData: Future[Seq[String]] = 15 | Future.successful(Seq("first", "second", "third")) 16 | 17 | def getFatal: Future[Seq[String]] = Future.failed(new DbFatalExeption) 18 | 19 | def throwThrowable: Future[Seq[String]] = throw new Throwable("Ops! (0_o)/`````") 20 | 21 | def stamp(list: Seq[DataForStamp]): Future[Seq[DataForStamp]] = 22 | Future.successful(list.map(_.copy(stamp = Some(Instant.now())))) 23 | 24 | } 25 | 26 | object FakeDbService { 27 | 28 | def apply: FakeDbService = new FakeDbService() 29 | 30 | } -------------------------------------------------------------------------------- /seminar/lesson8/src/test/scala/ru/allebedev/FutureStashActorSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.testkit.typed.scaladsl.{ActorTestKit, ActorTestKitBase} 4 | import org.scalatest.{MustMatchers, WordSpecLike} 5 | import ru.allebedev.core.FakeDbService 6 | import ru.allebedev.FutureStashActor._ 7 | import scala.concurrent.duration._ 8 | 9 | class FutureStashActorSpec extends ActorTestKitBase(ActorTestKit(ActorTestKitBase.testNameFromCallStack())) with WordSpecLike with MustMatchers { 10 | 11 | "FutureStashActor" should { 12 | 13 | "initial with future" in { 14 | val actor = testKit.spawn(FutureStashActor.behavior(FakeDbService.apply), "future-test") 15 | val probe = testKit.createTestProbe[Seq[String]]() 16 | actor ! PutData("four") 17 | actor ! GetData(probe.ref) 18 | probe.expectMessage(10.seconds, Seq("four", "first", "second", "third")) 19 | actor ! PutData("five") 20 | actor ! GetData(probe.ref) 21 | probe.expectMessage(Seq("five", "four", "first", "second", "third")) 22 | } 23 | 24 | } 25 | 26 | override protected def afterAll(): Unit = { 27 | testKit.shutdownTestKit() 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /seminar/lesson8/src/test/scala/ru/allebedev/ScheduleActorSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.testkit.typed.scaladsl.{ActorTestKit, ActorTestKitBase} 4 | import org.mockito.Mockito 5 | import org.scalatest.{MustMatchers, WordSpecLike} 6 | import org.scalatestplus.mockito.MockitoSugar 7 | import ru.allebedev.core.{DataForStamp, FakeDbService} 8 | import org.mockito.Matchers.any 9 | import ru.allebedev.ScheduleActor.StampData 10 | 11 | import scala.util.Random 12 | 13 | class ScheduleActorSpec extends ActorTestKitBase(ActorTestKit(ActorTestKitBase.testNameFromCallStack())) 14 | with WordSpecLike 15 | with MockitoSugar 16 | with MustMatchers { 17 | 18 | "ScheduleActor" should { 19 | 20 | "work" in { 21 | val list: Seq[DataForStamp] = Seq.fill(50)(DataForStamp(Random.nextString(10))) 22 | val fakeService = FakeDbService.apply 23 | val spyService = Mockito.spy(fakeService) 24 | val actor = testKit.spawn(ScheduleActor.behavior(spyService), "schedule-test") 25 | actor ! StampData(list) 26 | Thread.sleep(15000) 27 | Mockito.verify(spyService, Mockito.times(1)).stamp(any[Seq[DataForStamp]]) 28 | Thread.sleep(10000) 29 | Mockito.verify(spyService, Mockito.times(2)).stamp(any[Seq[DataForStamp]]) 30 | } 31 | 32 | } 33 | 34 | override protected def afterAll(): Unit = { 35 | testKit.shutdownTestKit() 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /seminar/lesson8/src/test/scala/ru/allebedev/SimpleActorSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.testkit.typed.scaladsl.{ActorTestKit, ActorTestKitBase} 4 | import org.scalatest.{MustMatchers, WordSpecLike} 5 | import ru.allebedev.SimpleActor._ 6 | 7 | class SimpleActorSpec extends ActorTestKitBase(ActorTestKit(ActorTestKitBase.testNameFromCallStack())) with WordSpecLike with MustMatchers { 8 | 9 | "SimpleActor" should { 10 | 11 | "put and get msg" in { 12 | val actor = testKit.spawn(SimpleActor.behavior(), "simple-test") 13 | val probe = testKit.createTestProbe[Seq[String]]() 14 | actor ! PutMsg("first") 15 | actor ! PutMsg("second") 16 | actor ! GetMsg(probe.ref) 17 | probe.expectMessage(Seq("second", "first")) 18 | } 19 | 20 | } 21 | 22 | override protected def afterAll(): Unit = { 23 | testKit.shutdownTestKit() 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /seminar/lesson8/src/test/scala/ru/allebedev/SuperActorSpec.scala: -------------------------------------------------------------------------------- 1 | package ru.allebedev 2 | 3 | import akka.actor.testkit.typed.scaladsl.{ActorTestKit, ActorTestKitBase} 4 | import org.scalatest.{MustMatchers, WordSpecLike} 5 | import ru.allebedev.SuperActor._ 6 | import ru.allebedev.core.FakeDbService 7 | 8 | class SuperActorSpec extends ActorTestKitBase(ActorTestKit(ActorTestKitBase.testNameFromCallStack())) with WordSpecLike with MustMatchers { 9 | 10 | "SuperActor" should { 11 | 12 | "restart and stop" in { 13 | val actor = testKit.spawn(SuperActor.behavior(FakeDbService.apply), "super-test") 14 | val probe = testKit.createTestProbe[Seq[String]]() 15 | 16 | actor ! Refresh 17 | probe.expectNoMessage() 18 | actor ! GetData(probe.ref) 19 | probe.expectMessage(Seq("first", "second", "third")) 20 | 21 | actor ! RefreshWithError 22 | probe.expectNoMessage() 23 | actor ! GetData(probe.ref) 24 | probe.expectMessage(Seq.empty) 25 | 26 | actor ! ThrowError 27 | probe.expectTerminated(actor) 28 | 29 | } 30 | 31 | } 32 | 33 | override protected def afterAll(): Unit = { 34 | testKit.shutdownTestKit() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /seminar/lesson8/version.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / version := "1.0" -------------------------------------------------------------------------------- /seminar/lesson9/.gitignore: -------------------------------------------------------------------------------- 1 | /.bsp/ 2 | .idea/ 3 | target/ 4 | -------------------------------------------------------------------------------- /seminar/lesson9/build.sbt: -------------------------------------------------------------------------------- 1 | name := "actors-patterns" 2 | 3 | version := "0.1" 4 | 5 | scalaVersion := "2.13.8" 6 | 7 | val akkaV = "2.6.19" 8 | val actorDependencies = Seq( 9 | "com.typesafe.akka" %% "akka-actor-typed" % akkaV, 10 | "com.typesafe.akka" %% "akka-slf4j" % akkaV, 11 | "com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaV % Test, 12 | "org.scalatest" %% "scalatest" % "3.0.8" % Test, 13 | "org.mockito" % "mockito-all" % "1.10.19" % Test 14 | ) 15 | 16 | 17 | libraryDependencies ++= actorDependencies 18 | libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" -------------------------------------------------------------------------------- /seminar/lesson9/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.6.2 -------------------------------------------------------------------------------- /seminar/lesson9/src/main/resourses/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loggers = ["akka.event.slf4j.Slf4jLogger"], 3 | loglevel = "DEBUG" 4 | } -------------------------------------------------------------------------------- /seminar/lesson9/src/main/scala/com/ClientActor.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.typed.Behavior 4 | import akka.actor.typed.scaladsl.Behaviors 5 | 6 | import java.util.UUID 7 | 8 | object ClientActor { 9 | sealed trait ClientProtocol 10 | 11 | case class SendRequest(msg: String) extends ClientProtocol 12 | case class SuccessResponse(id: UUID) extends ClientProtocol 13 | case class FailureResponse(id: UUID, failedMsg: String) extends ClientProtocol 14 | 15 | def apply(): Behavior[ClientProtocol] = Behaviors.setup[ClientProtocol] { ctx => 16 | ??? 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /seminar/lesson9/src/main/scala/com/Main.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.typed.{ActorRef, ActorSystem} 4 | import akka.util.Timeout 5 | 6 | import scala.concurrent.duration.DurationInt 7 | 8 | 9 | /** 10 | * Схема взаимодействия 11 | * Client -> RequestController -> Worker 12 | * 13 | * Задание 1: 14 | * 1.1 Создать ActorSystem 15 | * 1.2 Создать акторы, согласно схеме взаимодействия: 16 | * - Создать RequestController 17 | * - Создать Client 18 | * 1.3 - Создать Worker (его parent - RequestController) 19 | * Задание 2: 20 | * - ask pattern - Client ? RequestController ? worker 21 | * Задание 3: 22 | * - router pattern - много worker-ов 23 | */ 24 | 25 | // https://doc.akka.io/docs/akka/current/typed/guide/tutorial_1.html 26 | // https://doc.akka.io/docs/akka/current/typed/guide/tutorial_2.html 27 | object Main extends App { 28 | 29 | // SpawnProtocol 30 | // - https://maxcom.github.io/scala-course-2022/slides/day8.html#/32 31 | // - https://doc.akka.io/docs/akka/current/typed/actor-lifecycle.html#spawnprotocol 32 | 33 | // вариант 1 - root actor 34 | def createWithRootActor() = { 35 | // Первый подвариант - создаем actor использующий SpawnProtocol 36 | 37 | // Второй подвариант - если нужен root-овый Actor уже с протоколом, куда нужно что-то передать 38 | 39 | } 40 | 41 | // вариант 2 - SpawnProtocol - создаём корневой актор с помощью SpawnProtocol, который умеет spawn-нить другие акторы 42 | def createWithSpawnProtocol() = { 43 | implicit val timeout: Timeout = Timeout(3.seconds) 44 | 45 | } 46 | 47 | 48 | def createFromClassic() = { 49 | // вариант 3 - конвертация из classic системы. Используется при миграции с classic на Typed 50 | import akka.actor.typed.scaladsl.adapter.ClassicActorSystemOps 51 | import akka.{actor => classic} 52 | 53 | val systemClassic: classic.ActorSystem = classic.ActorSystem("system") 54 | val systemTyped: ActorSystem[Nothing] = systemClassic.toTyped 55 | 56 | //val requestController: ActorRef[RequestController.RequestResponseProtocol] = systemClassic.spawn[RequestController.RequestResponseProtocol](RequestController(), name = "request-controller") 57 | //val client: ActorRef[ClientActor.ClientProtocol] = systemClassic.spawn[ClientActor.ClientProtocol](ClientActor(requestController), name = "client-1") 58 | 59 | systemTyped 60 | } 61 | 62 | createWithRootActor() 63 | } 64 | -------------------------------------------------------------------------------- /seminar/lesson9/src/main/scala/com/RequestController.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | 4 | import akka.actor.typed.scaladsl.Behaviors 5 | import akka.actor.typed.{ActorRef, Behavior} 6 | 7 | 8 | import java.util.UUID 9 | 10 | 11 | object RequestController { 12 | sealed trait RequestResponseProtocol 13 | 14 | case class SimpleRequest() 15 | case class Request(id: UUID, query: String, replyTo: ActorRef[ClientActor.ClientProtocol]) extends RequestResponseProtocol 16 | case class Response(id: UUID, responseMsg: String) extends RequestResponseProtocol 17 | private case class Finished(rq: Request, responseMsg: String) extends RequestResponseProtocol 18 | private case class Failed(rq: Request, ex: Throwable) extends RequestResponseProtocol 19 | 20 | def apply(): Behavior[RequestResponseProtocol] = Behaviors.setup[RequestResponseProtocol] { ctx => 21 | ??? 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /seminar/lesson9/src/main/scala/com/Worker.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ActorRef, Behavior} 5 | 6 | import java.util.UUID 7 | 8 | object Worker { 9 | sealed trait WorkerProtocol 10 | case class Work(id: UUID, msg: String, replyTo: ActorRef[RequestController.RequestResponseProtocol]) extends WorkerProtocol 11 | case object PrintYourPath extends WorkerProtocol 12 | 13 | def work(): Behavior[WorkerProtocol] = Behaviors.receive[WorkerProtocol] { 14 | case (ctx, PrintYourPath) => 15 | ctx.log.info(s"MyPath is: ${ctx.self.path}") 16 | Behaviors.same[WorkerProtocol] 17 | 18 | case (ctx, Work(id, msg, replyTo)) => 19 | 20 | Thread.sleep(10000) 21 | 22 | ctx.log.info(s"${ctx.self.path}: $msg") 23 | replyTo ! RequestController.Response(id, "processed") 24 | Behaviors.same[WorkerProtocol] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /seminar/lesson9/src/test/scala/com/RequestControllerSpec.scala: -------------------------------------------------------------------------------- 1 | package com 2 | 3 | import akka.actor.testkit.typed.scaladsl.{ActorTestKit, ActorTestKitBase} 4 | import com.{ClientActor, RequestController} 5 | import org.scalatest.{MustMatchers, WordSpecLike} 6 | 7 | import java.util.UUID 8 | import scala.concurrent.duration.DurationInt 9 | 10 | class RequestControllerSpec extends ActorTestKitBase(ActorTestKit(ActorTestKitBase.testNameFromCallStack())) with WordSpecLike with MustMatchers { 11 | val actor = testKit.spawn(RequestController(), "RequestController-test") 12 | val probe = testKit.createTestProbe[ClientActor.ClientProtocol]() 13 | 14 | "RequestController" should { 15 | "send request to worker and return result" in { 16 | val requestUUID: UUID = UUID.randomUUID() 17 | val messageStr = "message #0000" 18 | actor ! RequestController.Request(requestUUID, messageStr, probe.ref) 19 | probe.expectMessage(10.seconds, ClientActor.SuccessResponse(requestUUID)) 20 | 21 | val requestUUID2: UUID = UUID.randomUUID() 22 | val messageStr2 = "message #1111" 23 | actor ! RequestController.Request(requestUUID2, messageStr2, probe.ref) 24 | probe.expectMessage(10.seconds, ClientActor.SuccessResponse(requestUUID2)) 25 | } 26 | } 27 | 28 | override protected def afterAll(): Unit = { 29 | testKit.shutdownTestKit() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /slides/CE-Threads-1-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-1-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-2-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-2-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-3-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-3-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-4-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-4-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-5-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-5-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-6-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-6-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-7-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-7-colored.png -------------------------------------------------------------------------------- /slides/CE-Threads-8-colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/CE-Threads-8-colored.png -------------------------------------------------------------------------------- /slides/Dynamite Effects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Dynamite Effects.png -------------------------------------------------------------------------------- /slides/Paper.Scala.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Paper.Scala.1.png -------------------------------------------------------------------------------- /slides/Paper.Scala.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Paper.Scala.2.png -------------------------------------------------------------------------------- /slides/Paper.Scala.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Paper.Scala.3.png -------------------------------------------------------------------------------- /slides/Paper.Scala.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Paper.Scala.4.png -------------------------------------------------------------------------------- /slides/Paper.Scala.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Paper.Scala.6.png -------------------------------------------------------------------------------- /slides/Thats all cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/Thats all cats.png -------------------------------------------------------------------------------- /slides/catfunctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/catfunctor.png -------------------------------------------------------------------------------- /slides/cats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/cats.png -------------------------------------------------------------------------------- /slides/cats2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/cats2.png -------------------------------------------------------------------------------- /slides/compose_shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/compose_shapes.png -------------------------------------------------------------------------------- /slides/cows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/cows.jpg -------------------------------------------------------------------------------- /slides/css/print/pdf.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This stylesheet is used to print reveal.js 3 | * presentations to PDF. 4 | * 5 | * https://github.com/hakimel/reveal.js#pdf-export 6 | */ 7 | 8 | * { 9 | -webkit-print-color-adjust: exact; 10 | } 11 | 12 | body { 13 | margin: 0 auto !important; 14 | border: 0; 15 | padding: 0; 16 | float: none !important; 17 | overflow: visible; 18 | } 19 | 20 | html { 21 | width: 100%; 22 | height: 100%; 23 | overflow: visible; 24 | } 25 | 26 | /* Remove any elements not needed in print. */ 27 | .nestedarrow, 28 | .reveal .controls, 29 | .reveal .progress, 30 | .reveal .playback, 31 | .reveal.overview, 32 | .fork-reveal, 33 | .share-reveal, 34 | .state-background { 35 | display: none !important; 36 | } 37 | 38 | h1, h2, h3, h4, h5, h6 { 39 | text-shadow: 0 0 0 #000 !important; 40 | } 41 | 42 | .reveal pre code { 43 | overflow: hidden !important; 44 | font-family: Courier, 'Courier New', monospace !important; 45 | } 46 | 47 | ul, ol, div, p { 48 | visibility: visible; 49 | position: static; 50 | width: auto; 51 | height: auto; 52 | display: block; 53 | overflow: visible; 54 | margin: auto; 55 | } 56 | .reveal { 57 | width: auto !important; 58 | height: auto !important; 59 | overflow: hidden !important; 60 | } 61 | .reveal .slides { 62 | position: static; 63 | width: 100% !important; 64 | height: auto !important; 65 | zoom: 1 !important; 66 | 67 | left: auto; 68 | top: auto; 69 | margin: 0 !important; 70 | padding: 0 !important; 71 | 72 | overflow: visible; 73 | display: block; 74 | 75 | -webkit-perspective: none; 76 | -moz-perspective: none; 77 | -ms-perspective: none; 78 | perspective: none; 79 | 80 | -webkit-perspective-origin: 50% 50%; /* there isn't a none/auto value but 50-50 is the default */ 81 | -moz-perspective-origin: 50% 50%; 82 | -ms-perspective-origin: 50% 50%; 83 | perspective-origin: 50% 50%; 84 | } 85 | 86 | .reveal .slides .pdf-page { 87 | position: relative; 88 | overflow: hidden; 89 | z-index: 1; 90 | 91 | page-break-after: always; 92 | } 93 | 94 | .reveal .slides section { 95 | visibility: visible !important; 96 | display: block !important; 97 | position: absolute !important; 98 | 99 | margin: 0 !important; 100 | padding: 0 !important; 101 | box-sizing: border-box !important; 102 | min-height: 1px; 103 | 104 | opacity: 1 !important; 105 | 106 | -webkit-transform-style: flat !important; 107 | -moz-transform-style: flat !important; 108 | -ms-transform-style: flat !important; 109 | transform-style: flat !important; 110 | 111 | -webkit-transform: none !important; 112 | -moz-transform: none !important; 113 | -ms-transform: none !important; 114 | transform: none !important; 115 | } 116 | 117 | .reveal section.stack { 118 | position: relative !important; 119 | margin: 0 !important; 120 | padding: 0 !important; 121 | page-break-after: avoid !important; 122 | height: auto !important; 123 | min-height: auto !important; 124 | } 125 | 126 | .reveal img { 127 | box-shadow: none; 128 | } 129 | 130 | .reveal .roll { 131 | overflow: visible; 132 | line-height: 1em; 133 | } 134 | 135 | /* Slide backgrounds are placed inside of their slide when exporting to PDF */ 136 | .reveal .slide-background { 137 | display: block !important; 138 | position: absolute; 139 | top: 0; 140 | left: 0; 141 | width: 100%; 142 | height: 100%; 143 | z-index: auto !important; 144 | } 145 | 146 | /* Display slide speaker notes when 'showNotes' is enabled */ 147 | .reveal.show-notes { 148 | max-width: none; 149 | max-height: none; 150 | } 151 | .reveal .speaker-notes-pdf { 152 | display: block; 153 | width: 100%; 154 | height: auto; 155 | max-height: none; 156 | top: auto; 157 | right: auto; 158 | bottom: auto; 159 | left: auto; 160 | z-index: 100; 161 | } 162 | 163 | /* Layout option which makes notes appear on a separate page */ 164 | .reveal .speaker-notes-pdf[data-layout="separate-page"] { 165 | position: relative; 166 | color: inherit; 167 | background-color: transparent; 168 | padding: 20px; 169 | page-break-after: always; 170 | border: 0; 171 | } 172 | 173 | /* Display slide numbers when 'slideNumber' is enabled */ 174 | .reveal .slide-number-pdf { 175 | display: block; 176 | position: absolute; 177 | font-size: 14px; 178 | } 179 | -------------------------------------------------------------------------------- /slides/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment including the Grunt dependencies installed before proceeding: https://github.com/hakimel/reveal.js#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/Gruntfile.js)) when you run `grunt css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /slides/css/theme/source/beige.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Beige theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(../../lib/font/league-gothic/league-gothic.css); 17 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainColor: #333; 22 | $headingColor: #333; 23 | $headingTextShadow: none; 24 | $backgroundColor: #f7f3de; 25 | $linkColor: #8b743d; 26 | $linkColorHover: lighten( $linkColor, 20% ); 27 | $selectionBackgroundColor: rgba(79, 64, 28, 0.99); 28 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 29 | 30 | // Background generator 31 | @mixin bodyBackground() { 32 | @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); 33 | } 34 | 35 | 36 | 37 | // Theme template ------------------------------ 38 | @import "../template/theme"; 39 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/source/black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. This is the opposite of the 'white' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #222; 20 | 21 | $mainColor: #fff; 22 | $headingColor: #fff; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #42affa; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-light-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #222; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/source/blood.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Blood theme for reveal.js 3 | * Author: Walther http://github.com/Walther 4 | * 5 | * Designed to be used with highlight.js theme 6 | * "monokai_sublime.css" available from 7 | * https://github.com/isagalaev/highlight.js/ 8 | * 9 | * For other themes, change $codeBackground accordingly. 10 | * 11 | */ 12 | 13 | // Default mixins and settings ----------------- 14 | @import "../template/mixins"; 15 | @import "../template/settings"; 16 | // --------------------------------------------- 17 | 18 | // Include theme-specific fonts 19 | 20 | @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic); 21 | 22 | // Colors used in the theme 23 | $blood: #a23; 24 | $coal: #222; 25 | $codeBackground: #23241f; 26 | 27 | $backgroundColor: $coal; 28 | 29 | // Main text 30 | $mainFont: Ubuntu, 'sans-serif'; 31 | $mainColor: #eee; 32 | 33 | // Headings 34 | $headingFont: Ubuntu, 'sans-serif'; 35 | $headingTextShadow: 2px 2px 2px $coal; 36 | 37 | // h1 shadow, borrowed humbly from 38 | // (c) Default theme by Hakim El Hattab 39 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 40 | 41 | // Links 42 | $linkColor: $blood; 43 | $linkColorHover: lighten( $linkColor, 20% ); 44 | 45 | // Text selection 46 | $selectionBackgroundColor: $blood; 47 | $selectionColor: #fff; 48 | 49 | 50 | // Theme template ------------------------------ 51 | @import "../template/theme"; 52 | // --------------------------------------------- 53 | 54 | // some overrides after theme template import 55 | 56 | .reveal p { 57 | font-weight: 300; 58 | text-shadow: 1px 1px $coal; 59 | } 60 | 61 | .reveal h1, 62 | .reveal h2, 63 | .reveal h3, 64 | .reveal h4, 65 | .reveal h5, 66 | .reveal h6 { 67 | font-weight: 700; 68 | } 69 | 70 | .reveal p code { 71 | background-color: $codeBackground; 72 | display: inline-block; 73 | border-radius: 7px; 74 | } 75 | 76 | .reveal small code { 77 | vertical-align: baseline; 78 | } -------------------------------------------------------------------------------- /slides/css/theme/source/league.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * League theme for reveal.js. 3 | * 4 | * This was the default theme pre-3.0.0. 5 | * 6 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(../../lib/font/league-gothic/league-gothic.css); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | // Override theme settings (see ../template/settings.scss) 22 | $headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); 23 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 24 | 25 | // Background generator 26 | @mixin bodyBackground() { 27 | @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); 28 | } 29 | 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/source/moon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | 54 | 55 | // Theme template ------------------------------ 56 | @import "../template/theme"; 57 | // --------------------------------------------- 58 | -------------------------------------------------------------------------------- /slides/css/theme/source/night.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700); 16 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); 17 | 18 | 19 | // Override theme settings (see ../template/settings.scss) 20 | $backgroundColor: #111; 21 | 22 | $mainFont: 'Open Sans', sans-serif; 23 | $linkColor: #e7ad52; 24 | $linkColorHover: lighten( $linkColor, 20% ); 25 | $headingFont: 'Montserrat', Impact, sans-serif; 26 | $headingTextShadow: none; 27 | $headingLetterSpacing: -0.03em; 28 | $headingTextTransform: none; 29 | $selectionBackgroundColor: #e7ad52; 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/source/serif.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is brown. 4 | * 5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. 6 | */ 7 | 8 | 9 | // Default mixins and settings ----------------- 10 | @import "../template/mixins"; 11 | @import "../template/settings"; 12 | // --------------------------------------------- 13 | 14 | 15 | 16 | // Override theme settings (see ../template/settings.scss) 17 | $mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 18 | $mainColor: #000; 19 | $headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 20 | $headingColor: #383D3D; 21 | $headingTextShadow: none; 22 | $headingTextTransform: none; 23 | $backgroundColor: #F0F1EB; 24 | $linkColor: #51483D; 25 | $linkColorHover: lighten( $linkColor, 20% ); 26 | $selectionBackgroundColor: #26351C; 27 | 28 | .reveal a { 29 | line-height: 1.3em; 30 | } 31 | 32 | 33 | // Theme template ------------------------------ 34 | @import "../template/theme"; 35 | // --------------------------------------------- 36 | -------------------------------------------------------------------------------- /slides/css/theme/source/simple.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is darkblue. 4 | * 5 | * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. 6 | * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | 22 | // Override theme settings (see ../template/settings.scss) 23 | $mainFont: 'Lato', sans-serif; 24 | $mainColor: #000; 25 | $headingFont: 'News Cycle', Impact, sans-serif; 26 | $headingColor: #000; 27 | $headingTextShadow: none; 28 | $headingTextTransform: none; 29 | $backgroundColor: #fff; 30 | $linkColor: #00008B; 31 | $linkColorHover: lighten( $linkColor, 20% ); 32 | $selectionBackgroundColor: rgba(0, 0, 0, 0.99); 33 | 34 | section.has-dark-background { 35 | &, h1, h2, h3, h4, h5, h6 { 36 | color: #fff; 37 | } 38 | } 39 | 40 | 41 | // Theme template ------------------------------ 42 | @import "../template/theme"; 43 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/source/sky.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sky theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); 17 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainFont: 'Open Sans', sans-serif; 22 | $mainColor: #333; 23 | $headingFont: 'Quicksand', sans-serif; 24 | $headingColor: #333; 25 | $headingLetterSpacing: -0.08em; 26 | $headingTextShadow: none; 27 | $backgroundColor: #f7fbfc; 28 | $linkColor: #3b759e; 29 | $linkColorHover: lighten( $linkColor, 20% ); 30 | $selectionBackgroundColor: #134674; 31 | 32 | // Fix links so they are not cut off 33 | .reveal a { 34 | line-height: 1.3em; 35 | } 36 | 37 | // Background generator 38 | @mixin bodyBackground() { 39 | @include radial-gradient( #add9e4, #f7fbfc ); 40 | } 41 | 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /slides/css/theme/source/solarized.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Light theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | 19 | /** 20 | * Solarized colors by Ethan Schoonover 21 | */ 22 | html * { 23 | color-profile: sRGB; 24 | rendering-intent: auto; 25 | } 26 | 27 | // Solarized colors 28 | $base03: #002b36; 29 | $base02: #073642; 30 | $base01: #586e75; 31 | $base00: #657b83; 32 | $base0: #839496; 33 | $base1: #93a1a1; 34 | $base2: #eee8d5; 35 | $base3: #fdf6e3; 36 | $yellow: #b58900; 37 | $orange: #cb4b16; 38 | $red: #dc322f; 39 | $magenta: #d33682; 40 | $violet: #6c71c4; 41 | $blue: #268bd2; 42 | $cyan: #2aa198; 43 | $green: #859900; 44 | 45 | // Override theme settings (see ../template/settings.scss) 46 | $mainColor: $base00; 47 | $headingColor: $base01; 48 | $headingTextShadow: none; 49 | $backgroundColor: $base3; 50 | $linkColor: $blue; 51 | $linkColorHover: lighten( $linkColor, 20% ); 52 | $selectionBackgroundColor: $magenta; 53 | 54 | // Background generator 55 | // @mixin bodyBackground() { 56 | // @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); 57 | // } 58 | 59 | 60 | 61 | // Theme template ------------------------------ 62 | @import "../template/theme"; 63 | // --------------------------------------------- 64 | -------------------------------------------------------------------------------- /slides/css/theme/source/white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * White theme for reveal.js. This is the opposite of the 'black' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #fff; 20 | 21 | $mainColor: #222; 22 | $headingColor: #222; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #2a76dd; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-dark-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #fff; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /slides/css/theme/template/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-gradient( $top, $bottom ) { 2 | background: $top; 3 | background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); 4 | background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); 5 | background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); 6 | background: -o-linear-gradient( top, $top 0%, $bottom 100% ); 7 | background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); 8 | background: linear-gradient( top, $top 0%, $bottom 100% ); 9 | } 10 | 11 | @mixin horizontal-gradient( $top, $bottom ) { 12 | background: $top; 13 | background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); 14 | background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); 15 | background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); 16 | background: -o-linear-gradient( left, $top 0%, $bottom 100% ); 17 | background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); 18 | background: linear-gradient( left, $top 0%, $bottom 100% ); 19 | } 20 | 21 | @mixin radial-gradient( $outer, $inner, $type: circle ) { 22 | background: $outer; 23 | background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 24 | background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); 25 | background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 26 | background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 27 | background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 28 | background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 29 | } -------------------------------------------------------------------------------- /slides/css/theme/template/settings.scss: -------------------------------------------------------------------------------- 1 | // Base settings for all themes that can optionally be 2 | // overridden by the super-theme 3 | 4 | // Background of the presentation 5 | $backgroundColor: #2b2b2b; 6 | 7 | // Primary/body text 8 | $mainFont: 'Lato', sans-serif; 9 | $mainFontSize: 40px; 10 | $mainColor: #eee; 11 | 12 | // Vertical spacing between blocks of text 13 | $blockMargin: 20px; 14 | 15 | // Headings 16 | $headingMargin: 0 0 $blockMargin 0; 17 | $headingFont: 'League Gothic', Impact, sans-serif; 18 | $headingColor: #eee; 19 | $headingLineHeight: 1.2; 20 | $headingLetterSpacing: normal; 21 | $headingTextTransform: uppercase; 22 | $headingTextShadow: none; 23 | $headingFontWeight: normal; 24 | $heading1TextShadow: $headingTextShadow; 25 | 26 | $heading1Size: 3.77em; 27 | $heading2Size: 2.11em; 28 | $heading3Size: 1.55em; 29 | $heading4Size: 1.00em; 30 | 31 | // Links and actions 32 | $linkColor: #13DAEC; 33 | $linkColorHover: lighten( $linkColor, 20% ); 34 | 35 | // Text selection 36 | $selectionBackgroundColor: #FF5E99; 37 | $selectionColor: #fff; 38 | 39 | // Generates the presentation background, can be overridden 40 | // to return a background image or gradient 41 | @mixin bodyBackground() { 42 | background: $backgroundColor; 43 | } -------------------------------------------------------------------------------- /slides/day2-task.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Работа со списками. Домашнее задание 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 |
26 |
27 |
28 |

Лекция 2.
Домашнее задание

29 |

Страничка курса: https://maxcom.github.io/scala-course-2022/ 30 |

31 | 32 |
33 | Множество на основе бинарных деревьев поиска. 34 | 35 |

Вершина содержит элемент и
опциональные под-деревья 36 |

Почитайте статью на wikipedia 37 |

38 | 39 |
40 |

Неизменяемый

41 |

Множество: храним только уникальные элементы 42 |

Моделируем алгебраическим типом или
case class с Option полями 43 |

Пустое множество - тоже множество. 44 |

45 | 46 |
47 |

 48 | // Int - тип элемента
 49 | trait SimpleTreeSet {
 50 |   // операция вставки (без балансировки)
 51 |   // подумайте про персистентность
 52 |   def +(v: Int): SimpleTreeSet
 53 | 
 54 |   // операция проверки наличия элемента
 55 |   def contains(v: Int): Boolean
 56 | 
 57 |   // операция foreach (в любом порядке)
 58 |   def foreach(f: Int => Unit): Unit
 59 | }
 60 | 					
61 | 62 |

интерфейсы scala-коллекций не реализуем 63 |

пишем юнит-тесты 64 |

65 | 66 |
67 |

Как выкладывать решение

68 |
    69 |
  • Детали в первом ДЗ 70 |
  • Название проекта - scala-2022-task2 71 |
72 |
73 | 74 |
75 |

Напоминаю: 76 |

80 |
81 |
82 | 83 | 84 | 85 | 86 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /slides/impatient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/impatient.jpg -------------------------------------------------------------------------------- /slides/korabli1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/korabli1.jpg -------------------------------------------------------------------------------- /slides/lib/css/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /slides/lib/font/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /slides/lib/font/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('league-gothic.eot'); 4 | src: url('league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('league-gothic.woff') format('woff'), 6 | url('league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } -------------------------------------------------------------------------------- /slides/lib/font/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /slides/lib/font/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /slides/lib/font/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-Black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-Black.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-BlackIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-BlackIt.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-Bold.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-BoldIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-BoldIt.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-ExtraLight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-ExtraLight.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-ExtraLightIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-ExtraLightIt.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-It.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-It.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-Light.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-LightIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-LightIt.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-Regular.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-Semibold.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/EOT/SourceSansPro-SemiboldIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/EOT/SourceSansPro-SemiboldIt.eot -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-Black.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-BlackIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-BlackIt.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-Bold.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-BoldIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-BoldIt.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-ExtraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-ExtraLight.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-ExtraLightIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-ExtraLightIt.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-It.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-It.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-Light.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-LightIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-LightIt.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-Semibold.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/OTF/SourceSansPro-SemiboldIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/OTF/SourceSansPro-SemiboldIt.otf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-Black.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-BlackIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-BlackIt.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-BoldIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-BoldIt.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-ExtraLight.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-ExtraLightIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-ExtraLightIt.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-It.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-It.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-Light.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-LightIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-LightIt.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-Semibold.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/TTF/SourceSansPro-SemiboldIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/TTF/SourceSansPro-SemiboldIt.ttf -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Black.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Black.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-BlackIt.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-BlackIt.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Bold.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-BoldIt.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-BoldIt.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-ExtraLight.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-ExtraLight.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-ExtraLightIt.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-ExtraLightIt.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-It.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-It.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Light.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Light.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-LightIt.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-LightIt.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Regular.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Semibold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-Semibold.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-SemiboldIt.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/OTF/SourceSansPro-SemiboldIt.otf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Black.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Black.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-BlackIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-BlackIt.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Bold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Bold.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-BoldIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-BoldIt.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-ExtraLight.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-ExtraLight.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-ExtraLightIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-ExtraLightIt.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-It.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-It.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Light.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Light.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-LightIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-LightIt.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Regular.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Semibold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-Semibold.ttf.woff -------------------------------------------------------------------------------- /slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-SemiboldIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxcom/scala-course-2022/81ad0d78b38a6f1f72d20a2236fe5694d6b8cdac/slides/lib/font/source-sans-pro/WOFF/TTF/SourceSansPro-SemiboldIt.ttf.woff -------------------------------------------------------------------------------- /slides/lib/js/classList.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 2 | if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;pbody{font-family: sans-serif;}

reveal.js multiplex server.

Generate token'); 38 | res.end(); 39 | }); 40 | stream.on('readable', function() { 41 | stream.pipe(res); 42 | }); 43 | }); 44 | 45 | app.get("/token", function(req,res) { 46 | var ts = new Date().getTime(); 47 | var rand = Math.floor(Math.random()*9999999); 48 | var secret = ts.toString() + rand.toString(); 49 | res.send({secret: secret, socketId: createHash(secret)}); 50 | }); 51 | 52 | var createHash = function(secret) { 53 | var cipher = crypto.createCipher('blowfish', secret); 54 | return(cipher.final('hex')); 55 | }; 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset ); -------------------------------------------------------------------------------- /slides/plugin/multiplex/master.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Don't emit events from inside of notes windows 4 | if ( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var multiplex = Reveal.getConfig().multiplex; 7 | 8 | var socket = io.connect( multiplex.url ); 9 | 10 | function post() { 11 | 12 | var messageData = { 13 | state: Reveal.getState(), 14 | secret: multiplex.secret, 15 | socketId: multiplex.id 16 | }; 17 | 18 | socket.emit( 'multiplex-statechanged', messageData ); 19 | 20 | }; 21 | 22 | // post once the page is loaded, so the client follows also on "open URL". 23 | window.addEventListener( 'load', post ); 24 | 25 | // Monitor events that trigger a change in state 26 | Reveal.addEventListener( 'slidechanged', post ); 27 | Reveal.addEventListener( 'fragmentshown', post ); 28 | Reveal.addEventListener( 'fragmenthidden', post ); 29 | Reveal.addEventListener( 'overviewhidden', post ); 30 | Reveal.addEventListener( 'overviewshown', post ); 31 | Reveal.addEventListener( 'paused', post ); 32 | Reveal.addEventListener( 'resumed', post ); 33 | 34 | }()); 35 | -------------------------------------------------------------------------------- /slides/plugin/multiplex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal-js-multiplex", 3 | "version": "1.0.0", 4 | "description": "reveal.js multiplex server", 5 | "homepage": "http://revealjs.com", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "engines": { 10 | "node": "~4.1.1" 11 | }, 12 | "dependencies": { 13 | "express": "~4.13.3", 14 | "grunt-cli": "~0.1.13", 15 | "mustache": "~2.2.1", 16 | "socket.io": "~1.3.7" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /slides/plugin/notes-server/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // don't emit events from inside the previews themselves 4 | if( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var socket = io.connect( window.location.origin ), 7 | socketId = Math.random().toString().slice( 2 ); 8 | 9 | console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId ); 10 | 11 | window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId ); 12 | 13 | /** 14 | * Posts the current slide data to the notes window 15 | */ 16 | function post() { 17 | 18 | var slideElement = Reveal.getCurrentSlide(), 19 | notesElement = slideElement.querySelector( 'aside.notes' ); 20 | 21 | var messageData = { 22 | notes: '', 23 | markdown: false, 24 | socketId: socketId, 25 | state: Reveal.getState() 26 | }; 27 | 28 | // Look for notes defined in a slide attribute 29 | if( slideElement.hasAttribute( 'data-notes' ) ) { 30 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 31 | } 32 | 33 | // Look for notes defined in an aside element 34 | if( notesElement ) { 35 | messageData.notes = notesElement.innerHTML; 36 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 37 | } 38 | 39 | socket.emit( 'statechanged', messageData ); 40 | 41 | } 42 | 43 | // When a new notes window connects, post our current state 44 | socket.on( 'new-subscriber', function( data ) { 45 | post(); 46 | } ); 47 | 48 | // When the state changes from inside of the speaker view 49 | socket.on( 'statechanged-speaker', function( data ) { 50 | Reveal.setState( data.state ); 51 | } ); 52 | 53 | // Monitor events that trigger a change in state 54 | Reveal.addEventListener( 'slidechanged', post ); 55 | Reveal.addEventListener( 'fragmentshown', post ); 56 | Reveal.addEventListener( 'fragmenthidden', post ); 57 | Reveal.addEventListener( 'overviewhidden', post ); 58 | Reveal.addEventListener( 'overviewshown', post ); 59 | Reveal.addEventListener( 'paused', post ); 60 | Reveal.addEventListener( 'resumed', post ); 61 | 62 | // Post the initial state 63 | post(); 64 | 65 | }()); 66 | -------------------------------------------------------------------------------- /slides/plugin/notes-server/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var Mustache = require('mustache'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port : 1947, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | 20 | socket.on( 'new-subscriber', function( data ) { 21 | socket.broadcast.emit( 'new-subscriber', data ); 22 | }); 23 | 24 | socket.on( 'statechanged', function( data ) { 25 | delete data.state.overview; 26 | socket.broadcast.emit( 'statechanged', data ); 27 | }); 28 | 29 | socket.on( 'statechanged-speaker', function( data ) { 30 | delete data.state.overview; 31 | socket.broadcast.emit( 'statechanged-speaker', data ); 32 | }); 33 | 34 | }); 35 | 36 | [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) { 37 | app.use( '/' + dir, staticDir( opts.baseDir + dir ) ); 38 | }); 39 | 40 | app.get('/', function( req, res ) { 41 | 42 | res.writeHead( 200, { 'Content-Type': 'text/html' } ); 43 | fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res ); 44 | 45 | }); 46 | 47 | app.get( '/notes/:socketId', function( req, res ) { 48 | 49 | fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) { 50 | res.send( Mustache.to_html( data.toString(), { 51 | socketId : req.params.socketId 52 | })); 53 | }); 54 | 55 | }); 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' ); 65 | 66 | console.log( brown + 'reveal.js - Speaker Notes' + reset ); 67 | console.log( '1. Open the slides at ' + green + slidesLocation + reset ); 68 | console.log( '2. Click on the link in your JS console to go to the notes page' ); 69 | console.log( '3. Advance through your slides and your notes will advance automatically' ); 70 | -------------------------------------------------------------------------------- /slides/plugin/print-pdf/print-pdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * phantomjs script for printing presentations to PDF. 3 | * 4 | * Example: 5 | * phantomjs print-pdf.js "http://revealjs.com?print-pdf" reveal-demo.pdf 6 | * 7 | * @author Manuel Bieh (https://github.com/manuelbieh) 8 | * @author Hakim El Hattab (https://github.com/hakimel) 9 | * @author Manuel Riezebosch (https://github.com/riezebosch) 10 | */ 11 | 12 | // html2pdf.js 13 | var system = require( 'system' ); 14 | 15 | var probePage = new WebPage(); 16 | var printPage = new WebPage(); 17 | 18 | var inputFile = system.args[1] || 'index.html?print-pdf'; 19 | var outputFile = system.args[2] || 'slides.pdf'; 20 | 21 | if( outputFile.match( /\.pdf$/gi ) === null ) { 22 | outputFile += '.pdf'; 23 | } 24 | 25 | console.log( 'Export PDF: Reading reveal.js config [1/4]' ); 26 | 27 | probePage.open( inputFile, function( status ) { 28 | 29 | console.log( 'Export PDF: Preparing print layout [2/4]' ); 30 | 31 | var config = probePage.evaluate( function() { 32 | return Reveal.getConfig(); 33 | } ); 34 | 35 | if( config ) { 36 | 37 | printPage.paperSize = { 38 | width: Math.floor( config.width * ( 1 + config.margin ) ), 39 | height: Math.floor( config.height * ( 1 + config.margin ) ), 40 | border: 0 41 | }; 42 | 43 | printPage.open( inputFile, function( status ) { 44 | console.log( 'Export PDF: Preparing pdf [3/4]') 45 | printPage.evaluate(function() { 46 | Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom ); 47 | }); 48 | } ); 49 | 50 | printPage.onCallback = function(data) { 51 | // For some reason we need to "jump the queue" for syntax highlighting to work. 52 | // See: http://stackoverflow.com/a/3580132/129269 53 | setTimeout(function() { 54 | console.log( 'Export PDF: Writing file [4/4]' ); 55 | printPage.render( outputFile ); 56 | console.log( 'Export PDF: Finished successfully!' ); 57 | phantom.exit(); 58 | }, 0); 59 | }; 60 | } 61 | else { 62 | 63 | console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' ); 64 | phantom.exit(1); 65 | 66 | } 67 | } ); 68 | 69 | 70 | --------------------------------------------------------------------------------