├── .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 |
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 |
--------------------------------------------------------------------------------