├── 1. From Data Science to Production with Kotlin ├── oreilly_from_data_science_to_production_with_kotlin.pptx ├── section_iii_kotlin_basics.md ├── section_iv_flow_control_and_classes.md └── section_v_collections.md ├── 2. Practical Data Modeling for Production with Kotlin ├── oreilly_practical_data_modeling_for_production_with_kotlin.pptx ├── resources │ ├── customer_orders.csv │ └── thunderbird_manufacturing.db ├── section_i_working_with_data_sources.md ├── section_ii_functional_programming.md ├── section_iii_adapting_kotlin_to_your_domain.md └── section_iv_practical_applications.md ├── kotlin_project_template ├── pom.xml └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── oreilly │ │ └── Hello.kt │ └── test │ └── kotlin │ └── com │ └── oreilly │ └── HelloTest.kt ├── outline.docx └── outline.md /1. From Data Science to Production with Kotlin/oreilly_from_data_science_to_production_with_kotlin.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasnield/oreilly_kotlin_for_data_science/5662a7d55d7f9bc1ad3d59a582ee9af0ce643cd8/1. From Data Science to Production with Kotlin/oreilly_from_data_science_to_production_with_kotlin.pptx -------------------------------------------------------------------------------- /1. From Data Science to Production with Kotlin/section_iii_kotlin_basics.md: -------------------------------------------------------------------------------- 1 | # Section III: Kotlin Basics 2 | 3 | Let's write our first Kotlin application. Right-click the `src/main/kotlin` folder, click "New", and select "Kotlin file/class" to create a new Kotlin file (or just use the `hello.kt` file that was created with the archetype). Call it "Launcher" or whatever you like. This file will serve as the entry point to launch our application. 4 | 5 | ## 3.1 Hello World 6 | 7 | In the "Launcher" file, add the following snippit: 8 | 9 | ```kotlin 10 | package com.oreilly 11 | 12 | fun main(args: Array) { 13 | 14 | } 15 | ``` 16 | 17 | What we just added is the `main()` function, which is an entry-point to a Kotlin application. It executes everything inside the block `{ }`. It accepts an `args` argument which will be an array of strings. These arguments can be provided from a command line or bash file to your application, which won't be using for this course. 18 | 19 | We'll start with the obligatory "Hello World", and use `println()` to write it in the console. To run the application, click the Kotlin icon in the gutter. 20 | 21 | ```kotlin 22 | package com.oreilly 23 | 24 | fun main(args: Array) { 25 | 26 | println("Hello World!") 27 | } 28 | ``` 29 | 30 | Curly brackets are used to build blocks of code, whereas Python uses indentation. Whitespaces and indents have no impact on how the code compiles. 31 | 32 | ## 3.2A Variables 33 | 34 | Variables in Kotlin are declared with the `val` or `var` keywords. 35 | 36 | Below, we declare two `Int` variables, `x` and `y`. We create a third `Int` variable called `sum` which is the sum of the two. 37 | 38 | ```kotlin 39 | package com.oreilly 40 | 41 | fun main(args: Array) { 42 | 43 | val x = 10 44 | val y = 23 45 | 46 | val sum = x + y 47 | 48 | println(sum) 49 | } 50 | ``` 51 | 52 | `x`, `y`, and `sum` are statically declared as `Int` types. This was inferred because they are being assigned `Int` values. You can find this out by clicking one of them and clicking `CTRL + Q`. 53 | 54 | ## 3.2B Variables (Explicit types) 55 | 56 | We can also explicitly declare them as `Int` values like this, although it is not necessary because the values can be inferred from the assignments. 57 | 58 | 59 | ```kotlin 60 | package com.oreilly 61 | 62 | 63 | fun main(args: Array) { 64 | 65 | val x: Int = 10 66 | val y: Int = 23 67 | 68 | val sum: Int = x + y 69 | 70 | println(sum) 71 | } 72 | ``` 73 | 74 | 75 | ## 3.2C Variables (Using mutable var) 76 | 77 | The `val` means you cannot reassign a value to the variable. For instance, you cannot reassign a value to `y` or `sum`. This permanent, unchanging state is known as immutablity and is something you should strive for. It is a best practice in modern programming because it minimizes bugs and accidents, and is especially embraced in functional programming. 78 | 79 | 80 | ```kotlin 81 | package com.oreilly 82 | 83 | 84 | fun main(args: Array) { 85 | 86 | val x = 10 87 | val y = 23 88 | 89 | val sum = x + y 90 | 91 | println(sum) 92 | 93 | y = 10 // error, can't be reassigned 94 | sum = x + y // error, can't be reassigned 95 | 96 | println(sum) 97 | } 98 | ``` 99 | 100 | If you do want variables to "mutate", or change value, use the `var` keyword instead. 101 | 102 | 103 | ```kotlin 104 | package com.oreilly 105 | 106 | 107 | fun main(args: Array) { 108 | 109 | var x = 10 110 | var y = 23 111 | 112 | var sum = x + y 113 | 114 | println(sum) 115 | 116 | y = 10 // okay 117 | sum = x + y // okay 118 | 119 | println(sum) 120 | } 121 | ``` 122 | 123 | ## 3.3 Types and Operators 124 | 125 | Kotlin is a statically-typed language. This means you must commit a variable, property, or any item to be of one type for its entire life. This is different than dynamic languages like Python, where a variable can hold any type at any given time. While in some ways this reduces flexibility, you will reap larger benefits in clarity, reliability, and evolvability. 126 | 127 | For instance, if you have declared a `myNumber` variable to be a number, you cannot change it to a `String` later. The compiler will not let you. 128 | 129 | ## 3.3A - Type safety 130 | 131 | ```kotlin 132 | package com.oreilly 133 | 134 | 135 | fun main(args: Array) { 136 | 137 | var myNumber = 10 138 | 139 | myNumber = "Alpha" // error, must be an Int 140 | 141 | println(myNumber) 142 | } 143 | ``` 144 | 145 | With static typing, the benefit you gain is greater visibility, refactorability, and reliability. This also enables productive tooling in IDEA like autocompletion and code assistance as you type. 146 | 147 | ## 3.3B - Working with dates 148 | 149 | (View slides for basic data types) 150 | 151 | Below, we get today's date, print it, then create another date off it that is 30 days later. Note you have to hit `ALT + ENTER` to automatically import the `LocalDate` type, just like a Python module. 152 | 153 | 154 | ```kotlin 155 | package com.oreilly 156 | 157 | 158 | import java.time.LocalDate 159 | 160 | fun main(args: Array) { 161 | 162 | val today = LocalDate.now() 163 | 164 | println(today) 165 | 166 | val thirtyDaysFromNow = today.plusDays(30L) 167 | 168 | println(thirtyDaysFromNow) 169 | } 170 | ``` 171 | 172 | Note that the `LocalDate` is immutable, and the `plusDays()` function will create a new `LocalDate` off the old one. 173 | 174 | 175 | ## 3.3C - Working with BigDecimal (and String Interpolation) 176 | 177 | Here is an example that works with money, which you often want to use the `BigDecimal` type for (for more accurate division and multiplication). You can create a `BigDecimal` off a `Double` or other numeric types using its `valueOf()` factory function. 178 | 179 | ```kotlin 180 | package com.oreilly 181 | 182 | 183 | import java.math.BigDecimal 184 | 185 | fun main(args: Array) { 186 | 187 | val balance = BigDecimal.valueOf(1808.2) 188 | println("BALANCE: $balance") 189 | 190 | val transactionAmount = BigDecimal.valueOf(56.12) 191 | val newBalance = balance - transactionAmount 192 | println("NEW BALANCE: $newBalance") 193 | } 194 | ``` 195 | 196 | Note above how we can inject a value into a `String` to be printed by using a `$` within a literal String. This is called interpolation. You can also inject an entire expression in a string like this. 197 | 198 | ```kotlin 199 | package com.oreilly 200 | 201 | import java.math.BigDecimal 202 | 203 | fun main(args: Array) { 204 | 205 | val balance = BigDecimal.valueOf(1808.2) 206 | println("BALANCE: $balance") 207 | 208 | val transactionAmount = BigDecimal.valueOf(56.12) 209 | println("NEW BALANCE: ${balance - transactionAmount}") 210 | } 211 | ``` 212 | 213 | ## 3.3C - Comparing items 214 | 215 | There are a number of operators available in Kotlin, and they support several types and custom types which we will discuss later. 216 | 217 | (Switch to slides to see operators) 218 | 219 | For instance, the equality operator checks if two items are equal, and will return a `Boolean` true/false value: 220 | 221 | 222 | ```kotlin 223 | package com.oreilly 224 | 225 | import java.time.LocalDate 226 | 227 | fun main(args: Array) { 228 | 229 | val date1 = LocalDate.of(2017,3,7) 230 | val date2 = LocalDate.of(2017,3,12) 231 | 232 | val isSameMonth = date1.month == date2.month 233 | 234 | println(isSameMonth) 235 | } 236 | ``` 237 | 238 | Comparative operators check if one item is less than (or equal to) or greater than (or equal to) another : 239 | 240 | 241 | ```kotlin 242 | package com.oreilly 243 | 244 | import java.time.LocalDate 245 | 246 | fun main(args: Array) { 247 | 248 | val date1 = LocalDate.of(2017,3,7) 249 | val date2 = LocalDate.of(2017,3,12) 250 | 251 | val result = date1 >= date2 252 | 253 | println(result) 254 | } 255 | ``` 256 | 257 | We will learn about creating our own types in the next section, and how they can support these operators. 258 | 259 | 260 | ## 3.4 - Functions 261 | 262 | Kotlin supports functions that feel similar to Python, with a few additional conveniences. 263 | 264 | ## 3.4A - A parameterless Unit function 265 | 266 | To declare a function, you specify it with the `fun` keyword, followed by the function name, and then paranthesis where arguments will go. 267 | 268 | Here we create a simple function that prints a random `Int`. It accepts no arguments and returns a `Unit`, which effectively means it returns nothing. 269 | 270 | ```kotlin 271 | package com.oreilly 272 | 273 | import java.util.concurrent.ThreadLocalRandom 274 | 275 | fun main(args: Array) { 276 | printRandomInt() 277 | } 278 | 279 | fun printRandomInt() { 280 | val randomInt = ThreadLocalRandom.current().nextInt() 281 | println(randomInt) 282 | } 283 | ``` 284 | 285 | 286 | ## 3.4B - A function that returns a value 287 | 288 | If your function is going to return a meaningful value, you will need to specify the return type following the paranthesis `()`, as in `fun gereateRandomInt(): Int` which will return an `Int`. 289 | 290 | 291 | ```kotlin 292 | package com.oreilly 293 | 294 | import java.util.concurrent.ThreadLocalRandom 295 | 296 | fun main(args: Array) { 297 | println(generateRandomInt()) 298 | } 299 | 300 | fun generateRandomInt(): Int { 301 | return ThreadLocalRandom.current().nextInt() 302 | } 303 | ``` 304 | 305 | ## 3.4C - A single-line function 306 | 307 | The function above only has one line in its body, so we can actually use a simpler syntax using an `=`. We can even remove the return type which it will infer. 308 | 309 | ```kotlin 310 | package com.oreilly 311 | 312 | import java.util.concurrent.ThreadLocalRandom 313 | 314 | fun main(args: Array) { 315 | println(generateRandomInt()) 316 | } 317 | 318 | fun generateRandomInt() = ThreadLocalRandom.current().nextInt() 319 | ``` 320 | 321 | ## 3.4D - A function with parameters 322 | 323 | We can also provide parameters to a function, like a `min` and `max` for our random function. These have to be declared with specified types, which in this case both will be `Int`. 324 | 325 | ```kotlin 326 | package com.oreilly 327 | 328 | import java.util.concurrent.ThreadLocalRandom 329 | 330 | fun main(args: Array) { 331 | println(generateRandomInt(100,200)) 332 | } 333 | 334 | fun generateRandomInt(min: Int, max: Int) = 335 | ThreadLocalRandom.current().nextInt(min, max + 1) 336 | ``` 337 | 338 | 339 | ## 3.4E - Passing parameters by name 340 | 341 | You can also pass the parameters in any order by invoking them by name. This is encouraged to explicitly call out which parameters you are providing, and prevents mixing up arguments. 342 | 343 | ```kotlin 344 | package com.oreilly 345 | 346 | import java.util.concurrent.ThreadLocalRandom 347 | 348 | fun main(args: Array) { 349 | println(generateRandomInt(max=200, min=100)) 350 | } 351 | 352 | fun generateRandomInt(min: Int, max: Int) = 353 | ThreadLocalRandom.current().nextInt(min, max + 1) 354 | ``` 355 | 356 | 357 | ## 3.4F - Default parameters 358 | 359 | You can specify default values for parameters. You'll typically want these parameters to be declared last in the function, so you are not forced to call the needed parameters by name. 360 | 361 | For instance, we can make the `min` parameter default to `0`. 362 | 363 | ```kotlin 364 | package com.oreilly 365 | 366 | import java.util.concurrent.ThreadLocalRandom 367 | 368 | fun main(args: Array) { 369 | println(generateRandomInt(100)) 370 | } 371 | 372 | fun generateRandomInt(max: Int, min: Int = 0) = 373 | ThreadLocalRandom.current().nextInt(min, max + 1) 374 | ``` 375 | 376 | Default parameters can also refer to the other parameters to calculate their default value. 377 | 378 | 379 | 380 | ## 3.5 - Nullable Types 381 | 382 | Kotlin has a special feature not available in other languages called nullable types. This effectively prevents errors with null values by flagging them as possibly null. This can stop a wide array of null-related errors at compile time rather than at run time. 383 | 384 | Before we start, note that when you assign a null value to variable (without explicitly declaring its type), its type will be `Nothing`. 385 | 386 | ## 3.5A - Declaring a nullable type 387 | 388 | ```kotlin 389 | package com.oreilly 390 | 391 | fun main(args: Array) { 392 | 393 | val myValue = null //type is `Nothing` 394 | } 395 | ``` 396 | 397 | In this case, we would rather this `myValue`, even if it is null, be a `String`. However, if you explicitly make it an `String` type, it will throw a compile error. 398 | 399 | ```kotlin 400 | package com.oreilly 401 | 402 | fun main(args: Array) { 403 | 404 | val myValue: String = null // error 405 | } 406 | ``` 407 | 408 | The compiler does not like indiscriminate null values, and explicitly expects you to declare items as "nullable" if they can be null. The way you do this is to make the type "nullable", and this can be done by following the type with a question mark `?`. 409 | 410 | ```kotlin 411 | package com.oreilly 412 | 413 | fun main(args: Array) { 414 | 415 | val myValue: String? = null 416 | } 417 | ``` 418 | 419 | Now the variable is nullable. Let's see what this means. 420 | 421 | ## 3.5B - Null Safety 422 | 423 | Let's say we want to get the `length` of `myValue`. The problem is the value might be null, so calling its `length` property will throw a compile error. This is because the compiler is stopping us from a possible `NullPointerException`, and would like us to handle the possibility it is null before calling its `length`. 424 | 425 | ```kotlin 426 | package com.oreilly 427 | 428 | fun main(args: Array) { 429 | 430 | val myValue: String? = null 431 | 432 | val length = myValue.length // error 433 | 434 | println(length) 435 | } 436 | ``` 437 | 438 | One way we can solve this is to use an `if` expression (covered in more detail next section) to check if the `myValue` is null. This would satisfy the compiler. 439 | 440 | ```kotlin 441 | package com.oreilly 442 | 443 | fun main(args: Array) { 444 | 445 | val myValue: String? = null 446 | 447 | if (myValue != null) { 448 | val length = myValue.length // okay 449 | 450 | println(length) 451 | } 452 | } 453 | ``` 454 | 455 | A more idiomatic way Kotlin handles null values though is using coalescing operators, also called "safe-calls". This will only proceed to get the `length` property if the `myValue` is not null. Otherwise it will stop and just yield `null`. 456 | 457 | 458 | ```kotlin 459 | package com.oreilly 460 | 461 | fun main(args: Array) { 462 | 463 | val myValue: String? = null 464 | 465 | val length = myValue?.length 466 | 467 | println(length) 468 | } 469 | ``` 470 | 471 | You can chain as many safe-calls as you like, which helps avoid "pyramids of doom" built off nested `if` statements. 472 | 473 | 474 | ## 3.5C - The Elvis Operator 475 | 476 | If you want to switch a null value to another value if it is null, you can use the "Elvis" operator to achieve this quickly. It is named after the eponymous music artist for which the operator `?:` looks like. 477 | 478 | Below, we default our operation to a length of `0` if the expression yields a null value. 479 | 480 | 481 | ```kotlin 482 | package com.oreilly 483 | 484 | fun main(args: Array) { 485 | 486 | val myValue: String? = null 487 | 488 | val length = myValue?.length?:0 489 | 490 | println(length) 491 | } 492 | ``` 493 | 494 | ```kotlin 495 | package com.oreilly 496 | 497 | fun main(args: Array) { 498 | 499 | val myValue: String? = "Foxtrot" 500 | 501 | val length = myValue?.length?:0 502 | 503 | println(length) 504 | } 505 | ``` 506 | 507 | ## 3.5C - Nullable Exceptions and the "Bang! Bang!" Operator 508 | 509 | The "Bang! Bang!" operator, unofficially known as the "hold my beer" operator, is a brute force way to treat a nullable type as no longer nullable. The problem is it will throw a null pointer exception if the value is indeed null. 510 | 511 | ```kotlin 512 | package com.oreilly 513 | 514 | 515 | fun main(args: Array) { 516 | 517 | val myValue: String? = "Foxtrot" 518 | 519 | val length: Int = myValue!!.length // okay 520 | 521 | println(length) 522 | } 523 | ``` 524 | 525 | 526 | ```kotlin 527 | package com.oreilly 528 | 529 | 530 | fun main(args: Array) { 531 | 532 | val myValue: String? = null 533 | 534 | val length: Int = myValue!!.length // runtime error 535 | 536 | println(length) 537 | } 538 | ``` 539 | 540 | This is an operator you will want to avoid using because it is better to leverage the null safety that Kotlin offers, and choose more effective strategies to handle the null value. If you want to force a value to not be nullable anymore, you can throw an explicit Exception instead. 541 | 542 | ```kotlin 543 | package com.oreilly 544 | 545 | fun main(args: Array) { 546 | 547 | val myValue: String? = "Foxtrot" 548 | 549 | val length: Int = myValue?.length ?: throw Exception("This should not be null!") 550 | 551 | println(length) 552 | } 553 | ``` 554 | 555 | # 3.6 Project Navigation and Organization 556 | 557 | Cover the following: 558 | * Version Control Tracking 559 | * Files and packages 560 | * CTRL + Q to see object types 561 | * CTRL + B to jump to declaration 562 | * SHIFT + F6 to rename 563 | * Quick search and Maven pane 564 | -------------------------------------------------------------------------------- /1. From Data Science to Production with Kotlin/section_iv_flow_control_and_classes.md: -------------------------------------------------------------------------------- 1 | # Section IV - Flow Control and Classes 2 | 3 | Like any capable programming language, Kotlin has operators to control program flow and logic, like `if`, `when`, and loops. However, you likely won't use these often after you discover Sequences and other functional tools in Kotlin. But they are here for your use. We will also cover pragmatic OOP for data science, and learn some practical ways you can leverage object-oriented programming with Kotlin. 4 | 5 | Note we are not going to cover Kotlin's language and class features comprehensively, and instead focus on the parts that add immediate value for data science tasks. You can always check out https://kotlinlang.org/docs/reference/ to get a complete walkthrough of Kotlin's language features. 6 | 7 | # 4-1: `if` 8 | 9 | In Kotlin, there are two helpful operators to express conditional actions or values: `if` and `when`. 10 | 11 | ## 4-1A: `if` with an action 12 | 13 | The `if` expression should be fairly familiar. It will return a value or execute an action when a condition tests true. 14 | 15 | For example, we can print a message if a `currentSpeed` is greater than the `speedLimit`. 16 | 17 | ```kotlin 18 | package com.oreilly 19 | 20 | 21 | fun main(args: Array) { 22 | 23 | val speedLimit = 65 24 | val currentSpeed = 40 25 | 26 | if (currentSpeed > speedLimit) { 27 | println("Exceeding speed limit!") 28 | } 29 | } 30 | ``` 31 | 32 | ## 4-1B: `if-else` with an action 33 | 34 | You can also add an `else` clause to execute an action if the condition is `false`. 35 | 36 | ```kotlin 37 | package com.oreilly 38 | 39 | 40 | fun main(args: Array) { 41 | 42 | val speedLimit = 65 43 | val currentSpeed = 40 44 | 45 | if (currentSpeed > speedLimit) { 46 | println("Exceeding speed limit!") 47 | } 48 | else { 49 | println("Speed is okay!") 50 | } 51 | } 52 | ``` 53 | 54 | If each branch is one line, you don't need curly brackets. 55 | 56 | ```kotlin 57 | package com.oreilly 58 | 59 | 60 | fun main(args: Array) { 61 | 62 | val speedLimit = 65 63 | val currentSpeed = 40 64 | 65 | if (currentSpeed > speedLimit) 66 | println("Exceeding speed limit!") 67 | else 68 | println("Speed is okay!") 69 | } 70 | ``` 71 | 72 | ## 4-1C: `if-else` that returns a value 73 | 74 | The `if` expression can actually return a value (in our earlier examples, it returned a `Unit`). This means you can save the result of an `if` expression and assign it to a variable. 75 | 76 | ```kotlin 77 | package com.oreilly 78 | 79 | 80 | fun main(args: Array) { 81 | 82 | val distance = 150 83 | 84 | val haulType = if (distance > 200) { 85 | "LONG HAUL" 86 | } 87 | else { 88 | "SHORT HAUL" 89 | } 90 | 91 | println("$distance is a $haulType") 92 | } 93 | ``` 94 | 95 | ## 4-1D: `if-else` that returns a value 96 | 97 | If you don't need multiple lines in a block, you can shorthand this in a single line without curly brackets `{ }`. 98 | 99 | ```kotlin 100 | package com.oreilly 101 | 102 | 103 | fun main(args: Array) { 104 | 105 | val distance = 150 106 | 107 | val haulType = if (distance > 200) "LONG HAUL" else "SHORT HAUL" 108 | 109 | println("$distance is a $haulType") 110 | } 111 | ``` 112 | 113 | ## 4-1D: "AND" 114 | 115 | You can combine conditions using the "and" `&&` operator, which joins several conditions together and requires all of them to be `true`. Below we check for sleet conditions, which must have rain present and a temperature less than 32 degrees to occur. 116 | 117 | 118 | ```kotlin 119 | package com.oreilly 120 | 121 | 122 | fun main(args: Array) { 123 | 124 | val isRain = true 125 | val temperature = 31 126 | 127 | if (isRain && temperature < 32) { 128 | println("FORECAST: Freezing sleet") 129 | } 130 | } 131 | ``` 132 | 133 | ## 4-1E: "OR" 134 | 135 | The double-pipe `||` serves as an `OR` operator, which joins several conditions and requires one of them to be `true` to yield `true`. Below, we check for any snowfall or sleet conditions. Note we group up the sleet conditons in paranthesis `( )` so there is no mixup between the `AND` and `OR`. 136 | 137 | ```kotlin 138 | package com.oreilly 139 | 140 | 141 | fun main(args: Array) { 142 | 143 | val isRain = false 144 | val temperature = 38 145 | 146 | val snowFall = 12 147 | if (snowFall > 0 || (isRain && temperature < 32)) { 148 | println("FORECAST: Freezing sleet or snow") 149 | } 150 | } 151 | ``` 152 | 153 | # 4-2: `when` Expressions 154 | 155 | A `when` expression is a more flexible alternative to `if` that allows you to specify multiple conditions mapped to resulting values. 156 | 157 | 158 | ## 4-2A: Measuring Wind Speed 159 | 160 | Below we map different conditions to different `println` actions (each which return `Unit`) to categorize a wind speed. 161 | 162 | ```kotlin 163 | package com.oreilly 164 | 165 | 166 | fun main(args: Array) { 167 | 168 | // solicit wind speed input 169 | println("Input a wind speed") 170 | val windSpeed = readLine()?.toInt()?: throw Exception("Please provide a wind speed!") 171 | 172 | // evaluate wind speed category 173 | when { 174 | windSpeed >= 40 -> println("HIGH") 175 | windSpeed >= 30 -> println("MODERATE") 176 | else -> println("LOW") 177 | } 178 | } 179 | ``` 180 | 181 | ## 4-2B: Measuring Wind Speed (by mapping value) 182 | 183 | We can also use a `when` to map each condition to a value, and save that resulting value to a variable. 184 | 185 | ```kotlin 186 | package com.oreilly 187 | 188 | 189 | fun main(args: Array) { 190 | 191 | // solicit wind speed input 192 | println("Input a wind speed") 193 | val windSpeed = readLine()?.toInt()?: throw Exception("Please provide a wind speed!") 194 | 195 | // evaluate wind speed category 196 | val windSeverity = when { 197 | windSpeed >= 40 -> "HIGH" 198 | windSpeed >= 30 -> "MODERATE" 199 | else -> "LOW" 200 | } 201 | 202 | println("$windSpeed MPH has a severity of $windSeverity") 203 | } 204 | ``` 205 | 206 | 207 | ## 4-2C: Throwing an Exception in `when` 208 | 209 | If you want to invalidate everything that falls to the `else` clause, you can have it throw an `Exception`. This is a good way to exhaustively define valid conditions, and throw an error for anything else. 210 | 211 | ```kotlin 212 | package com.oreilly 213 | 214 | 215 | fun main(args: Array) { 216 | 217 | // solicit wind speed input 218 | println("Input a wind speed") 219 | val windSpeed = readLine()?.toInt()?: throw Exception("Please provide a wind speed!") 220 | 221 | // evaluate wind speed category 222 | val windSeverity = when { 223 | windSpeed >= 40 -> "HIGH" 224 | windSpeed >= 30 -> "MODERATE" 225 | windSpeed >= 0 -> "LOW" 226 | else -> throw Exception("Wind speed must be positive!") 227 | } 228 | 229 | println("$windSpeed MPH has a severity of $windSeverity") 230 | } 231 | ``` 232 | 233 | 234 | # 4-3 Classes 235 | 236 | Object-oriented programming and classes have often been overlooked in data science, primarily because they traditionally require a lot of boilerplate code and reduce flexibility. This is not the case with Kotlin. Classes are a powerful tool in expressing business domains, especially with data classes which are an effective substitute for tuples. 237 | 238 | Currently, classes don't provide direct means to translate to matrices and other tabular data structures essential for learning tasks. But classes provide a way of keeping business domain code well organized, and it's not much effort to fluently convert a class into a vector/matrix that works with data science libraries. This will be covered in _Practical Data Modeling for Production, with Kotlin_. 239 | 240 | ## 4-3A: A Basic Class 241 | 242 | A class is an entity that is used to create instances of objects, which can be used to model things in our world. For instance, we can create a `Patient` class that holds `firstName`, `lastName`, and `birthday` properties. Each type must be explicitly declared, with a `val` or `var` keyword just like variables. We can use this to create several patients with these properties. 243 | 244 | Note you can also provide the properties as named parameters, just like functions. 245 | 246 | ```kotlin 247 | package com.oreilly 248 | 249 | 250 | import java.time.LocalDate 251 | 252 | fun main(args: Array) { 253 | 254 | val firstPatient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 255 | println("First patient is ${firstPatient.firstName} ${firstPatient.lastName}") 256 | 257 | val secondPatient = Patient(firstName="John", lastName="Payne", birthday=LocalDate.of(1981, 6, 11)) 258 | println("Second patient is ${secondPatient.firstName} ${secondPatient.lastName}") 259 | } 260 | 261 | class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) 262 | ``` 263 | 264 | ## 4-3B: Putting functions in a class 265 | 266 | It is possible to add functions to a class which can use the properties (or other code in your project) to produce helpful calculations. Below, we add a `getAge()` function to the `Patient` class which returns the patient's age. 267 | 268 | ```kotlin 269 | package com.oreilly 270 | 271 | 272 | import java.time.LocalDate 273 | import java.time.temporal.ChronoUnit 274 | 275 | fun main(args: Array) { 276 | 277 | val firstPatient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 278 | 279 | println("${firstPatient.firstName}'s age is ${firstPatient.getAge()}") 280 | } 281 | 282 | class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 283 | 284 | fun getAge() = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 285 | } 286 | ``` 287 | 288 | Everything we learned about functions from the last section can be applied to functions inside classes. 289 | 290 | ## 4-3C: Putting Derived Properties in a class 291 | 292 | However, the above example could be improved by using a derived property instead. Functions are often used when you expect parameters could be provided. If we are simply calculating an attribute based on no parameters at all about the item, we can express a "derived property" like so: 293 | 294 | ```kotlin 295 | package com.oreilly 296 | 297 | 298 | import java.time.LocalDate 299 | import java.time.temporal.ChronoUnit 300 | 301 | fun main(args: Array) { 302 | 303 | val patient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 304 | 305 | println("${patient.firstName}'s age is ${patient.age}") 306 | } 307 | 308 | class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 309 | 310 | val age get() = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 311 | } 312 | ``` 313 | 314 | `get()` will calculate the value every time it is called. If you omit the `get()` keyword, this will calculate and persist the value once. This can be good if the value is expensive to calculate, but then it takes up memory. 315 | 316 | ```kotlin 317 | package com.oreilly 318 | 319 | 320 | import java.time.LocalDate 321 | import java.time.temporal.ChronoUnit 322 | 323 | fun main(args: Array) { 324 | 325 | val patient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 326 | 327 | println("${patient.firstName}'s age is ${patient.age}") 328 | } 329 | 330 | class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 331 | 332 | val age = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 333 | } 334 | ``` 335 | 336 | 337 | ## 4-4: Data Classes 338 | 339 | When your class is often holding data (which is the case for our `Patient` class), it can be useful to declare it as a `data class` instead. This enhances the class with a lot of additional functionality, including making it print-friendly and displaying its contents. 340 | 341 | 342 | ```kotlin 343 | package com.oreilly 344 | 345 | 346 | import java.time.LocalDate 347 | import java.time.temporal.ChronoUnit 348 | 349 | fun main(args: Array) { 350 | 351 | val patient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 352 | 353 | println(patient) 354 | } 355 | 356 | data class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 357 | 358 | val age get() = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 359 | } 360 | ``` 361 | 362 | The `data class` sports the following features: 363 | 364 | * A concept of equality to see if two instances of data classes are equal based on their properties 365 | * A `toString()` implementation that displays the contents of the object 366 | * A `copy()` function that allows you to create new objects off the old one, and change certain properties. 367 | * `componentN()` functions that numerically correspond to each property. 368 | 369 | Data classes are helpful for respresenting domain objects. Because of their concept of equality, they can be used as key objects for rapid lookups as well (covered later). They serve as a robust and clearer alternative to Tuples or Dicts. 370 | 371 | Here is a demonstration of equality. Again, the properties in the primary constructor (where the provided properties are held) drive the data class features, including equality. 372 | 373 | ```kotlin 374 | package com.oreilly 375 | 376 | 377 | import java.time.LocalDate 378 | import java.time.temporal.ChronoUnit 379 | 380 | fun main(args: Array) { 381 | 382 | val firstPatient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 383 | val secondPatient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 384 | val thirdPatient = Patient("Alex", "Johnson", LocalDate.of(1981, 3, 15)) 385 | 386 | if (firstPatient == secondPatient) 387 | println("firstPatient and secondPatient are duplicates!") 388 | 389 | if (firstPatient != thirdPatient) 390 | println("firstPatient and thirdPatient are not duplicates!") 391 | 392 | } 393 | 394 | data class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 395 | 396 | val age get() = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 397 | } 398 | ``` 399 | 400 | Here is a demonstration of copying-and-modifying a `Patient`. 401 | 402 | 403 | ```kotlin 404 | package com.oreilly 405 | 406 | 407 | import java.time.LocalDate 408 | import java.time.temporal.ChronoUnit 409 | 410 | fun main(args: Array) { 411 | 412 | val patient = Patient("Elena", "Patterson", LocalDate.of(1985, 1, 4)) 413 | 414 | val modifiedPatient = patient.copy(lastName = "Connors") 415 | 416 | println(patient) 417 | println(modifiedPatient) 418 | } 419 | 420 | data class Patient(val firstName: String, val lastName: String, val birthday: LocalDate) { 421 | 422 | val age get() = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 423 | } 424 | ``` 425 | 426 | This copy-and-modify patterns maintains immutability, which is preferable and can help prevent accidental mutations. 427 | 428 | # 4-5 Leveraging Initializers 429 | 430 | Sometimes you may use an `init { }` block to further initialize your class with certain operations like loading data or performing validation. Below, we have a `Vehicle` class that needs either a `highwayMpg` or a `kilowattHours`, but it cannot be assigned both. We use the `init` block below to throw an error if these rules are broken. 431 | 432 | ```kotlin 433 | package com.oreilly 434 | 435 | 436 | fun main(args: Array) { 437 | 438 | val firstVehicle = Vehicle("Tesla", "Model S", 2017, kilowattHours = 75) 439 | val secondVehicle = Vehicle("Ford", "Fusion", 2016, highwayMpg = 26, kilowattHours = 38) 440 | } 441 | 442 | data class Vehicle(val make: String, 443 | val model: String, 444 | val year: Int, 445 | val highwayMpg: Int? = null, 446 | val kilowattHours: Int? = null) { 447 | 448 | init { 449 | if (highwayMpg == null && kilowattHours == null) 450 | throw Exception("A highwayMpg or kilowattHours must be assigned!") 451 | 452 | if (highwayMpg != null && kilowattHours != null) 453 | throw Exception("highwayMpg and kilowattHours cannot both be assigned!") 454 | } 455 | 456 | val isElectric = kilowattHours != null 457 | val isGas = highwayMpg != null 458 | } 459 | ``` 460 | 461 | # 4-6 Singletons 462 | 463 | Sometimes we want to only have a single instance of a given class and make it easily accessible everywhere. We can do this using the `object` keyword instead of `class`, and it will have all the features of a class other than its restricted to a single instance. 464 | 465 | For instance, we may set universal arguments for some business parameters, like model constants. Here, we create a `ModelArguments` object that contains two mutable properties that can be reassigned at any time. 466 | 467 | ```kotlin 468 | package com.oreilly 469 | 470 | 471 | 472 | fun main(args: Array) { 473 | 474 | ModelArguments.minimumAccuracy = .20 475 | ModelArguments.targetAccuracy = .60 476 | 477 | val correctPredictions = 205 478 | val totalPredictions = 500 479 | 480 | val accuracy = correctPredictions.toDouble() / totalPredictions.toDouble() 481 | 482 | println(ModelArguments.meetsTarget(accuracy)) 483 | } 484 | 485 | object ModelArguments { 486 | var minimumAccuracy = 0.0 487 | var targetAccuracy = 0.0 488 | 489 | fun meetsTarget(accuracy: Double) = accuracy >= targetAccuracy 490 | fun meetsMinimum(accuracy: Double) = accuracy >= minimumAccuracy 491 | } 492 | ``` 493 | 494 | # 4-7 Enums 495 | 496 | 497 | Enums are kind of like Singletons, but are restricted to a few specified instances rather than just one. They can be useful to define a type that only has a strict set of values. For example, instead of using a "MALE" and "FEMALE" as strings, you can make this an enum type instead that allows no other values (whereas Strings can allow any value). 498 | 499 | ```kotlin 500 | package com.oreilly 501 | 502 | 503 | fun main(args: Array) { 504 | 505 | val patient = Patient(firstName = "John", lastName = "Mooney", gender = Gender.MALE) 506 | 507 | println(patient.gender) 508 | 509 | } 510 | 511 | data class Patient(val firstName: String, 512 | val lastName: String, 513 | val gender: Gender) 514 | 515 | 516 | enum class Gender { 517 | MALE, 518 | FEMALE 519 | } 520 | ``` 521 | 522 | Enums can be much more complex and have abstract functions with different implementations, but we will keep enums simple for our purposes. Since enums are classes, we can specify properties for each instance. Below, we supply a "chromosomes" property which holds a `String` of "XX" or "XY" for `FEMALE` and `MALE` respectively. 523 | 524 | ```kotlin 525 | package com.oreilly 526 | 527 | 528 | fun main(args: Array) { 529 | 530 | val patient = Patient(firstName = "John", lastName = "Mooney", gender = Gender.MALE) 531 | 532 | println(patient.gender.chromosomes) 533 | } 534 | 535 | data class Patient(val firstName: String, 536 | val lastName: String, 537 | val gender: Gender) 538 | 539 | 540 | enum class Gender(val chromosomes: String) { 541 | MALE("XY"), 542 | FEMALE("XX") 543 | } 544 | ``` 545 | -------------------------------------------------------------------------------- /1. From Data Science to Production with Kotlin/section_v_collections.md: -------------------------------------------------------------------------------- 1 | # Section V: Collections 2 | 3 | Kotlin has several convenient collection types that can be implemented in a variety of ways. In this section, we will focus on arrays, lists, and maps, as well as factory patterns and collection operators. 4 | 5 | # Section 5.1: Ranges and For-Loops 6 | 7 | In Kotlin, you can pair two values together to create a "range". Some types support ranges like numbers, dates, and other comparables. Some ranges can be looped through. 8 | 9 | ## 5.1A: Looping through an Int Range 10 | 11 | To create a range, put a `..` between the two values. Below, we create a range of 1 through 10, then print each value in that range with a `for` loop. 12 | 13 | ```kotlin 14 | package com.oreilly 15 | 16 | fun main(args: Array) { 17 | 18 | val range = 1..10 19 | 20 | for (i in range) { 21 | println(i) 22 | } 23 | } 24 | ``` 25 | 26 | ## 5.1B: Checking if a Value is in a Range 27 | 28 | Below, we create a range with two dates, and use the `in` operator to see if it contains a certain date in that range. 29 | 30 | ```kotlin 31 | package com.oreilly 32 | 33 | import java.time.LocalDate 34 | 35 | fun main(args: Array) { 36 | 37 | val dateRange = LocalDate.of(2017,3,1)..LocalDate.of(2017,6,1) 38 | 39 | val testDate = LocalDate.of(2017,4,6) 40 | 41 | println(testDate in dateRange) 42 | } 43 | ``` 44 | 45 | ## 5.1C: Closed Ranges 46 | 47 | Note that you cannot loop through a range of `LocalDate`, because it is a `Comparable` and therefore only works with `ClosedRange` rather than `IntRange` or other numeric ranges. We will learn how to iterate a series of dates in a range later. 48 | 49 | ```kotlin 50 | package com.oreilly 51 | 52 | 53 | import java.time.LocalDate 54 | 55 | fun main(args: Array) { 56 | 57 | val dateRange = LocalDate.of(2017,3,1)..LocalDate.of(2017,6,1) 58 | 59 | for (dt in dateRange) { // error, not supported 60 | println(dt) 61 | } 62 | } 63 | ``` 64 | 65 | # Section 5.2: Arrays 66 | 67 | An `Array` is a fixed-length collection of items that maintains their order. 68 | 69 | # 5.2A Using an Array of Objects 70 | 71 | Below, we create an array of vehicles and print the second item in the array. 72 | 73 | ```kotlin 74 | package com.oreilly 75 | 76 | 77 | fun main(args: Array) { 78 | 79 | val samples = arrayOf( 80 | Vehicle("Tesla", "Model S", 2017, kilowattHours = 75), 81 | Vehicle("Ford", "Fusion", 2016, highwayMpg = 26), 82 | Vehicle("Toyota", "Camry", 2016, highwayMpg = 35) 83 | ) 84 | 85 | println(samples[1]) 86 | } 87 | 88 | data class Vehicle(val make: String, 89 | val model: String, 90 | val year: Int, 91 | val highwayMpg: Int? = null, 92 | val kilowattHours: Int? = null) { 93 | 94 | val isElectric = kilowattHours != null 95 | val isGas = highwayMpg != null 96 | } 97 | ``` 98 | 99 | We can also loop through the items: 100 | 101 | ```kotlin 102 | package com.oreilly 103 | 104 | 105 | fun main(args: Array) { 106 | 107 | val samples = arrayOf( 108 | Vehicle("Tesla", "Model S", 2017, kilowattHours = 75), 109 | Vehicle("Ford", "Fusion", 2016, highwayMpg = 26), 110 | Vehicle("Toyota", "Camry", 2016, highwayMpg = 35) 111 | ) 112 | 113 | for (car in samples) { 114 | println(car) 115 | } 116 | } 117 | 118 | data class Vehicle(val make: String, 119 | val model: String, 120 | val year: Int, 121 | val highwayMpg: Int? = null, 122 | val kilowattHours: Int? = null) { 123 | 124 | val isElectric = kilowattHours != null 125 | val isGas = highwayMpg != null 126 | } 127 | ``` 128 | 129 | ## 5.3B: Looping with indices 130 | 131 | We can also loop through with indices: 132 | 133 | ```kotlin 134 | package com.oreilly 135 | 136 | 137 | fun main(args: Array) { 138 | 139 | val samples = arrayOf( 140 | Vehicle("Tesla", "Model S", 2017, kilowattHours = 75), 141 | Vehicle("Ford", "Fusion", 2016, highwayMpg = 26), 142 | Vehicle("Toyota", "Camry", 2016, highwayMpg = 35) 143 | ) 144 | 145 | for ((index,car) in samples.withIndex()) { 146 | println("Item at $index is $car") 147 | } 148 | } 149 | 150 | data class Vehicle(val make: String, 151 | val model: String, 152 | val year: Int, 153 | val highwayMpg: Int? = null, 154 | val kilowattHours: Int? = null) { 155 | 156 | val isElectric = kilowattHours != null 157 | val isGas = highwayMpg != null 158 | } 159 | ``` 160 | 161 | ## 5.2C: Using an Array of Numbers 162 | 163 | Because arrays are of fixed length, and this length must be inferred in advance, they are not used often as the amount of data expected is not always known in advance. You are more likely to use Lists, which we will learn about shortly. 164 | 165 | Arrays are needed if you need to do numerical vector or matrix work, but more likely you will use libraries like Apache Commons Math, ND4J, Koma, and Kotlin-Statistics to do matrix work for you. 166 | 167 | When you are using numeric types in arrays, use the array builder that is optimized for that numeric type. Below, we declare an array of Doubles and print them as a concatenated string using `joinToString()`. 168 | 169 | ```kotlin 170 | package com.oreilly 171 | 172 | 173 | fun main(args: Array) { 174 | 175 | val vector = doubleArrayOf(1.0, 5.0, 6.0, 11.0) 176 | 177 | println(vector.joinToString(separator = ",", prefix = "[", postfix = "]")) 178 | } 179 | ``` 180 | 181 | 182 | Note also that arrays are mutable, and any element can be modified at any given time. 183 | 184 | ```kotlin 185 | package com.oreilly 186 | 187 | 188 | fun main(args: Array) { 189 | 190 | val vector = doubleArrayOf(1.0, 5.0, 6.0, 11.0) 191 | 192 | vector[2] = 3.0 193 | 194 | println(vector.joinToString(separator = ",", prefix = "[", postfix = "]")) 195 | } 196 | ``` 197 | 198 | ## 5.2D Multi-dimensional arrays 199 | 200 | To leverage multidimensional arrays, nest `arrayOf()` calls to create as many dimensions as you need. Again, you should try to leverage libraries like Apache Commons Math, ND4J, Koma, or Kotlin-Statistics instead. 201 | 202 | ```kotlin 203 | package com.oreilly 204 | 205 | 206 | fun main(args: Array) { 207 | 208 | val matrix = arrayOf( 209 | doubleArrayOf(1.0, 5.0, 6.0, 11.0), 210 | doubleArrayOf(-51.0, 91.0, 5.0, 67.1), 211 | doubleArrayOf(29.4, 2.1, 6.4, 65.3) 212 | ) 213 | 214 | println(matrix[1][3]) 215 | } 216 | ``` 217 | 218 | # 5.3A Lists 219 | 220 | Lists are an ordered collection of elements that, depending on whether it is mutable, can grow or shrink. Lists are a more common type in Kotlin that feel similar to arrays, and work similarly to lists in Python. The primary difference is they pack a lot more features and are immutable (unless you make an immutable list, which we will cover later in this section). 221 | 222 | You declare an immutable List using the `listOf()` function. 223 | 224 | ```kotlin 225 | package com.oreilly 226 | 227 | 228 | fun main(args: Array) { 229 | 230 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 231 | 232 | for (s in strings) { 233 | println(s) 234 | } 235 | } 236 | ``` 237 | 238 | Lists are immutable, meaning that factories that "modify" the List will actually return new Lists. Below, we call the `reversed()` function which will return a new `List` with the Strings in reverse order. 239 | 240 | 241 | ```kotlin 242 | package com.oreilly 243 | 244 | 245 | fun main(args: Array) { 246 | 247 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 248 | 249 | val reversed = strings.reversed() 250 | 251 | for (s in reversed) { 252 | println(s) 253 | } 254 | } 255 | ``` 256 | 257 | ## 5.3B Mutable Lists 258 | 259 | To make a mutable List, use the `mutableListOf()` function instead. This list can have items modified, removed, and appended at any time. Try to stick with immutable lists so you don't create opportunities for mistakes. 260 | 261 | ```kotlin 262 | package com.oreilly 263 | 264 | 265 | fun main(args: Array) { 266 | 267 | val strings = mutableListOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 268 | 269 | strings[3] = "Whiskey" 270 | 271 | strings += "Tango" //append Tango 272 | strings -= "Alpha" //remove Alpha 273 | 274 | println(strings) 275 | } 276 | ``` 277 | 278 | # 5.4 Sets 279 | 280 | To only hold unique items (based on the `hashcode()`/`equals()` implementation of objects), you can use a `Set` which may or may not be ordered depending on its underlying implementation. 281 | 282 | Below, we try to add an item to a `MutableSet` but since it already contains that item, it will ignore that addition. 283 | 284 | 285 | ```kotlin 286 | package com.oreilly 287 | 288 | 289 | fun main(args: Array) { 290 | 291 | val strings = mutableSetOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 292 | 293 | strings += "Alpha" 294 | 295 | println(strings) 296 | } 297 | ``` 298 | 299 | # 5.5 Collection operators 300 | 301 | There are many convenient collection operators that allow you to work with and transform collections easily. In _Practical Data Modeling for Production with Kotlin_, we will cover Sequences which can unlock further efficiency in transforming and loading data. But for now we will stick with the collection operators. 302 | 303 | 304 | ## 5.5A forEach() 305 | 306 | In Kotlin, you can pass _lambda arguments_ to some functions (known as higher-order functions) to quickly pass a behavior as an argument. We will dissect what this means in more detail in the next video, but for now just know it can quickly specify an instruction on what to do with each element in a collection. 307 | 308 | For instance, rather than using a loop to print each item in a `List`, `Array`, `Set`, or `Map`, we can simply call the `forEach()` function and pass a lambda argument saying to print each item (referred to as `it`). 309 | 310 | 311 | ```kotlin 312 | package com.oreilly 313 | 314 | 315 | fun main(args: Array) { 316 | 317 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 318 | 319 | strings.forEach { println(it) } 320 | } 321 | ``` 322 | 323 | As you start using these kinds of functions, you will find loops become a rarity in your daily work. 324 | 325 | 326 | ## 5.5B Mapping to Lengths 327 | 328 | Below, we quickly turn a `List` into a `List` by calling the `map()` function and pass a lambda argument mapping each String to its length. 329 | 330 | ```kotlin 331 | package com.oreilly 332 | 333 | 334 | fun main(args: Array) { 335 | 336 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 337 | 338 | val lengths = strings.map { it.length } 339 | 340 | lengths.forEach { println(it) } 341 | } 342 | ``` 343 | 344 | 345 | ## 5.5C Filtering 346 | 347 | 348 | We can also filter items that meet a condition into a new `List`. For instance, we can derive a new List that only contains Strings greater than 5 characters in length. 349 | 350 | 351 | ```kotlin 352 | package com.oreilly 353 | 354 | 355 | fun main(args: Array) { 356 | 357 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 358 | 359 | val filteredStrings = strings.filter { it.length > 5 } 360 | 361 | filteredStrings.forEach { println(it) } 362 | } 363 | ``` 364 | 365 | ## 5.5D Getting Distinct Letters from All Strings 366 | 367 | In the below example, we derive all the distinct letters from our six Strings. We use `flatMap()` to break up into a `List` of all the characters, and then `filter()` to remove any empty characters. Finally we use `map()` to make all letters uppercase and then use `distinct()` to get the distinct letters. 368 | 369 | 370 | ```kotlin 371 | package com.oreilly 372 | 373 | 374 | fun main(args: Array) { 375 | 376 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 377 | 378 | val distinctLetters = strings.flatMap { it.split("") } 379 | .filter { it.trim() != "" } 380 | .map { it.toUpperCase() } 381 | .distinct() 382 | 383 | println(distinctLetters) 384 | } 385 | ``` 386 | 387 | 388 | 389 | # 5.6 Maps 390 | 391 | Maps are analagous to Dicts in Python, and they are a useful tool to quickly look up items based on a "Key" object. 392 | 393 | Data classes (as well as Strings, Ints, Longs, Dates, and other "value" types) make good key objects because they already implement `hashcode()/equals()`. 394 | 395 | ## 5.6A 396 | 397 | To quickly declare a map on-the-spot, you can use the `mapOf()` function. 398 | 399 | ```kotlin 400 | package com.oreilly 401 | 402 | 403 | fun main(args: Array) { 404 | 405 | val map = mapOf( 406 | 5 to "Alpha", 407 | 6 to "Beta", 408 | 3 to "Gamma", 409 | 7 to "Delta", 410 | 11 to "Epsilon" 411 | ) 412 | 413 | println(map[6]) 414 | println(map[8]) 415 | } 416 | ``` 417 | 418 | 419 | ## 5.6B 420 | 421 | If you want to load data into a map later, you can use `mutableMapOf()` to create a `Map` that can be modified. Note that if multiple values are assigned to the same key, only the last assigned value will persist. 422 | 423 | 424 | ```kotlin 425 | package com.oreilly 426 | 427 | 428 | fun main(args: Array) { 429 | 430 | val strings = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 431 | 432 | val stringsByLengths = mutableMapOf() 433 | 434 | for (s in strings) { 435 | stringsByLengths[s.length] = s 436 | } 437 | 438 | println(stringsByLengths) 439 | } 440 | ``` 441 | 442 | We will learn about more robust ways to load maps (including multimaps) in the next video. 443 | 444 | ## 5.6C 445 | 446 | You can also leverage data classes as keys. Below, we retrieve "John Simone" from the `Map` by using a `NameKey`. 447 | 448 | ```kotlin 449 | package com.oreilly 450 | 451 | 452 | import java.time.LocalDate 453 | 454 | fun main(args: Array) { 455 | 456 | data class Patient(val firstName: String, 457 | val lastName: String, 458 | val birthday: LocalDate, 459 | val whiteBloodCellCount: Int) 460 | 461 | data class NameKey(val firstName: String, val lastName: String) 462 | 463 | val patients = listOf( 464 | Patient("John", "Simone", LocalDate.of(1989, 1, 7), 4500), 465 | Patient("Sarah", "Marley", LocalDate.of(1970, 2, 5), 6700), 466 | Patient("Jessica", "Arnold", LocalDate.of(1980, 3, 9), 3400), 467 | Patient("Sam", "Beasley", LocalDate.of(1981, 4, 17), 8800), 468 | Patient("Dan", "Forney", LocalDate.of(1985, 9, 13), 5400), 469 | Patient("Lauren", "Michaels", LocalDate.of(1975, 8, 21), 5000), 470 | Patient("Michael", "Erlich", LocalDate.of(1985, 12, 17), 4100), 471 | Patient("Jason", "Miles", LocalDate.of(1991, 11, 1), 3900), 472 | Patient("Rebekah", "Earley", LocalDate.of(1985, 2, 18), 4600), 473 | Patient("James", "Larson", LocalDate.of(1974, 4, 10), 5100), 474 | Patient("Dan", "Ulrech", LocalDate.of(1991, 7, 11), 6000), 475 | Patient("Heather", "Eisner", LocalDate.of(1994, 3, 6), 6000), 476 | Patient("Jasper", "Martin", LocalDate.of(1971, 7, 1), 6000) 477 | ) 478 | 479 | val mappedByName = mutableMapOf() 480 | 481 | for (patient in patients) { 482 | val key = NameKey(patient.firstName, patient.lastName) 483 | mappedByName[key] = patient 484 | } 485 | 486 | val retrievedPatient = mappedByName[NameKey("John", "Simone")] 487 | println(retrievedPatient) 488 | } 489 | ``` 490 | 491 | # 5.7: Factory Patterns with Companion Objects 492 | 493 | One thing that can be helpful is to save properties and functions at the "class" level, not the instance of each object. This can be achieved using objects and companion objects. 494 | 495 | For instance, we can have a `PatientDirectory` object embedded right inside the `Patient` class that could hold or access the entire database of patients. It can also have a helper function to retrieve a `Patient` for a given ID. 496 | 497 | 498 | ```kotlin 499 | package com.oreilly 500 | 501 | fun main(args: Array) { 502 | 503 | val retreivedPatient = Patient.PatientDirectory.forId(3) 504 | 505 | print(retreivedPatient) 506 | } 507 | 508 | data class Patient(val id: Int, 509 | val firstName: String, 510 | val lastName: String) { 511 | 512 | object PatientDirectory { 513 | 514 | val allPatients = listOf( 515 | Patient(1, "John", "Mooney"), 516 | Patient(2, "Sam", "Bella"), 517 | Patient(3, "Jake", "Blaine"), 518 | Patient(4, "Hannah", "Smith"), 519 | Patient(5, "John", "Mooney") 520 | ) 521 | 522 | fun forId(id: Int) = allPatients.find { it.id == id } 523 | } 524 | } 525 | ``` 526 | 527 | You can also eliminate having to explicitly call the embedded object by using a `companion object` instead. This is helpful if you don't intend on having different objects in a class. 528 | 529 | ```kotlin 530 | package com.oreilly 531 | 532 | 533 | 534 | 535 | fun main(args: Array) { 536 | 537 | val retreivedPatient = Patient.forId(3) 538 | 539 | print(retreivedPatient) 540 | } 541 | 542 | data class Patient(val id: Int, 543 | val firstName: String, 544 | val lastName: String) { 545 | 546 | companion object { 547 | 548 | val allPatients = listOf( 549 | Patient(1, "John", "Mooney"), 550 | Patient(2, "Sam", "Bella"), 551 | Patient(3, "Jake", "Blaine"), 552 | Patient(4, "Hannah", "Smith"), 553 | Patient(5, "John", "Mooney") 554 | ) 555 | 556 | fun forId(id: Int) = allPatients.find { it.id == id } 557 | } 558 | } 559 | ``` 560 | -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/oreilly_practical_data_modeling_for_production_with_kotlin.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasnield/oreilly_kotlin_for_data_science/5662a7d55d7f9bc1ad3d59a582ee9af0ce643cd8/2. Practical Data Modeling for Production with Kotlin/oreilly_practical_data_modeling_for_production_with_kotlin.pptx -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/resources/customer_orders.csv: -------------------------------------------------------------------------------- 1 | CUSTOMER_ORDER_ID,CUSTOMER_ID,ORDER_DATE,PRODUCT_ID,QUANTITY 2 | 1,9,2017-01-01,7,20 3 | 2,5,2017-01-01,15,110 4 | 3,3,2017-01-01,4,120 5 | 4,6,2017-01-01,7,200 6 | 5,2,2017-01-01,3,60 7 | 6,10,2017-01-01,15,140 8 | 7,7,2017-01-01,4,150 9 | 8,10,2017-01-01,6,10 10 | 10,3,2017-01-01,10,10 11 | 11,4,2017-01-01,7,110 12 | 12,5,2017-01-01,10,90 13 | 13,2,2017-01-01,15,10 14 | 14,9,2017-01-01,10,60 15 | 15,9,2017-01-01,15,120 16 | 16,2,2017-01-01,13,60 17 | 19,10,2017-01-01,8,150 18 | 20,3,2017-01-01,6,150 19 | 21,10,2017-01-02,3,60 20 | 22,4,2017-01-02,12,80 21 | 24,10,2017-01-02,13,60 22 | 25,10,2017-01-02,9,180 23 | 26,5,2017-01-02,7,120 24 | 27,6,2017-01-02,6,160 25 | 28,10,2017-01-02,7,70 26 | 30,6,2017-01-02,6,160 27 | 31,5,2017-01-02,7,30 28 | 32,8,2017-01-02,1,110 29 | 33,10,2017-01-02,5,100 30 | 34,10,2017-01-02,4,40 31 | 36,6,2017-01-02,6,10 32 | 37,4,2017-01-02,14,180 33 | 38,9,2017-01-02,12,180 34 | 40,8,2017-01-02,1,50 35 | 41,7,2017-01-02,15,40 36 | 42,4,2017-01-02,13,200 37 | 43,7,2017-01-02,1,120 38 | 44,10,2017-01-02,6,100 39 | 45,6,2017-01-02,12,180 40 | 46,8,2017-01-02,14,40 41 | 47,8,2017-01-02,8,110 42 | 48,6,2017-01-02,6,90 43 | 49,8,2017-01-02,3,150 44 | 50,8,2017-01-02,4,10 45 | 51,10,2017-01-03,7,90 46 | 52,4,2017-01-03,9,40 47 | 54,9,2017-01-03,11,90 48 | 55,9,2017-01-03,15,60 49 | 56,5,2017-01-03,14,30 50 | 58,5,2017-01-03,8,90 51 | 59,7,2017-01-03,14,110 52 | 60,8,2017-01-03,6,190 53 | 61,8,2017-01-03,9,10 54 | 63,4,2017-01-03,2,50 55 | 64,7,2017-01-03,5,70 56 | 65,5,2017-01-03,5,100 57 | 89,10,2017-01-05,14,200 58 | 90,7,2017-01-05,3,140 59 | 91,9,2017-01-05,15,110 60 | 92,8,2017-01-05,13,170 61 | 93,7,2017-01-05,8,110 62 | 94,10,2017-01-05,8,30 63 | 96,6,2017-01-05,6,30 64 | 97,9,2017-01-05,15,80 65 | 98,9,2017-01-05,3,190 66 | 100,7,2017-01-06,2,110 67 | 101,10,2017-01-06,11,110 68 | 105,7,2017-01-06,9,140 69 | 107,4,2017-01-06,14,50 70 | 109,7,2017-01-06,6,40 71 | 111,4,2017-01-06,14,100 72 | 112,9,2017-01-06,9,100 73 | 113,9,2017-01-06,15,90 74 | 114,4,2017-01-07,2,120 75 | 115,4,2017-01-07,6,190 76 | 116,3,2017-01-07,4,160 77 | 117,9,2017-01-07,8,90 78 | 120,5,2017-01-07,8,10 79 | 121,6,2017-01-07,14,160 80 | 124,9,2017-01-07,10,170 81 | 125,3,2017-01-07,11,170 82 | 127,5,2017-01-07,12,140 83 | 129,2,2017-01-07,13,40 84 | 146,10,2017-01-09,9,50 85 | 147,2,2017-01-09,4,160 86 | 148,2,2017-01-09,11,20 87 | 149,6,2017-01-09,3,180 88 | 152,10,2017-01-09,4,80 89 | 153,4,2017-01-09,5,170 90 | 154,10,2017-01-09,6,130 91 | 155,8,2017-01-09,1,180 92 | 156,7,2017-01-09,9,200 93 | 157,2,2017-01-09,13,40 94 | 158,8,2017-01-09,8,50 95 | 160,4,2017-01-09,6,150 96 | 161,10,2017-01-09,3,130 97 | 163,7,2017-01-10,7,140 98 | 165,4,2017-01-10,6,20 99 | 166,8,2017-01-10,1,160 100 | 168,8,2017-01-10,10,180 101 | 171,4,2017-01-10,6,110 102 | 172,4,2017-01-10,9,170 103 | 177,6,2017-01-10,2,20 104 | 180,8,2017-01-10,4,170 105 | 181,7,2017-01-10,6,70 106 | 182,6,2017-01-11,3,70 107 | 184,10,2017-01-11,5,100 108 | 185,2,2017-01-11,4,40 109 | 186,5,2017-01-11,9,160 110 | 187,2,2017-01-11,14,20 111 | 188,6,2017-01-11,2,190 112 | 189,2,2017-01-11,6,20 113 | 190,7,2017-01-11,15,90 114 | 191,10,2017-01-11,7,130 115 | 192,4,2017-01-11,10,180 116 | 193,9,2017-01-11,15,110 117 | 194,3,2017-01-11,5,90 118 | 196,8,2017-01-11,14,90 119 | 197,5,2017-01-11,7,20 120 | 198,10,2017-01-11,4,30 121 | 199,7,2017-01-11,5,70 122 | 200,2,2017-01-11,9,130 123 | 221,7,2017-01-13,7,140 124 | 222,7,2017-01-13,14,190 125 | 223,5,2017-01-13,14,30 126 | 225,8,2017-01-13,10,160 127 | 226,2,2017-01-13,14,190 128 | 228,7,2017-01-13,13,110 129 | 229,2,2017-01-13,1,40 130 | 230,9,2017-01-13,15,20 131 | 231,10,2017-01-13,7,10 132 | 232,3,2017-01-13,15,80 133 | 233,6,2017-01-13,2,110 134 | 234,6,2017-01-13,1,30 135 | 235,5,2017-01-13,9,120 136 | 236,8,2017-01-13,10,110 137 | 237,2,2017-01-13,4,90 138 | 238,4,2017-01-13,5,100 139 | 239,5,2017-01-13,1,120 140 | 241,2,2017-01-13,3,80 141 | 242,5,2017-01-13,3,140 142 | 243,3,2017-01-13,11,150 143 | 244,6,2017-01-13,12,70 144 | 245,6,2017-01-13,2,40 145 | 246,6,2017-01-13,6,190 146 | 250,6,2017-01-14,6,30 147 | 251,9,2017-01-14,6,160 148 | 252,9,2017-01-14,9,160 149 | 254,3,2017-01-14,11,190 150 | 255,3,2017-01-14,4,50 151 | 257,3,2017-01-14,5,200 152 | 258,6,2017-01-14,8,170 153 | 259,4,2017-01-14,12,110 154 | 261,8,2017-01-15,15,200 155 | 262,2,2017-01-15,11,10 156 | 264,6,2017-01-15,2,90 157 | 265,8,2017-01-15,12,20 158 | 266,4,2017-01-15,5,150 159 | 267,6,2017-01-15,7,90 160 | 268,2,2017-01-15,7,190 161 | 270,6,2017-01-15,9,20 162 | 271,6,2017-01-15,4,100 163 | 272,9,2017-01-15,6,130 164 | 273,2,2017-01-15,4,80 165 | 275,4,2017-01-15,8,190 166 | 276,2,2017-01-15,8,110 167 | 278,10,2017-01-15,13,20 168 | 281,6,2017-01-15,4,70 169 | 311,8,2017-01-17,7,60 170 | 312,7,2017-01-17,9,60 171 | 313,3,2017-01-17,8,50 172 | 315,5,2017-01-17,14,40 173 | 316,10,2017-01-17,14,50 174 | 317,2,2017-01-17,11,120 175 | 318,8,2017-01-17,2,90 176 | 319,9,2017-01-17,4,180 177 | 320,6,2017-01-17,7,150 178 | 321,3,2017-01-17,7,180 179 | 322,5,2017-01-17,3,30 180 | 323,5,2017-01-17,9,160 181 | 324,9,2017-01-17,12,120 182 | 325,2,2017-01-17,2,130 183 | 326,3,2017-01-17,14,100 184 | 327,2,2017-01-17,12,50 185 | 329,3,2017-01-17,7,160 186 | 330,6,2017-01-17,15,20 187 | 331,4,2017-01-17,14,160 188 | 332,4,2017-01-17,12,120 189 | 333,2,2017-01-17,1,30 190 | 335,9,2017-01-17,7,170 191 | 336,6,2017-01-17,14,110 192 | 337,8,2017-01-17,11,20 193 | 338,3,2017-01-17,1,50 194 | 339,6,2017-01-17,2,90 195 | 340,3,2017-01-17,8,130 196 | 341,9,2017-01-17,4,180 197 | 342,4,2017-01-17,14,100 198 | 343,7,2017-01-17,8,170 199 | 344,6,2017-01-17,12,150 200 | 345,8,2017-01-18,1,90 201 | 354,7,2017-01-18,12,170 202 | 356,5,2017-01-18,8,70 203 | 357,4,2017-01-18,15,130 204 | 358,4,2017-01-18,15,120 205 | 359,10,2017-01-18,15,10 206 | 363,4,2017-01-18,6,140 207 | 366,8,2017-01-18,13,150 208 | 367,8,2017-01-19,1,10 209 | 368,10,2017-01-19,7,150 210 | 369,2,2017-01-19,8,90 211 | 370,10,2017-01-19,8,170 212 | 371,4,2017-01-19,13,60 213 | 373,2,2017-01-19,9,190 214 | 374,10,2017-01-19,14,60 215 | 375,5,2017-01-19,13,90 216 | 376,6,2017-01-19,11,80 217 | 377,7,2017-01-19,5,170 218 | 378,2,2017-01-19,14,140 219 | 379,4,2017-01-19,7,10 220 | 380,6,2017-01-19,4,20 221 | 381,5,2017-01-19,1,40 222 | 382,9,2017-01-19,12,180 223 | 383,5,2017-01-19,4,140 224 | 384,2,2017-01-19,1,150 225 | 385,8,2017-01-19,14,190 226 | 386,3,2017-01-19,10,20 227 | 417,10,2017-01-21,5,60 228 | 419,4,2017-01-21,8,30 229 | 420,4,2017-01-21,11,20 230 | 421,2,2017-01-21,14,170 231 | 424,8,2017-01-21,15,60 232 | 425,2,2017-01-21,3,10 233 | 426,2,2017-01-21,4,70 234 | 427,9,2017-01-21,12,130 235 | 428,6,2017-01-21,4,40 236 | 429,5,2017-01-21,4,130 237 | 430,2,2017-01-21,15,170 238 | 431,6,2017-01-21,11,80 239 | 432,8,2017-01-21,1,160 240 | 433,2,2017-01-21,8,180 241 | 434,9,2017-01-21,1,40 242 | 435,10,2017-01-21,1,170 243 | 436,8,2017-01-22,8,80 244 | 438,3,2017-01-22,10,90 245 | 440,6,2017-01-22,5,50 246 | 441,7,2017-01-22,2,110 247 | 445,5,2017-01-22,4,150 248 | 446,9,2017-01-22,14,180 249 | 447,7,2017-01-22,15,180 250 | 448,8,2017-01-22,9,70 251 | 450,8,2017-01-22,15,20 252 | 451,4,2017-01-22,4,40 253 | 452,8,2017-01-22,9,140 254 | 453,6,2017-01-22,3,50 255 | 454,6,2017-01-23,5,10 256 | 455,5,2017-01-23,10,80 257 | 456,9,2017-01-23,11,90 258 | 457,3,2017-01-23,3,190 259 | 459,9,2017-01-23,2,180 260 | 460,4,2017-01-23,10,150 261 | 461,7,2017-01-23,15,160 262 | 462,3,2017-01-23,12,190 263 | 463,9,2017-01-23,4,150 264 | 464,8,2017-01-23,12,100 265 | 465,3,2017-01-23,10,150 266 | 466,10,2017-01-23,10,130 267 | 467,8,2017-01-23,3,80 268 | 468,3,2017-01-23,10,120 269 | 469,8,2017-01-23,6,90 270 | 470,7,2017-01-23,15,140 271 | 471,10,2017-01-23,10,110 272 | 472,3,2017-01-23,8,90 273 | 495,7,2017-01-25,9,200 274 | 496,9,2017-01-25,6,20 275 | 497,10,2017-01-25,11,110 276 | 498,4,2017-01-25,10,170 277 | 500,10,2017-01-25,11,130 278 | 502,6,2017-01-25,15,120 279 | 503,8,2017-01-25,12,140 280 | 504,2,2017-01-25,14,160 281 | 506,4,2017-01-25,14,80 282 | 507,6,2017-01-25,14,110 283 | 508,10,2017-01-25,7,150 284 | 509,8,2017-01-25,13,170 285 | 510,6,2017-01-25,12,30 286 | 511,8,2017-01-25,3,120 287 | 512,7,2017-01-25,13,100 288 | 513,9,2017-01-25,14,180 289 | 514,3,2017-01-26,2,190 290 | 515,5,2017-01-26,9,180 291 | 516,5,2017-01-26,11,80 292 | 518,10,2017-01-26,4,190 293 | 519,10,2017-01-26,6,70 294 | 520,9,2017-01-26,6,60 295 | 521,4,2017-01-26,10,170 296 | 522,9,2017-01-26,1,130 297 | 523,10,2017-01-26,8,200 298 | 524,4,2017-01-26,2,110 299 | 525,8,2017-01-26,6,80 300 | 526,4,2017-01-26,15,170 301 | 528,3,2017-01-26,8,30 302 | 529,5,2017-01-26,1,110 303 | 531,5,2017-01-26,5,40 304 | 532,8,2017-01-26,4,150 305 | 533,9,2017-01-26,7,70 306 | 534,6,2017-01-26,14,150 307 | 535,5,2017-01-26,9,160 308 | 536,4,2017-01-27,5,10 309 | 537,5,2017-01-27,7,160 310 | 538,7,2017-01-27,4,60 311 | 541,8,2017-01-27,5,40 312 | 545,8,2017-01-27,8,180 313 | 546,4,2017-01-27,7,160 314 | 548,7,2017-01-27,5,80 315 | 549,5,2017-01-27,1,50 316 | 552,5,2017-01-27,13,90 317 | 554,8,2017-01-27,3,130 318 | 573,3,2017-01-29,11,60 319 | 574,8,2017-01-29,13,190 320 | 575,9,2017-01-29,5,200 321 | 576,4,2017-01-29,10,200 322 | 577,8,2017-01-29,12,30 323 | 578,3,2017-01-29,11,160 324 | 580,8,2017-01-29,7,180 325 | 581,10,2017-01-29,1,30 326 | 582,8,2017-01-29,13,70 327 | 583,9,2017-01-29,11,140 328 | 584,7,2017-01-29,12,160 329 | 585,10,2017-01-29,9,90 330 | 586,3,2017-01-29,8,100 331 | 587,7,2017-01-29,14,90 332 | 588,3,2017-01-29,6,50 333 | 589,2,2017-01-29,3,50 334 | 590,4,2017-01-29,4,170 335 | 591,3,2017-01-29,3,20 336 | 592,6,2017-01-29,10,120 337 | 594,4,2017-01-29,11,10 338 | 596,8,2017-01-29,6,50 339 | 597,9,2017-01-29,13,170 340 | 598,2,2017-01-29,5,140 341 | 599,10,2017-01-29,3,80 342 | 600,3,2017-01-29,7,60 343 | 601,10,2017-01-29,3,50 344 | 603,9,2017-01-30,10,10 345 | 604,8,2017-01-30,6,50 346 | 606,9,2017-01-30,7,190 347 | 609,8,2017-01-30,14,160 348 | 612,4,2017-01-30,5,180 349 | 613,9,2017-01-30,8,170 350 | 614,9,2017-01-30,10,40 351 | 617,9,2017-01-30,9,160 352 | 620,8,2017-01-30,15,130 353 | 622,9,2017-01-30,3,190 354 | 625,7,2017-01-31,11,180 355 | 626,10,2017-01-31,4,60 356 | 627,2,2017-01-31,8,10 357 | 628,8,2017-01-31,13,90 358 | 629,7,2017-01-31,9,70 359 | 630,10,2017-01-31,11,110 360 | 631,5,2017-01-31,14,90 361 | 632,3,2017-01-31,14,180 362 | 634,6,2017-01-31,2,70 363 | 635,9,2017-01-31,11,110 364 | 636,4,2017-01-31,11,20 365 | 637,4,2017-01-31,10,80 366 | 638,10,2017-01-31,7,100 367 | 639,6,2017-01-31,4,10 368 | 640,6,2017-02-01,3,40 369 | 642,2,2017-02-01,4,140 370 | 643,10,2017-02-01,14,150 371 | 644,8,2017-02-01,6,70 372 | 645,9,2017-02-01,14,70 373 | 646,4,2017-02-01,6,90 374 | 647,8,2017-02-01,2,130 375 | 648,3,2017-02-01,12,190 376 | 649,8,2017-02-01,5,50 377 | 650,5,2017-02-01,10,20 378 | 651,9,2017-02-01,10,160 379 | 652,7,2017-02-01,13,110 380 | 653,4,2017-02-01,4,110 381 | 654,8,2017-02-01,8,160 382 | 655,6,2017-02-01,5,50 383 | 656,8,2017-02-01,5,170 384 | 657,4,2017-02-01,11,80 385 | 658,7,2017-02-01,8,140 386 | 659,10,2017-02-01,13,160 387 | 660,5,2017-02-01,3,160 388 | 661,4,2017-02-01,7,30 389 | 662,9,2017-02-02,15,70 390 | 663,8,2017-02-02,1,60 391 | 664,10,2017-02-02,9,120 392 | 665,3,2017-02-02,15,40 393 | 666,9,2017-02-02,13,110 394 | 667,6,2017-02-02,11,170 395 | 670,6,2017-02-02,15,60 396 | 671,7,2017-02-02,7,70 397 | 672,10,2017-02-02,3,20 398 | 673,7,2017-02-02,2,80 399 | 674,6,2017-02-02,15,130 400 | 675,3,2017-02-02,2,100 401 | 676,5,2017-02-02,2,100 402 | 678,5,2017-02-02,4,40 403 | 679,7,2017-02-02,1,110 404 | 680,6,2017-02-02,9,120 405 | 681,10,2017-02-02,2,80 406 | 682,5,2017-02-03,11,120 407 | 683,4,2017-02-03,1,130 408 | 685,10,2017-02-03,8,120 409 | 686,8,2017-02-03,8,70 410 | 688,9,2017-02-03,8,170 411 | 689,5,2017-02-03,2,90 412 | 690,7,2017-02-03,5,10 413 | 691,5,2017-02-03,6,80 414 | 692,9,2017-02-03,10,120 415 | 693,10,2017-02-03,11,110 416 | 695,7,2017-02-03,6,100 417 | 696,9,2017-02-03,6,170 418 | 697,10,2017-02-03,11,40 419 | 721,7,2017-02-05,8,190 420 | 722,9,2017-02-05,9,140 421 | 723,4,2017-02-05,10,200 422 | 724,2,2017-02-05,15,100 423 | 725,3,2017-02-05,15,150 424 | 726,3,2017-02-05,11,80 425 | 727,10,2017-02-05,13,150 426 | 728,2,2017-02-05,2,150 427 | 731,7,2017-02-05,12,20 428 | 733,4,2017-02-05,15,90 429 | 734,3,2017-02-05,3,100 430 | 735,7,2017-02-05,1,80 431 | 736,2,2017-02-05,6,80 432 | 738,3,2017-02-05,11,100 433 | 740,8,2017-02-05,6,60 434 | 741,4,2017-02-05,5,70 435 | 742,8,2017-02-05,15,110 436 | 743,6,2017-02-05,5,60 437 | 744,4,2017-02-05,4,140 438 | 745,10,2017-02-05,10,80 439 | 746,4,2017-02-05,12,20 440 | 747,10,2017-02-05,2,140 441 | 748,2,2017-02-05,11,100 442 | 749,4,2017-02-05,5,10 443 | 751,7,2017-02-05,7,20 444 | 752,7,2017-02-05,12,50 445 | 753,7,2017-02-05,5,50 446 | 754,6,2017-02-05,9,190 447 | 755,9,2017-02-05,8,60 448 | 756,9,2017-02-06,10,120 449 | 758,8,2017-02-06,9,40 450 | 760,4,2017-02-06,11,60 451 | 762,7,2017-02-06,9,80 452 | 764,7,2017-02-06,10,90 453 | 766,5,2017-02-06,4,90 454 | 769,8,2017-02-06,13,30 455 | 771,9,2017-02-06,15,100 456 | 772,5,2017-02-06,6,190 457 | 774,8,2017-02-06,3,100 458 | 778,4,2017-02-06,14,90 459 | 779,6,2017-02-07,7,140 460 | 780,4,2017-02-07,6,110 461 | 781,10,2017-02-07,5,150 462 | 782,9,2017-02-07,7,30 463 | 783,5,2017-02-07,6,190 464 | 784,3,2017-02-07,1,10 465 | 785,2,2017-02-07,10,100 466 | 786,2,2017-02-07,15,110 467 | 787,10,2017-02-07,15,90 468 | 788,6,2017-02-07,11,180 469 | 789,8,2017-02-07,5,170 470 | 790,8,2017-02-07,12,110 471 | 791,8,2017-02-07,11,140 472 | 792,3,2017-02-07,14,130 473 | 793,9,2017-02-07,7,20 474 | 794,10,2017-02-07,2,160 475 | 821,6,2017-02-09,2,120 476 | 822,10,2017-02-09,1,50 477 | 823,8,2017-02-09,10,30 478 | 824,6,2017-02-09,13,60 479 | 825,6,2017-02-09,10,160 480 | 826,2,2017-02-09,8,150 481 | 827,6,2017-02-09,12,160 482 | 828,8,2017-02-09,7,50 483 | 829,6,2017-02-09,13,120 484 | 831,4,2017-02-09,8,180 485 | 832,10,2017-02-09,6,140 486 | 833,6,2017-02-09,3,90 487 | 834,4,2017-02-09,3,110 488 | 835,4,2017-02-09,7,10 489 | 836,4,2017-02-09,9,180 490 | 838,5,2017-02-09,14,80 491 | 842,7,2017-02-09,3,160 492 | 844,7,2017-02-09,3,200 493 | 845,4,2017-02-09,11,10 494 | 847,4,2017-02-10,1,40 495 | 848,7,2017-02-10,7,40 496 | 849,6,2017-02-10,12,10 497 | 850,8,2017-02-10,11,120 498 | 851,4,2017-02-10,3,90 499 | 852,7,2017-02-10,14,140 500 | 853,4,2017-02-10,13,140 501 | 855,8,2017-02-10,7,120 502 | 856,6,2017-02-10,15,50 503 | 857,4,2017-02-10,6,30 504 | 858,4,2017-02-10,8,90 505 | 859,4,2017-02-10,11,80 506 | 860,8,2017-02-10,10,100 507 | 861,8,2017-02-10,12,100 508 | 862,7,2017-02-11,3,60 509 | 863,7,2017-02-11,6,60 510 | 864,5,2017-02-11,13,160 511 | 865,4,2017-02-11,7,120 512 | 866,6,2017-02-11,7,170 513 | 867,5,2017-02-11,14,170 514 | 868,4,2017-02-11,13,170 515 | 869,5,2017-02-11,1,10 516 | 870,10,2017-02-11,2,40 517 | 871,2,2017-02-11,10,190 518 | 872,4,2017-02-11,13,170 519 | 873,8,2017-02-11,11,140 520 | 874,2,2017-02-11,9,170 521 | 876,2,2017-02-11,6,40 522 | 878,2,2017-02-11,4,200 523 | 879,9,2017-02-11,10,30 524 | 880,10,2017-02-11,10,80 525 | 881,5,2017-02-11,9,60 526 | 905,7,2017-02-13,8,70 527 | 906,7,2017-02-13,10,190 528 | 907,2,2017-02-13,14,50 529 | 908,4,2017-02-13,15,70 530 | 909,6,2017-02-13,12,120 531 | 910,7,2017-02-13,5,50 532 | 911,8,2017-02-13,5,180 533 | 912,2,2017-02-13,15,160 534 | 913,7,2017-02-13,8,60 535 | 914,3,2017-02-13,10,120 536 | 915,4,2017-02-13,12,70 537 | 916,2,2017-02-13,8,70 538 | 917,5,2017-02-13,9,20 539 | 918,4,2017-02-13,2,120 540 | 920,3,2017-02-13,5,190 541 | 921,2,2017-02-13,2,160 542 | 922,9,2017-02-13,3,150 543 | 923,2,2017-02-13,3,70 544 | 924,7,2017-02-13,9,80 545 | 925,4,2017-02-13,15,140 546 | 926,9,2017-02-13,3,100 547 | 927,3,2017-02-13,15,140 548 | 928,3,2017-02-14,11,90 549 | 931,6,2017-02-14,15,180 550 | 932,9,2017-02-14,3,100 551 | 933,5,2017-02-14,13,190 552 | 934,6,2017-02-14,9,140 553 | 935,9,2017-02-14,11,110 554 | 939,3,2017-02-14,6,110 555 | 940,8,2017-02-14,1,90 556 | 941,8,2017-02-14,7,110 557 | 942,6,2017-02-14,4,140 558 | 943,4,2017-02-14,9,160 559 | 944,5,2017-02-14,8,100 560 | 945,10,2017-02-14,9,60 561 | 946,4,2017-02-14,9,200 562 | 947,6,2017-02-14,11,30 563 | 948,6,2017-02-14,3,160 564 | 949,3,2017-02-14,7,60 565 | 950,10,2017-02-14,14,170 566 | 951,6,2017-02-14,14,140 567 | 952,3,2017-02-14,1,140 568 | 954,8,2017-02-14,13,110 569 | 956,9,2017-02-15,1,90 570 | 957,8,2017-02-15,1,100 571 | 958,9,2017-02-15,15,130 572 | 959,4,2017-02-15,13,200 573 | 961,6,2017-02-15,3,40 574 | 963,10,2017-02-15,7,60 575 | 964,9,2017-02-15,9,180 576 | 965,4,2017-02-15,6,90 577 | 967,8,2017-02-15,5,160 578 | 969,2,2017-02-15,12,190 579 | 970,8,2017-02-15,2,50 580 | 971,7,2017-02-15,9,130 581 | 998,8,2017-02-17,4,60 582 | 999,2,2017-02-17,14,200 583 | 1000,5,2017-02-17,14,160 584 | 1001,5,2017-02-17,10,90 585 | 1002,10,2017-02-17,15,70 586 | 1003,10,2017-02-17,12,80 587 | 1004,10,2017-02-17,4,60 588 | 1005,10,2017-02-17,12,110 589 | 1006,3,2017-02-17,8,20 590 | 1007,3,2017-02-17,15,20 591 | 1008,9,2017-02-17,12,140 592 | 1009,2,2017-02-17,11,160 593 | 1010,5,2017-02-17,6,70 594 | 1011,7,2017-02-17,6,70 595 | 1012,6,2017-02-17,14,150 596 | 1013,2,2017-02-17,15,30 597 | 1014,2,2017-02-17,12,130 598 | 1015,3,2017-02-17,3,20 599 | 1016,5,2017-02-17,8,110 600 | 1017,5,2017-02-18,15,80 601 | 1020,10,2017-02-18,11,50 602 | 1021,10,2017-02-18,4,130 603 | 1023,8,2017-02-18,9,80 604 | 1024,5,2017-02-18,14,170 605 | 1026,5,2017-02-18,4,60 606 | 1027,7,2017-02-18,11,190 607 | 1029,5,2017-02-18,13,50 608 | 1030,8,2017-02-18,2,50 609 | 1031,10,2017-02-18,9,70 610 | 1033,6,2017-02-19,8,40 611 | 1034,7,2017-02-19,15,60 612 | 1035,2,2017-02-19,14,160 613 | 1036,6,2017-02-19,15,120 614 | 1037,4,2017-02-19,6,70 615 | 1038,2,2017-02-19,6,60 616 | 1040,3,2017-02-19,8,170 617 | 1042,5,2017-02-19,4,180 618 | 1043,9,2017-02-19,12,160 619 | 1044,4,2017-02-19,12,150 620 | 1045,8,2017-02-19,15,50 621 | 1046,2,2017-02-19,10,20 622 | 1047,5,2017-02-19,15,130 623 | 1069,4,2017-02-21,5,10 624 | 1071,10,2017-02-21,5,150 625 | 1072,10,2017-02-21,5,170 626 | 1074,8,2017-02-21,2,180 627 | 1075,2,2017-02-21,2,60 628 | 1077,9,2017-02-21,15,180 629 | 1078,8,2017-02-21,4,110 630 | 1080,2,2017-02-21,3,90 631 | 1081,5,2017-02-21,13,170 632 | 1082,8,2017-02-21,5,80 633 | 1083,8,2017-02-22,12,90 634 | 1085,9,2017-02-22,14,90 635 | 1086,5,2017-02-22,13,80 636 | 1087,8,2017-02-22,11,10 637 | 1089,3,2017-02-22,8,170 638 | 1090,10,2017-02-22,9,60 639 | 1091,7,2017-02-22,3,110 640 | 1092,4,2017-02-22,5,160 641 | 1094,8,2017-02-22,4,190 642 | 1095,6,2017-02-22,10,20 643 | 1097,8,2017-02-22,10,80 644 | 1098,6,2017-02-22,5,190 645 | 1099,8,2017-02-22,4,110 646 | 1100,10,2017-02-23,2,60 647 | 1101,9,2017-02-23,2,100 648 | 1102,3,2017-02-23,6,120 649 | 1103,5,2017-02-23,3,80 650 | 1104,2,2017-02-23,11,160 651 | 1105,4,2017-02-23,14,100 652 | 1108,6,2017-02-23,3,80 653 | 1109,6,2017-02-23,2,90 654 | 1110,6,2017-02-23,4,150 655 | 1111,8,2017-02-23,10,50 656 | 1112,3,2017-02-23,1,110 657 | 1113,10,2017-02-23,3,90 658 | 1114,3,2017-02-23,1,160 659 | 1116,6,2017-02-23,9,180 660 | 1117,10,2017-02-23,6,200 661 | 1118,2,2017-02-23,5,10 662 | 1119,7,2017-02-23,10,70 663 | 1120,2,2017-02-23,8,180 664 | 1146,6,2017-02-25,12,90 665 | 1147,2,2017-02-25,7,10 666 | 1148,9,2017-02-25,4,140 667 | 1149,6,2017-02-25,4,190 668 | 1150,9,2017-02-25,12,50 669 | 1151,6,2017-02-25,13,160 670 | 1152,9,2017-02-25,1,150 671 | 1153,3,2017-02-25,4,60 672 | 1155,2,2017-02-25,15,140 673 | 1156,4,2017-02-25,7,130 674 | 1157,10,2017-02-25,3,60 675 | 1158,2,2017-02-25,4,10 676 | 1160,8,2017-02-25,10,40 677 | 1163,4,2017-02-25,6,20 678 | 1164,7,2017-02-25,11,30 679 | 1165,2,2017-02-25,8,200 680 | 1167,10,2017-02-25,15,150 681 | 1168,6,2017-02-25,3,60 682 | 1169,7,2017-02-25,13,70 683 | 1170,4,2017-02-26,9,10 684 | 1171,6,2017-02-26,8,160 685 | 1172,7,2017-02-26,12,110 686 | 1173,10,2017-02-26,13,180 687 | 1174,6,2017-02-26,9,180 688 | 1175,5,2017-02-26,9,200 689 | 1177,6,2017-02-26,6,110 690 | 1179,9,2017-02-26,5,190 691 | 1180,10,2017-02-26,3,30 692 | 1181,8,2017-02-26,1,20 693 | 1182,5,2017-02-26,13,60 694 | 1183,3,2017-02-26,4,40 695 | 1184,7,2017-02-26,6,100 696 | 1185,3,2017-02-26,12,30 697 | 1187,8,2017-02-26,13,120 698 | 1188,9,2017-02-26,7,200 699 | 1189,7,2017-02-26,6,40 700 | 1190,5,2017-02-26,9,150 701 | 1192,4,2017-02-27,6,190 702 | 1193,10,2017-02-27,1,90 703 | 1194,6,2017-02-27,8,170 704 | 1195,7,2017-02-27,1,130 705 | 1196,5,2017-02-27,4,160 706 | 1197,2,2017-02-27,6,40 707 | 1200,10,2017-02-27,1,120 708 | 1202,8,2017-02-27,1,20 709 | 1205,6,2017-02-27,14,70 710 | 1206,7,2017-02-27,2,190 711 | 1207,10,2017-02-27,1,40 712 | 1208,6,2017-02-27,7,70 713 | 1209,8,2017-02-27,13,130 714 | 1211,4,2017-02-27,8,200 715 | 1212,4,2017-02-27,9,100 716 | 1213,5,2017-02-27,5,80 717 | 1214,7,2017-02-27,2,110 718 | 1247,9,2017-03-01,2,120 719 | 1248,2,2017-03-01,9,200 720 | 1250,4,2017-03-01,6,140 721 | 1251,3,2017-03-01,7,130 722 | 1252,4,2017-03-01,4,40 723 | 1253,7,2017-03-01,5,30 724 | 1254,8,2017-03-01,15,100 725 | 1255,6,2017-03-01,13,190 726 | 1256,7,2017-03-01,13,120 727 | 1257,9,2017-03-01,5,50 728 | 1259,2,2017-03-01,2,190 729 | 1260,3,2017-03-01,9,20 730 | 1261,4,2017-03-01,3,60 731 | 1262,10,2017-03-01,7,10 732 | 1263,4,2017-03-01,5,40 733 | 1264,10,2017-03-01,5,40 734 | 1265,6,2017-03-01,3,200 735 | 1266,7,2017-03-01,3,90 736 | 1267,6,2017-03-01,14,160 737 | 1268,6,2017-03-01,13,80 738 | 1269,4,2017-03-01,6,140 739 | 1270,7,2017-03-01,15,180 740 | 1272,7,2017-03-01,2,60 741 | 1274,5,2017-03-01,8,50 742 | 1275,5,2017-03-01,13,70 743 | 1276,3,2017-03-01,6,20 744 | 1277,7,2017-03-01,12,10 745 | 1278,7,2017-03-01,6,160 746 | 1279,4,2017-03-01,3,40 747 | 1280,6,2017-03-01,15,20 748 | 1281,8,2017-03-01,6,110 749 | 1282,7,2017-03-01,9,10 750 | 1283,6,2017-03-01,5,120 751 | 1284,3,2017-03-01,14,60 752 | 1285,9,2017-03-01,7,10 753 | 1286,10,2017-03-01,12,40 754 | 1287,8,2017-03-01,9,90 755 | 1288,6,2017-03-01,11,70 756 | 1289,4,2017-03-01,3,90 757 | 1290,2,2017-03-01,2,200 758 | 1291,3,2017-03-01,8,90 759 | 1292,10,2017-03-01,2,50 760 | 1293,9,2017-03-01,14,150 761 | 1294,9,2017-03-02,10,110 762 | 1295,4,2017-03-02,11,100 763 | 1296,7,2017-03-02,11,20 764 | 1297,8,2017-03-02,12,30 765 | 1298,9,2017-03-02,15,190 766 | 1300,7,2017-03-02,14,80 767 | 1301,10,2017-03-02,9,80 768 | 1303,9,2017-03-02,6,140 769 | 1305,7,2017-03-02,3,10 770 | 1306,7,2017-03-02,4,140 771 | 1307,7,2017-03-02,3,20 772 | 1308,3,2017-03-02,9,80 773 | 1309,4,2017-03-02,10,40 774 | 1310,7,2017-03-02,11,80 775 | 1312,4,2017-03-02,11,170 776 | 1313,8,2017-03-02,9,190 777 | 1314,7,2017-03-02,4,130 778 | 1315,10,2017-03-02,2,150 779 | 1318,6,2017-03-02,7,120 780 | 1319,3,2017-03-02,7,40 781 | 1321,4,2017-03-02,8,80 782 | 1322,3,2017-03-02,11,120 783 | 1323,10,2017-03-02,12,30 784 | 1324,7,2017-03-02,15,70 785 | 1325,5,2017-03-02,11,190 786 | 1326,7,2017-03-02,9,180 787 | 1327,4,2017-03-02,11,40 788 | 1328,5,2017-03-02,11,30 789 | 1329,10,2017-03-02,5,110 790 | 1331,7,2017-03-02,2,40 791 | 1332,7,2017-03-02,13,180 792 | 1334,5,2017-03-02,3,40 793 | 1335,9,2017-03-02,4,70 794 | 1336,7,2017-03-02,13,120 795 | 1337,4,2017-03-03,1,10 796 | 1338,9,2017-03-03,10,150 797 | 1340,5,2017-03-03,13,120 798 | 1341,10,2017-03-03,9,70 799 | 1343,4,2017-03-03,6,200 800 | 1344,7,2017-03-03,13,110 801 | 1345,2,2017-03-03,2,100 802 | 1347,8,2017-03-03,8,190 803 | 1348,9,2017-03-03,1,20 804 | 1350,9,2017-03-03,14,130 805 | 1351,2,2017-03-03,8,100 806 | 1352,5,2017-03-03,3,160 807 | 1353,5,2017-03-03,8,170 808 | 1355,8,2017-03-03,15,80 809 | 1356,6,2017-03-03,10,10 810 | 1357,2,2017-03-03,3,200 811 | 1358,9,2017-03-03,10,160 812 | 1359,7,2017-03-03,3,180 813 | 1361,6,2017-03-03,11,140 814 | 1362,10,2017-03-03,1,110 815 | 1363,10,2017-03-03,13,190 816 | 1364,7,2017-03-03,13,160 817 | 1366,9,2017-03-03,12,40 818 | 1369,8,2017-03-03,13,130 819 | 1371,9,2017-03-03,5,170 820 | 1372,4,2017-03-03,3,130 821 | 1373,4,2017-03-03,10,50 822 | 1374,7,2017-03-03,15,200 823 | 1375,5,2017-03-03,13,150 824 | 1376,5,2017-03-03,14,150 825 | 1377,8,2017-03-03,8,70 826 | 1379,8,2017-03-03,5,90 827 | 1380,6,2017-03-03,1,60 828 | 1398,3,2017-03-05,4,70 829 | 1400,4,2017-03-05,3,100 830 | 1402,7,2017-03-05,9,170 831 | 1404,8,2017-03-05,1,120 832 | 1408,4,2017-03-05,10,110 833 | 1409,4,2017-03-05,11,40 834 | 1410,6,2017-03-05,9,120 835 | 1411,5,2017-03-05,12,50 836 | 1412,4,2017-03-05,13,80 837 | 1413,8,2017-03-05,14,160 838 | 1414,4,2017-03-05,12,200 839 | 1416,4,2017-03-05,4,100 840 | 1418,6,2017-03-05,6,160 841 | 1423,4,2017-03-05,6,10 842 | 1424,8,2017-03-05,3,190 843 | 1425,10,2017-03-05,13,170 844 | 1426,8,2017-03-05,3,160 845 | 1428,5,2017-03-06,9,100 846 | 1429,5,2017-03-06,12,140 847 | 1431,9,2017-03-06,12,150 848 | 1433,8,2017-03-06,6,120 849 | 1434,7,2017-03-06,7,90 850 | 1436,5,2017-03-06,12,40 851 | 1437,8,2017-03-06,14,20 852 | 1438,7,2017-03-06,9,180 853 | 1439,5,2017-03-06,13,120 854 | 1440,7,2017-03-06,4,90 855 | 1444,4,2017-03-06,11,70 856 | 1445,4,2017-03-06,11,130 857 | 1448,4,2017-03-06,1,40 858 | 1449,9,2017-03-07,9,90 859 | 1450,5,2017-03-07,12,160 860 | 1451,10,2017-03-07,1,100 861 | 1452,10,2017-03-07,14,130 862 | 1454,2,2017-03-07,1,90 863 | 1455,6,2017-03-07,1,140 864 | 1456,4,2017-03-07,7,60 865 | 1458,9,2017-03-07,11,120 866 | 1459,10,2017-03-07,4,80 867 | 1460,2,2017-03-07,11,50 868 | 1461,3,2017-03-07,15,50 869 | 1463,2,2017-03-07,11,30 870 | 1465,2,2017-03-07,1,70 871 | 1466,10,2017-03-07,13,50 872 | 1467,4,2017-03-07,10,100 873 | 1468,10,2017-03-07,11,130 874 | 1469,9,2017-03-07,1,130 875 | 1470,9,2017-03-07,3,40 876 | 1472,4,2017-03-07,6,40 877 | 1473,6,2017-03-07,14,120 878 | 1474,8,2017-03-07,8,90 879 | 1475,9,2017-03-07,12,30 880 | 1476,3,2017-03-07,10,30 881 | 1477,2,2017-03-07,10,150 882 | 1478,9,2017-03-07,12,140 883 | 1505,2,2017-03-09,13,90 884 | 1507,5,2017-03-09,14,200 885 | 1508,7,2017-03-09,6,180 886 | 1509,7,2017-03-09,10,110 887 | 1511,4,2017-03-09,7,150 888 | 1512,7,2017-03-09,8,100 889 | 1514,6,2017-03-09,14,180 890 | 1515,2,2017-03-09,6,10 891 | 1516,7,2017-03-09,12,40 892 | 1518,2,2017-03-09,13,80 893 | 1519,7,2017-03-09,1,50 894 | 1520,5,2017-03-09,5,170 895 | 1522,8,2017-03-09,4,80 896 | 1523,7,2017-03-09,13,70 897 | 1524,6,2017-03-09,3,40 898 | 1525,10,2017-03-09,5,170 899 | 1527,4,2017-03-10,8,80 900 | 1528,3,2017-03-10,14,20 901 | 1530,9,2017-03-10,10,50 902 | 1531,8,2017-03-10,4,90 903 | 1533,8,2017-03-10,4,130 904 | 1534,4,2017-03-10,2,180 905 | 1535,7,2017-03-10,15,130 906 | 1536,3,2017-03-10,4,140 907 | 1540,6,2017-03-10,4,150 908 | 1541,6,2017-03-10,3,80 909 | 1543,6,2017-03-10,9,20 910 | 1544,3,2017-03-10,7,10 911 | 1546,5,2017-03-11,5,60 912 | 1547,4,2017-03-11,12,60 913 | 1548,7,2017-03-11,13,140 914 | 1549,3,2017-03-11,7,170 915 | 1550,3,2017-03-11,13,10 916 | 1551,4,2017-03-11,4,110 917 | 1552,10,2017-03-11,14,10 918 | 1553,9,2017-03-11,15,130 919 | 1554,5,2017-03-11,1,150 920 | 1555,8,2017-03-11,8,200 921 | 1556,8,2017-03-11,2,100 922 | 1557,5,2017-03-11,2,100 923 | 1558,4,2017-03-11,1,40 924 | 1559,6,2017-03-11,14,80 925 | 1560,4,2017-03-11,8,60 926 | 1561,6,2017-03-11,5,10 927 | 1563,10,2017-03-11,10,170 928 | 1564,2,2017-03-11,12,170 929 | 1565,6,2017-03-11,1,40 930 | 1566,9,2017-03-11,7,40 931 | 1583,2,2017-03-13,3,80 932 | 1584,4,2017-03-13,10,120 933 | 1585,7,2017-03-13,3,100 934 | 1586,9,2017-03-13,6,80 935 | 1587,4,2017-03-13,15,60 936 | 1588,3,2017-03-13,6,50 937 | 1589,7,2017-03-13,7,80 938 | 1590,9,2017-03-13,11,70 939 | 1591,3,2017-03-13,6,140 940 | 1593,8,2017-03-13,1,180 941 | 1594,3,2017-03-13,15,180 942 | 1595,7,2017-03-13,12,120 943 | 1596,3,2017-03-13,11,140 944 | 1597,3,2017-03-13,2,10 945 | 1598,4,2017-03-13,9,170 946 | 1599,4,2017-03-13,2,150 947 | 1600,2,2017-03-13,13,70 948 | 1601,5,2017-03-13,6,140 949 | 1602,6,2017-03-13,3,160 950 | 1603,3,2017-03-14,11,150 951 | 1605,3,2017-03-14,4,80 952 | 1606,8,2017-03-14,1,40 953 | 1608,8,2017-03-14,11,10 954 | 1609,8,2017-03-14,5,70 955 | 1610,9,2017-03-14,1,160 956 | 1612,5,2017-03-14,9,30 957 | 1613,8,2017-03-14,4,90 958 | 1615,9,2017-03-14,12,110 959 | 1616,6,2017-03-14,1,170 960 | 1617,3,2017-03-14,8,120 961 | 1619,5,2017-03-14,3,50 962 | 1620,9,2017-03-14,15,80 963 | 1621,10,2017-03-14,5,100 964 | 1622,4,2017-03-14,9,150 965 | 1624,7,2017-03-15,10,100 966 | 1625,8,2017-03-15,15,170 967 | 1628,4,2017-03-15,2,80 968 | 1629,2,2017-03-15,6,30 969 | 1630,4,2017-03-15,5,120 970 | 1632,8,2017-03-15,11,90 971 | 1633,10,2017-03-15,10,60 972 | 1634,2,2017-03-15,14,130 973 | 1635,9,2017-03-15,6,200 974 | 1636,6,2017-03-15,14,160 975 | 1638,2,2017-03-15,11,190 976 | 1640,8,2017-03-15,8,70 977 | 1662,5,2017-03-17,14,160 978 | 1663,2,2017-03-17,9,160 979 | 1664,7,2017-03-17,8,40 980 | 1665,6,2017-03-17,1,40 981 | 1666,3,2017-03-17,6,30 982 | 1667,4,2017-03-17,7,80 983 | 1668,4,2017-03-17,13,80 984 | 1669,5,2017-03-17,3,190 985 | 1670,2,2017-03-17,14,20 986 | 1671,10,2017-03-17,7,100 987 | 1672,5,2017-03-17,13,100 988 | 1673,2,2017-03-17,4,130 989 | 1674,8,2017-03-17,10,90 990 | 1675,3,2017-03-17,2,180 991 | 1676,3,2017-03-17,15,120 992 | 1677,4,2017-03-17,10,10 993 | 1678,3,2017-03-17,6,90 994 | 1679,4,2017-03-17,6,170 995 | 1680,2,2017-03-17,12,180 996 | 1681,6,2017-03-17,6,130 997 | 1682,7,2017-03-17,13,120 998 | 1683,6,2017-03-17,10,100 999 | 1684,10,2017-03-17,14,80 1000 | 1685,2,2017-03-17,2,50 1001 | 1686,3,2017-03-17,11,110 1002 | 1687,7,2017-03-18,8,140 1003 | 1688,5,2017-03-18,6,180 1004 | 1690,10,2017-03-18,5,70 1005 | 1692,7,2017-03-18,14,100 1006 | 1694,5,2017-03-18,9,80 1007 | 1696,5,2017-03-18,10,40 1008 | 1697,4,2017-03-18,10,150 1009 | 1700,4,2017-03-18,5,30 1010 | 1701,5,2017-03-18,3,190 1011 | 1703,8,2017-03-18,12,50 1012 | 1705,10,2017-03-18,9,50 1013 | 1706,4,2017-03-18,8,110 1014 | 1707,10,2017-03-18,3,170 1015 | 1708,8,2017-03-18,10,70 1016 | 1709,7,2017-03-18,7,80 1017 | 1710,8,2017-03-18,8,50 1018 | 1712,5,2017-03-19,12,160 1019 | 1713,5,2017-03-19,2,180 1020 | 1714,5,2017-03-19,4,90 1021 | 1715,9,2017-03-19,14,60 1022 | 1716,9,2017-03-19,9,80 1023 | 1717,9,2017-03-19,5,120 1024 | 1718,6,2017-03-19,5,50 1025 | 1719,3,2017-03-19,13,90 1026 | 1720,7,2017-03-19,1,10 1027 | 1721,3,2017-03-19,12,180 1028 | 1723,6,2017-03-19,15,160 1029 | 1724,4,2017-03-19,11,170 1030 | 1725,6,2017-03-19,4,10 1031 | 1726,5,2017-03-19,11,40 1032 | 1727,3,2017-03-19,13,10 1033 | 1728,8,2017-03-19,11,180 1034 | 1729,2,2017-03-19,2,10 1035 | 1730,3,2017-03-19,15,140 1036 | 1731,3,2017-03-19,1,50 1037 | 1732,3,2017-03-19,2,70 1038 | 1751,8,2017-03-21,13,30 1039 | 1752,10,2017-03-21,13,140 1040 | 1753,8,2017-03-21,11,180 1041 | 1754,2,2017-03-21,7,130 1042 | 1755,9,2017-03-21,4,180 1043 | 1756,9,2017-03-21,5,120 1044 | 1757,2,2017-03-21,1,80 1045 | 1758,9,2017-03-21,7,80 1046 | 1762,10,2017-03-21,9,170 1047 | 1763,6,2017-03-21,10,170 1048 | 1764,4,2017-03-21,3,130 1049 | 1765,4,2017-03-21,4,100 1050 | 1766,8,2017-03-21,15,140 1051 | 1767,4,2017-03-21,2,150 1052 | 1768,8,2017-03-21,1,170 1053 | 1769,7,2017-03-22,7,200 1054 | 1770,3,2017-03-22,6,50 1055 | 1771,10,2017-03-22,6,170 1056 | 1772,9,2017-03-22,8,120 1057 | 1773,4,2017-03-22,10,130 1058 | 1774,4,2017-03-22,7,90 1059 | 1775,4,2017-03-22,12,140 1060 | 1776,4,2017-03-22,3,60 1061 | 1778,8,2017-03-22,3,150 1062 | 1779,8,2017-03-22,2,40 1063 | 1780,3,2017-03-22,13,80 1064 | 1781,9,2017-03-22,6,30 1065 | 1783,6,2017-03-22,10,10 1066 | 1784,9,2017-03-23,6,70 1067 | 1785,8,2017-03-23,5,190 1068 | 1786,7,2017-03-23,3,40 1069 | 1787,10,2017-03-23,2,130 1070 | 1788,9,2017-03-23,8,180 1071 | 1790,8,2017-03-23,15,140 1072 | 1792,9,2017-03-23,9,160 1073 | 1793,2,2017-03-23,9,10 1074 | 1794,3,2017-03-23,11,120 1075 | 1795,3,2017-03-23,11,90 1076 | 1796,5,2017-03-23,14,20 1077 | 1798,9,2017-03-23,6,70 1078 | 1799,6,2017-03-23,1,50 1079 | 1800,9,2017-03-23,13,30 1080 | 1801,7,2017-03-23,10,130 1081 | 1802,6,2017-03-23,12,30 1082 | 1803,3,2017-03-23,4,140 1083 | 1804,5,2017-03-23,5,80 1084 | 1805,5,2017-03-23,15,140 1085 | 1806,9,2017-03-23,9,150 1086 | 1807,5,2017-03-23,13,190 1087 | 1808,4,2017-03-23,3,110 1088 | 1809,4,2017-03-23,2,90 1089 | 1810,3,2017-03-23,2,170 1090 | 1835,9,2017-03-25,14,130 1091 | 1837,3,2017-03-25,15,200 1092 | 1838,6,2017-03-25,3,20 1093 | 1839,9,2017-03-25,1,10 1094 | 1840,4,2017-03-25,12,200 1095 | 1842,3,2017-03-25,13,180 1096 | 1843,7,2017-03-25,1,150 1097 | 1844,2,2017-03-25,7,180 1098 | 1845,2,2017-03-25,7,20 1099 | 1846,2,2017-03-25,15,20 1100 | 1847,4,2017-03-25,12,90 1101 | 1848,4,2017-03-25,2,180 1102 | 1849,7,2017-03-25,11,50 1103 | 1850,8,2017-03-25,13,150 1104 | 1851,3,2017-03-25,2,120 1105 | 1852,6,2017-03-25,15,20 1106 | 1853,7,2017-03-25,12,120 1107 | 1854,9,2017-03-25,1,30 1108 | 1857,10,2017-03-25,14,200 1109 | 1859,6,2017-03-25,7,180 1110 | 1861,9,2017-03-26,15,50 1111 | 1862,10,2017-03-26,13,200 1112 | 1863,7,2017-03-26,10,50 1113 | 1864,3,2017-03-26,4,170 1114 | 1866,3,2017-03-26,7,160 1115 | 1867,7,2017-03-26,7,150 1116 | 1868,3,2017-03-26,3,120 1117 | 1869,5,2017-03-26,4,40 1118 | 1870,4,2017-03-26,6,80 1119 | 1871,9,2017-03-26,10,10 1120 | 1872,8,2017-03-26,4,90 1121 | 1873,5,2017-03-26,6,60 1122 | 1875,3,2017-03-26,10,40 1123 | 1876,9,2017-03-26,7,190 1124 | 1877,4,2017-03-26,6,200 1125 | 1878,3,2017-03-26,4,150 1126 | 1879,7,2017-03-26,6,180 1127 | 1881,6,2017-03-27,4,10 1128 | 1882,2,2017-03-27,4,120 1129 | 1883,6,2017-03-27,15,170 1130 | 1885,2,2017-03-27,10,130 1131 | 1891,7,2017-03-27,14,110 1132 | 1892,8,2017-03-27,10,140 1133 | 1893,5,2017-03-27,2,160 1134 | 1895,6,2017-03-27,13,120 1135 | 1896,6,2017-03-27,6,60 1136 | 1918,4,2017-03-29,1,50 1137 | 1919,8,2017-03-29,6,20 1138 | 1920,3,2017-03-29,11,120 1139 | 1921,5,2017-03-29,2,10 1140 | 1922,7,2017-03-29,8,140 1141 | 1923,4,2017-03-29,6,80 1142 | 1924,3,2017-03-29,3,110 1143 | 1925,8,2017-03-29,4,40 1144 | 1926,8,2017-03-29,11,20 1145 | 1927,3,2017-03-29,5,60 1146 | 1928,2,2017-03-29,8,200 1147 | 1929,8,2017-03-29,1,160 1148 | 1930,2,2017-03-29,5,180 1149 | 1931,7,2017-03-29,1,190 1150 | 1933,8,2017-03-29,8,90 1151 | 1934,7,2017-03-29,3,60 1152 | 1935,3,2017-03-29,8,200 1153 | 1937,9,2017-03-30,1,110 1154 | 1938,7,2017-03-30,2,90 1155 | 1948,7,2017-03-30,10,10 1156 | 1951,9,2017-03-30,3,80 1157 | 1953,9,2017-03-30,15,20 1158 | 1956,7,2017-03-30,15,190 1159 | 1957,7,2017-03-30,2,90 1160 | 1958,9,2017-03-30,14,80 1161 | 1961,8,2017-03-30,5,120 1162 | 1965,7,2017-03-30,3,160 1163 | 1968,7,2017-03-30,2,80 1164 | 1970,7,2017-03-30,2,90 1165 | 1971,9,2017-03-30,5,190 1166 | 1972,4,2017-03-30,5,70 1167 | 1973,8,2017-03-30,6,30 1168 | 1974,4,2017-03-31,3,100 1169 | 1975,10,2017-03-31,2,170 1170 | 1976,7,2017-03-31,2,90 1171 | 1977,2,2017-03-31,7,80 1172 | 1978,6,2017-03-31,11,70 1173 | 1979,9,2017-03-31,4,100 1174 | 1980,10,2017-03-31,4,30 1175 | 1981,2,2017-03-31,12,100 1176 | 1982,10,2017-03-31,2,50 1177 | 1984,2,2017-03-31,10,180 1178 | 1985,2,2017-03-31,9,130 1179 | 1986,5,2017-03-31,4,50 1180 | 1987,4,2017-03-31,7,110 1181 | 1988,5,2017-03-31,11,30 1182 | 1989,10,2017-03-31,2,90 1183 | 1990,9,2017-03-31,9,190 1184 | 1991,10,2017-03-31,14,120 1185 | 1992,7,2017-03-31,10,40 1186 | 1993,5,2017-03-31,4,130 1187 | 1994,9,2017-03-31,4,70 1188 | 1995,5,2017-03-31,8,140 1189 | 1996,10,2017-03-31,7,80 1190 | 1997,9,2017-03-31,6,20 1191 | 1999,8,2017-03-31,5,100 1192 | -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/resources/thunderbird_manufacturing.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasnield/oreilly_kotlin_for_data_science/5662a7d55d7f9bc1ad3d59a582ee9af0ce643cd8/2. Practical Data Modeling for Production with Kotlin/resources/thunderbird_manufacturing.db -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/section_i_working_with_data_sources.md: -------------------------------------------------------------------------------- 1 | # Section I: Reading Data Sources 2 | 3 | Reading a text file (that is under 2GB) is pretty simple in Kotlin. 4 | 5 | 6 | ## 1-1A: Reading a File 7 | 8 | ```kotlin 9 | package com.oreilly 10 | 11 | import java.io.File 12 | 13 | 14 | fun main(args: Array) { 15 | 16 | val lines = File("C:\\Users\\thoma\\Desktop\\customer_orders.csv").readLines() 17 | 18 | for (l in lines) { 19 | println(l) 20 | } 21 | } 22 | ``` 23 | 24 | 25 | ## 1-1B: Reading a File 26 | 27 | 28 | Of course, you can also use a `forEach()` with a lambda: 29 | 30 | ```kotlin 31 | package com.oreilly 32 | 33 | import java.io.File 34 | 35 | 36 | fun main(args: Array) { 37 | 38 | val lines = File("C:\\Users\\thoma\\Desktop\\customer_orders.csv").readLines() 39 | 40 | lines.forEach { 41 | println(it) 42 | } 43 | } 44 | ``` 45 | 46 | ## 1-1C: Reading a Large File 47 | 48 | For files larger than 2GB, you may want to use a `BufferedReader` instead. 49 | 50 | ```kotlin 51 | package com.oreilly 52 | 53 | 54 | import java.io.File 55 | 56 | 57 | fun main(args: Array) { 58 | 59 | val reader = File("C:\\Users\\thoma\\Desktop\\customer_orders.csv").bufferedReader() 60 | 61 | reader.forEachLine { 62 | println(it) 63 | } 64 | } 65 | ``` 66 | 67 | ## 1-1D: Turning Each Record into a Data Class Instance 68 | 69 | We can turn each line of the file into an instance of a `CustomerOrder` data class. 70 | 71 | ```kotlin 72 | package com.oreilly 73 | 74 | import java.io.File 75 | import java.time.LocalDate 76 | 77 | fun main(args: Array) { 78 | 79 | val reader = File("C:\\Users\\thoma\\Desktop\\customer_orders.csv").bufferedReader() 80 | 81 | val orders = reader.readLines() 82 | .drop(1) //skip header line 83 | .map { it.split(",") } 84 | .map { 85 | CustomerOrder( 86 | customerOrderId = it[0].toInt(), 87 | customerId = it[1].toInt(), 88 | orderDate = LocalDate.parse(it[2]), 89 | productId = it[3].toInt(), 90 | quantity = it[4].toInt() 91 | ) 92 | } 93 | 94 | orders.forEach { 95 | println(it) 96 | } 97 | } 98 | 99 | data class CustomerOrder( 100 | val customerOrderId: Int, 101 | val customerId: Int, 102 | val orderDate: LocalDate, 103 | val productId: Int, 104 | val quantity: Int 105 | ) 106 | ``` 107 | 108 | This would be more efficient if we used Sequences (which effectively work like assembly lines) rather than creating all these intermediary collections with collection operators, which we will learn about in the next section. 109 | 110 | ## 1-1E: Writing to a File 111 | 112 | You can also write to files. There are functional, advanced ways to express this in fewer lines, but here is the most basic way: 113 | 114 | ```kotlin 115 | package com.oreilly 116 | 117 | 118 | import java.io.File 119 | import java.time.LocalDate 120 | 121 | 122 | fun main(args: Array) { 123 | 124 | val writer = File("C:\\Users\\thoma\\Desktop\\output.csv").bufferedWriter() 125 | 126 | val orders = listOf( 127 | CustomerOrder(2, 5, LocalDate.of(2017,1,1), 15, 110), 128 | CustomerOrder(12, 5, LocalDate.of(2017,1,1), 10, 90), 129 | CustomerOrder(26, 5, LocalDate.of(2017,1,2), 7, 120) 130 | ) 131 | 132 | orders.forEach { 133 | writer.write("${it.customerOrderId},${it.customerId},${it.orderDate},${it.productId},${it.quantity}") 134 | writer.newLine() 135 | } 136 | 137 | writer.flush() 138 | } 139 | 140 | data class CustomerOrder( 141 | val customerOrderId: Int, 142 | val customerId: Int, 143 | val orderDate: LocalDate, 144 | val productId: Int, 145 | val quantity: Int 146 | ) 147 | ``` 148 | 149 | 150 | ## 1-2A: Reading a SQL Query 151 | 152 | To work with data sources, you typically use a JDBC connection (Java's standard way of connecting to a database). You will need to get the proper JDBC driver and bring it into your Maven dependencies. 153 | 154 | In this example, we are going to use SQLite. Add the following dependency to your Maven `pom.xml`: 155 | 156 | ```xml 157 | 158 | org.xerial 159 | sqlite-jdbc 160 | 3.20.0 161 | 162 | ``` 163 | 164 | Unfortunately,the Kotlin standard library has no streamlined utilities for JDBC so there are a few steps you will need to do. Of course, you can create your own extensions or use other Kotlin libraries, but here is the vanilla way to create a List of `CustomerOrder`s off a SQL query: 165 | 166 | 167 | ```kotlin 168 | package com.oreilly 169 | 170 | import java.sql.DriverManager 171 | import java.time.LocalDate 172 | 173 | fun main(args: Array) { 174 | val conn = DriverManager.getConnection("jdbc:sqlite:C:/Users/thoma/Desktop/thunderbird_manufacturing.db") 175 | 176 | val ps = conn.prepareStatement("SELECT * FROM CUSTOMER_ORDER") 177 | val rs = ps.executeQuery() 178 | 179 | val orders = mutableListOf() 180 | 181 | while (rs.next()) { 182 | orders += CustomerOrder( 183 | rs.getInt("CUSTOMER_ORDER_ID"), 184 | rs.getInt("CUSTOMER_ID"), 185 | LocalDate.parse(rs.getString("ORDER_DATE")), 186 | rs.getInt("PRODUCT_ID"), 187 | rs.getInt("QUANTITY") 188 | ) 189 | } 190 | 191 | ps.close() 192 | 193 | orders.forEach { println(it) } 194 | } 195 | 196 | data class CustomerOrder( 197 | val customerOrderId: Int, 198 | val customerId: Int, 199 | val orderDate: LocalDate, 200 | val productId: Int, 201 | val quantity: Int 202 | ) 203 | ``` 204 | 205 | ## 1-2B: Passing Parameters to a query 206 | 207 | 208 | To pass parameters to a query, use a `PreparedStatement` to safely inject them. This will avoid inadvertent or malicious SQL injection. 209 | 210 | 211 | ```kotlin 212 | package com.oreilly 213 | 214 | 215 | import java.sql.DriverManager 216 | import java.time.LocalDate 217 | 218 | 219 | fun main(args: Array) { 220 | 221 | val orders = ordersForCustomerId(5) 222 | orders.forEach { println(it) } 223 | } 224 | 225 | val conn = DriverManager.getConnection("jdbc:sqlite:C:/Users/thoma/Desktop/thunderbird_manufacturing.db") 226 | 227 | fun ordersForCustomerId(customerId: Int): List { 228 | val ps = conn.prepareStatement("SELECT * FROM CUSTOMER_ORDER WHERE CUSTOMER_ID = ?") 229 | 230 | ps.setInt(1,customerId) 231 | 232 | val rs = ps.executeQuery() 233 | 234 | val orders = mutableListOf() 235 | 236 | while (rs.next()) { 237 | orders += CustomerOrder( 238 | rs.getInt("CUSTOMER_ORDER_ID"), 239 | rs.getInt("CUSTOMER_ID"), 240 | LocalDate.parse(rs.getString("ORDER_DATE")), 241 | rs.getInt("PRODUCT_ID"), 242 | rs.getInt("QUANTITY") 243 | ) 244 | } 245 | 246 | ps.close() 247 | return orders 248 | } 249 | 250 | data class CustomerOrder( 251 | val customerOrderId: Int, 252 | val customerId: Int, 253 | val orderDate: LocalDate, 254 | val productId: Int, 255 | val quantity: Int 256 | ) 257 | ``` 258 | 259 | ## 1-3A: Working with web requests 260 | 261 | Kotlin should be able to retrieve any remote resources using standard protocols. 262 | 263 | For instance, you can process an HTTP request fairly easily. Below, we read a Gist on GitHub containing a text file of the 50 U.S. states. 264 | 265 | ```kotlin 266 | package com.oreilly 267 | 268 | 269 | import java.net.URL 270 | 271 | fun main(args: Array) { 272 | 273 | val usStates = URL("https://goo.gl/S0xuOi").readText().split(Regex("\\r?\\n")) 274 | 275 | println(usStates) 276 | } 277 | ``` 278 | 279 | ## 1-3B: Lazy Properties 280 | 281 | Sometimes it can be helpful to lazily initialize a property, especially for large expensive data sets. The benefit of lazy properties is they will not eagerly initialize when the containing Kotlin file is used. Rather, they will construct the moment that property is called. 282 | 283 | Lazy properties are enabled by Kotlin's property delegation, which we will not dive into in depth but you can read about in the [Kotlin Reference](https://kotlinlang.org/docs/reference/delegated-properties.html). For our purposes, all you need to do is call `by lazy { }` next to your property declaration and the data set will go into that lambda argument. 284 | 285 | 286 | ```kotlin 287 | package com.oreilly 288 | 289 | 290 | import java.net.URL 291 | 292 | fun main(args: Array) { 293 | 294 | println("Application started without loading U.S. states") 295 | println(usStates) //usStates will load now that its called 296 | } 297 | 298 | val usStates by lazy { 299 | println("property called, loading and caching!") 300 | URL("https://goo.gl/S0xuOi").readText().split(Regex("\\r?\\n")) 301 | } 302 | ``` 303 | -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/section_ii_functional_programming.md: -------------------------------------------------------------------------------- 1 | # Section II: Functional Programming with Kotlin 2 | 3 | Kotlin has adopted several functional programming ideas from languages like C#, Scala and Groovy, but streamlines and improves them in many practical ways. 4 | 5 | ## 2.1 - Understanding Higher Order Functions 6 | 7 | We actually have used higher order functions already when using lambda arguments. We will go into more depth in this section on how they work. 8 | 9 | ## 2.1A - A simple mapping function 10 | 11 | We can use higher-order functions to turn a `String` somehow into a `Int`, but allow the user to define a lambda on how to do that. For instance, we can pass a lambda that maps a `String` to its length. 12 | 13 | ```kotlin 14 | package com.oreilly 15 | 16 | fun main(args: Array) { 17 | 18 | val myString = "Foxtrot" 19 | 20 | val length = mapStringToInt(input = myString, mapper = { it.length }) 21 | 22 | println(length) 23 | } 24 | 25 | fun mapStringToInt(input: String, mapper: (String) -> Int) = mapper(input) 26 | ``` 27 | ## 2.1B - Lambdas as last argument 28 | 29 | If a lambda is the last argument, you can put it outside the rounded paranthesis like so: 30 | 31 | ```kotlin 32 | package com.oreilly 33 | 34 | fun main(args: Array) { 35 | 36 | val myString = "Foxtrot" 37 | 38 | val length = mapStringToInt(myString) { it.length } 39 | 40 | println(length) 41 | } 42 | 43 | fun mapStringToInt(input: String, mapper: (String) -> Int) = mapper(input) 44 | ``` 45 | 46 | ## 2.1C - Inlining Functions 47 | 48 | When passing lambda arguments, you can sometimes get greater efficiency by inlining functions. This basically means injecting the resulting bytecode of the lambda into its target, and therefore eliminating the lambda object upon compilation which saves memory. 49 | 50 | ```kotlin 51 | package com.oreilly 52 | 53 | fun main(args: Array) { 54 | val myString = "Foxtrot" 55 | 56 | val length = mapStringToInt(myString) { it.length } 57 | 58 | println(length) 59 | } 60 | 61 | inline fun mapStringToInt(input: String, crossinline mapper: (String) -> Int) = mapper(input) 62 | ``` 63 | 64 | If you are unsure whether you can inline or not, try it out and see if an error or warning occurs. If there isn't any, then you can take advantage of inlining. You can further read about the benefits of inlining in the Kotlin Reference: 65 | 66 | https://kotlinlang.org/docs/reference/inline-functions.html 67 | 68 | 69 | ## 2.2A - Lambda Syntax 70 | 71 | There are other syntaxes you can use to define lambdas. The simplest one is the `it` keyword referring to the single paramter input, but there are times you need to be more explicit. 72 | 73 | Here are the different supported lambda syntaxes you can use: 74 | 75 | ```kotlin 76 | val length = mapStringToInt(myString) { it.length } 77 | val length = mapStringToInt(myString) { s -> s.length } 78 | val length = mapStringToInt(myString) { s: String -> s.length } 79 | val length = mapStringToInt(myString, String::length)) 80 | ``` 81 | 82 | ## 2.2B - Multiple parameter lambdas 83 | 84 | If you have multiple parameters in your lambda, separate them by commas and use the arrow syntax. 85 | 86 | ```kotlin 87 | package com.oreilly 88 | 89 | fun main(args: Array) { 90 | 91 | val str1 = "Alpha" 92 | val str2 = "Beta" 93 | 94 | val combineMethod1 = combineStrings(str1, str2) { s1, s2 -> s1 + s2 } 95 | println(combineMethod1) 96 | 97 | val combineMethod2 = combineStrings(str1, str2) { s1, s2 -> "$s1-$s2"} 98 | println(combineMethod2) 99 | } 100 | 101 | fun combineStrings(str1: String, str2: String, combiner: (String,String) -> String) = combiner(str1, str2) 102 | ``` 103 | 104 | You can read more about lambda syntax on the Kotlin Reference: 105 | 106 | https://kotlinlang.org/docs/reference/lambdas.html 107 | 108 | ## 2.3 Generics 109 | 110 | If you want to support any given types with your functional arguments, you can leverage generics to increase flexiblity. 111 | 112 | ## 2.3A: A Generic Higher Order Function 113 | 114 | For instance, we can take a `String` input and map it to any given type `R`. Generic types are often defined as a single uppercase letter, and you must put before the function name in a diamond ``. 115 | 116 | Below, we take a `dateString` of `2017-01-01` and use a `mapString()` function to convert it a `LocalDate` as well as an `Int` (it's length). 117 | 118 | ```kotlin 119 | package com.oreilly 120 | 121 | import java.time.LocalDate 122 | 123 | fun main(args: Array) { 124 | 125 | val dateString = "2017-01-01" 126 | 127 | val date = mapString(dateString) { LocalDate.parse(it) } 128 | println(date) 129 | 130 | val length = mapString(dateString) { it.length } 131 | println(length) 132 | } 133 | 134 | inline fun mapString(input: String, crossinline mapper: (String) -> R) = mapper(input) 135 | ``` 136 | 137 | This is not dynamically typed. The compiler was smart enough to see `R` was a `LocalDate` in the first call, and an `Int` in the second call. It will enforce these types as they were inferred. 138 | 139 | ## 2.3B: Multiple Generic Types in a Higher Order Function 140 | 141 | You can have multiple generic types for a given function as well. For instance, we can accept a parameter of any given type `T` and return any type `R`. Below, we use a `map()` function that maps any arbitrary type `T` to an arbitrary type `R`. It can be used to turn a `String` into a `LocalDate` as well as an `Int` into a `String`. 142 | 143 | ```kotlin 144 | package com.oreilly 145 | 146 | import java.time.LocalDate 147 | 148 | fun main(args: Array) { 149 | 150 | //map a String to a LocalDate 151 | val dateString = "2017-01-01" 152 | val date = map(dateString) { LocalDate.parse(it) } 153 | println(date) 154 | 155 | //map an Int to a String 156 | val myNumber = 4 157 | val myNumberAsString = map(myNumber) { 158 | when (it) { 159 | 1 -> "ONE" 160 | 2 -> "TWO" 161 | 3 -> "THREE" 162 | 4 -> "FOUR" 163 | 5 -> "FIVE" 164 | 6 -> "SIX" 165 | 7 -> "SEVEN" 166 | 8 -> "EIGHT" 167 | 9 -> "NINE" 168 | else -> throw Exception("Only 1 through 9 supported") 169 | } 170 | } 171 | 172 | println(myNumberAsString) 173 | } 174 | 175 | inline fun map(input: T, crossinline mapper: (T) -> R) = mapper(input) 176 | ``` 177 | 178 | To learn more about generics in depth, go to the Kotlin Reference: 179 | https://kotlinlang.org/docs/reference/generics.html 180 | 181 | 182 | ## 2.4: Sequences 183 | 184 | Sometimes a chain of collection operators can expensively create intermediary collections. Therefore, in cases where multiple operations are being processed it can be better to use a `Sequence`. A `Sequence` is much like an assembly line processing one item downstream at a time. 185 | 186 | There are many, many operators you can leverage in a `Sequence`, and you can view them all in the Kotlin reference: 187 | 188 | https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html 189 | 190 | If you really enjoy this style of programming where items are fluently processed in a pipeline, there are similar tools available such as Java 8 Streams (which support efficient parallelization) as well as RxJava Observables and Flowables (which are not only concurrent but handle both data and events). 191 | 192 | ## 2.4A: Sequence Example 193 | 194 | A `Sequence` is lazy and does not execute until certain terminator operators are called (like `toList()`, `average()`, `count()`, etc). 195 | 196 | ```kotlin 197 | package com.oreilly 198 | 199 | fun main(args: Array) { 200 | 201 | val codeWords = sequenceOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 202 | 203 | val averageLength = codeWords.map { it.length } 204 | .average() 205 | 206 | println(averageLength) 207 | } 208 | ``` 209 | 210 | ## 2.4B: Sequence Example with flatMap() 211 | 212 | Pretty much any collection or Iterable type will have an `asSequence()` function that will iterate the elements as a `Sequence`. Some sources can only be iterated once, while others can be iterated multiple times. You can also use `flatMap()` to turn an element into a `Sequence` of other elements, and merge all those resulting sequeneces together. 213 | 214 | Below, we take a `List` and turn it into a `Sequence` that is turned into a `Sequence` of its letters, then we `distinct` them and collect them into another `List`. 215 | 216 | ```kotlin 217 | package com.oreilly 218 | 219 | fun main(args: Array) { 220 | 221 | val codeWords = listOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 222 | 223 | val distinctLetters = codeWords.asSequence() 224 | .flatMap { it.toCharArray().asSequence() } 225 | .map { it.toUpperCase() } 226 | .distinct() 227 | .toList() 228 | 229 | println(distinctLetters) 230 | } 231 | ``` 232 | 233 | 234 | ## 2.5: `let()` and `apply()` 235 | 236 | Kotlin has a few higher order functions available on every single type. Two we will cover are `let()` and `apply()`. 237 | 238 | 239 | ## 2.5A `let()` 240 | 241 | 242 | The `let()` operator will take any given type `T` and turn it into a type `R`. It certainly is not critical, but it is a very nice convenience that can save you from declaring intermediary variables for values that need to be referred multiple times. 243 | 244 | For instance, let's say you want to uppercase a `String` and then concatenate it to its reversed counterpart (essentially mirroring it). You may have to save an `uppercased` variable to do this. 245 | 246 | ```kotlin 247 | package com.oreilly 248 | 249 | fun main(args: Array) { 250 | 251 | val myString = "Bravo" 252 | 253 | val uppercased = myString.toUpperCase() 254 | 255 | val mirrored = uppercased + "|" + uppercased.reversed() 256 | 257 | println(mirrored) 258 | } 259 | ``` 260 | 261 | But if you use the `let()`, you can avoid that step of saving the `uppercased` variable, and instead immediately call `let()` on the result of `uppercase()` to immediately transform it to something else. 262 | 263 | 264 | ```kotlin 265 | package com.oreilly 266 | 267 | fun main(args: Array) { 268 | 269 | val myString = "Bravo" 270 | 271 | val mirrored = myString.toUpperCase().let { it + "|" + it.reversed() } 272 | 273 | println(mirrored) 274 | } 275 | ``` 276 | 277 | ## 2.5B: `let()` for quick printing 278 | 279 | You can also use `let()` to quickly print the result of a long chain of operations rather than saving the result to a variable and printing it. 280 | 281 | ```kotlin 282 | package com.oreilly 283 | 284 | fun main(args: Array) { 285 | 286 | val codeWords = sequenceOf("Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot") 287 | 288 | codeWords.flatMap { it.toCharArray().asSequence() } 289 | .map { it.toUpperCase() } 290 | .distinct() 291 | .joinToString(separator = ",") 292 | .let { println(it) } 293 | } 294 | ``` 295 | 296 | So in a nutshell, `let()` can help keep your code fluent and avoid creating several intermediary variables. 297 | 298 | 299 | ## 2.5C: `apply()` 300 | 301 | The `apply()` is kind of like `let()`, but it will refer to the object it is called on as `this` rather than `it`, making the lambda operate as if its inside that class. It will then return that same object. 302 | 303 | It can be helpful to declare an object but then immediately manipulate it before saving it to a variable or property, without breaking the declaration flow. 304 | 305 | Below, we have two examples of `apply()`. The first `apply()` call will do further manipulations with the returned `LocalDate` before assigning it to the `todaysDate` variable. The second will construct a `MyProcess` instance but immediately call its `run()` function before assigning it to the `immediateProcess` variable. 306 | 307 | ```kotlin 308 | package com.oreilly 309 | 310 | import java.time.LocalDate 311 | import java.time.LocalTime 312 | 313 | fun main(args: Array) { 314 | 315 | val todaysDate = LocalDate.now().apply { 316 | //do stuff with LocalDate before assigning to todaysDate variable 317 | println("Constructed today's date, which is $month $dayOfMonth, $year") 318 | } 319 | 320 | val immediateProcess = MyProcess().apply { 321 | // kick off the run() function immediately before assigning to the immediateProcess variable 322 | run() 323 | } 324 | } 325 | 326 | class MyProcess { 327 | 328 | init { 329 | println("Constructed MyProcess") 330 | } 331 | 332 | fun run() { 333 | println("Starting process at ${LocalTime.now()}") 334 | } 335 | } 336 | ``` 337 | -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/section_iii_adapting_kotlin_to_your_domain.md: -------------------------------------------------------------------------------- 1 | # Section III: Adapting Kotlin to your Domain 2 | 3 | One of the more modern, useful features of Kotlin is it's ability to quickly adapt libraries and even the language itself so it streamlines towards your business domain. These ideas are not new as C# and Groovy pioneered a couple of these features, but Kotlin streamlines them into a single platform. 4 | 5 | ## 3.1 - Extension Functions 6 | 7 | Have you ever wanted to "add" functions or properties to an object, even if it's in a library you don't own? You may have done this in Python before, but it can quickly become messy because you are mutating the behaviors and attributes of an object (known as "monkey programming"). Kotlin can achieve this for the most part, without actually modifying the object itself. 8 | 9 | ## 3.1A - Extension Functions 10 | 11 | Sometimes it can be helpful to add functions to types we do not own, or we do own but don't want to clutter just to support one use case. Kotlin extension functions allow us to "add" a function to class but it will actually compile it as a standalone helper function, not physically add it to the class. 12 | 13 | For instance, say when we are working with dates we are frequently calculating the start date of the week containing that date. This could be helpful for reporting or grouping up items by week. 14 | 15 | Rather than creating a helper function, we can use an extension function that will attach itself directly to the `LocalDate` type. We can then see it in Intellij IDEA's autocompletion. 16 | 17 | ```kotlin 18 | package com.oreilly 19 | 20 | import java.time.DayOfWeek 21 | import java.time.LocalDate 22 | 23 | fun main(args: Array) { 24 | 25 | val myDate = LocalDate.of(2017,8,31) 26 | 27 | val weekStartDate = myDate.startOfWeek() 28 | 29 | println(weekStartDate) 30 | } 31 | 32 | fun LocalDate.startOfWeek() = (0..6).asSequence() 33 | .map { this.minusDays(it.toLong()) } 34 | .first { it.dayOfWeek == DayOfWeek.MONDAY } 35 | 36 | ``` 37 | 38 | The body of the function will refer to the object it has been applied on as `this`, acting as if its scope is inside the class. However, extension functions only have access to code of that type that has been made public and can't access anything that is private. 39 | 40 | 41 | ## 3.1B - Extension Function with Parameters 42 | 43 | Extension functions behave just like functions, other than they are targeting that type they are being applied to. We can therefore use parameters and default parameters. 44 | 45 | ```kotlin 46 | package com.oreilly 47 | \npackage com.oreilly 48 | \r\npackage com.oreilly 49 | 50 | import java.time.DayOfWeek 51 | import java.time.LocalDate 52 | 53 | fun main(args: Array) { 54 | 55 | val myDate = LocalDate.of(2017,8,31) 56 | 57 | val weekStartDate = myDate.startOfWeek(DayOfWeek.SUNDAY) 58 | 59 | println(weekStartDate) 60 | } 61 | 62 | fun LocalDate.startOfWeek(startDayOfWeek: DayOfWeek = DayOfWeek.MONDAY) = (0..6).asSequence() 63 | .map { this.minusDays(it.toLong()) } 64 | .first { it.dayOfWeek == startDayOfWeek } 65 | ``` 66 | 67 | ## 3.1C - Extension Properties 68 | 69 | If your extension function takes arguments or could take arguments, you should keep it a function. But if you never foresee needing any arguments, you can use an extension property instead. Below, we make `startOfWeek` an extension property that will always be starting on Monday. 70 | 71 | ```kotlin 72 | package com.oreilly 73 | 74 | import java.time.DayOfWeek 75 | import java.time.LocalDate 76 | 77 | fun main(args: Array) { 78 | 79 | val myDate = LocalDate.of(2017,8,31) 80 | 81 | val weekStartDate = myDate.startOfWeek 82 | 83 | println(weekStartDate) 84 | } 85 | 86 | val LocalDate.startOfWeek get() = (0..6).asSequence() 87 | .map { this.minusDays(it.toLong()) } 88 | .first { it.dayOfWeek == DayOfWeek.MONDAY } 89 | ``` 90 | 91 | Note that extension properties cannot store state in the targeted object, but rather have to be derived off a calculation or store that state elsewhere. 92 | 93 | ## 3.2 - Operator Functions 94 | 95 | You can leverage operator symbols to create intuitive operator syntaxes for a type. 96 | 97 | 98 | ## 3.2 - Adding Integers to a LocalDate 99 | 100 | For instance, you cannot add an Integer to a `LocalDate` object to add that many days to it. But if you implement the operator `plus()` function, you can achieve this. 101 | 102 | ```kotlin 103 | package com.oreilly 104 | 105 | import java.time.LocalDate 106 | import java.time.temporal.ChronoUnit 107 | 108 | fun main(args: Array) { 109 | 110 | val myDate = LocalDate.of(2017,8,31) 111 | 112 | val tomorrow = myDate + 1 113 | 114 | println(tomorrow) 115 | } 116 | 117 | operator fun LocalDate.plus(days: Int) = plusDays(days.toLong()) 118 | operator fun LocalDate.minus(days: Int) = minusDays(days.toLong()) 119 | ``` 120 | 121 | Here are the operator functions you can leverage: 122 | 123 | |Symbol Signature|Operator Function| 124 | |---|---| 125 | |a + b|a.plus(b)| 126 | |a - b|a.minus(b)| 127 | |a / b|a.div(b)| 128 | |a * b|a.times(b)| 129 | |a % b|a.rem(b)| 130 | |a..b|a.rangeTo(b)| 131 | |a -= b| a.minusAssign(b) 132 | |a *= b| a.timesAssign(b) 133 | |a /= b| a.divAssign(b) 134 | |a %= b| a.remAssign(b), a.modAssign(b) (deprecated) 135 | |a in b|b.contains(a)| 136 | |a !in b|!b.contains(a)| 137 | |a[i]| a.get(i) 138 | |a[i, j]| a.get(i, j) 139 | |a[i_1, ..., i_n]| a.get(i_1, ..., i_n) 140 | |a[i] = b| a.set(i, b) 141 | |a[i, j] = b| a.set(i, j, b) 142 | |a[i_1, ..., i_n] = b| a.set(i_1, ..., i_n, b) 143 | 144 | There are also infix functions which allow you to take single argument, single result functions and express them with a keyword operator. These are something you will likely want to avoid using as they can cause a lot clutter. But you can read more about operator and infix functions on the Kotlin Reference. 145 | 146 | https://kotlinlang.org/docs/reference/operator-overloading.html 147 | 148 | 149 | ## 3.3 - DSL's and Builders 150 | 151 | A cool feature that you can leverage with Kotlin are DSL's (domain specific languages). This essentially allows you to cleverly use Kotlin to create a programming language specific to your business. This can be a helpful way to parameterize models, processes, and inputs, even accessible to nontechnical users. They can also be used to create complex data or nested structures like user interfaces. 152 | 153 | Below is a DSL to build the inputs for a hotel pricing model. 154 | 155 | ```kotlin 156 | package com.oreilly 157 | 158 | import java.math.BigDecimal 159 | 160 | fun main(args: Array) { 161 | 162 | hotel { 163 | 164 | rooms { 165 | queen(quantity = 60) 166 | doublequeen(quantity = 60) 167 | 168 | king(quantity = 20) 169 | doubleking(quantity = 20) 170 | } 171 | 172 | prices { 173 | price(daysBeforeStay = 0..4, priceRange = 170.01..200.00) 174 | price(daysBeforeStay = 5..10, priceRange = 150.01..170.00) 175 | price(daysBeforeStay = 11..20, priceRange = 110.01..150.00) 176 | price(daysBeforeStay = 21..60, priceRange = 75.00..110.00) 177 | } 178 | } 179 | } 180 | ``` 181 | 182 | And here is the backing implementation. We are using a special lambda type called a receiver function, which is a lambda that targets a "receiving object". For instance `PriceBuilder.() -> Unit` is a parameterless function that targets actions against a `PriceBuilder`. The function will have a `this` scope against the `PriceBuilder`. 183 | 184 | ```kotlin 185 | package com.oreilly 186 | 187 | fun hotel(op: HotelBuilder.() -> Unit): HotelBuilder { 188 | val newHotelModel = HotelBuilder() 189 | 190 | newHotelModel.op() 191 | 192 | return newHotelModel 193 | } 194 | 195 | 196 | class HotelBuilder { 197 | 198 | private val availableRooms = mutableListOf() 199 | private val availablePrices = mutableListOf() 200 | 201 | fun rooms(op: RoomBuilder.() -> Unit) { 202 | val builder = RoomBuilder() 203 | builder.op() 204 | availableRooms += builder.roomQueue 205 | } 206 | 207 | fun prices(op: PriceBuilder.() -> Unit) { 208 | val builder = PriceBuilder() 209 | builder.op() 210 | availablePrices += builder.prices 211 | } 212 | 213 | fun executeOptimization() { 214 | println("Input contains ${availableRooms.size} rooms and ${availablePrices.size} price ranges.") 215 | println("Executing optimization operations...") 216 | } 217 | } 218 | 219 | class Room(val bedType: BedType, val isDouble: Boolean = false) 220 | 221 | class Price(val daysBeforeStayRange: IntRange, val priceRange: ClosedRange) 222 | 223 | enum class BedType { 224 | KING, 225 | QUEEN 226 | } 227 | 228 | class RoomBuilder { 229 | val roomQueue = mutableListOf() 230 | 231 | fun queen(quantity: Int) = add(BedType.QUEEN, quantity, false) 232 | fun king(quantity: Int) = add(BedType.KING, quantity, false) 233 | fun doublequeen(quantity: Int) = add(BedType.QUEEN, quantity, true) 234 | fun doubleking(quantity: Int) = add(BedType.KING, quantity, true) 235 | 236 | fun add(bedType: BedType, quantity: Int, isDouble: Boolean) = 237 | (1..quantity).asSequence() 238 | .map { Room(bedType, isDouble) } 239 | .forEach { roomQueue += it } 240 | } 241 | 242 | class PriceBuilder { 243 | val prices = mutableListOf() 244 | 245 | fun price(daysBeforeStay: IntRange, priceRange: ClosedRange) { 246 | prices += Price( 247 | daysBeforeStay, 248 | BigDecimal.valueOf(priceRange.start)..BigDecimal.valueOf(priceRange.endInclusive) 249 | ) 250 | } 251 | } 252 | ``` 253 | 254 | It is easy to get carried away with DSL's, and they shouldn't be used for the sake of. Only use them if they significantly streamline the creation of complex structures. They can also be helpful to streamline certain API's and libraries that often have nested items, [like user interfaces with nested controls (see the TornadoFX library)](https://github.com/edvin/tornadofx) or [defining static data structures like HTML](https://github.com/Kotlin/kotlinx.html). 255 | -------------------------------------------------------------------------------- /2. Practical Data Modeling for Production with Kotlin/section_iv_practical_applications.md: -------------------------------------------------------------------------------- 1 | # IV. Practical Applications 2 | 3 | In this section, we will explore a number of applications of Kotlin for data science and data engineering tasks. We will also apply our knowledge of Kotlin to streamline existing Java data science libraries. 4 | 5 | ## 4.1 Ranking Mutual Friends in a Social Network 6 | 7 | Here is an analytics task: take the existing friendship connections of a social network, and recommend new connections based on the number of mutual friends between people. With Kotlin, we can use a combination of object-oriented and functional programming to achieve this. 8 | 9 | ```kotlin 10 | package com.oreilly 11 | 12 | fun main(args: Array) { 13 | 14 | //Retrieve users "John" and "Billy" 15 | val user1 = users.first { it.firstName == "John" } 16 | 17 | //see recommended friends for Billy 18 | user1.recommendedFriends() 19 | .forEach { println(it) } 20 | } 21 | 22 | data class SocialUser( 23 | val userId: Int, 24 | val firstName: String, 25 | val lastName: String 26 | ) { 27 | val friends get() = friendships.asSequence() 28 | .filter { userId == it.first || userId == it.second } 29 | .flatMap { sequenceOf(it.first, it.second) } 30 | .filter { it != userId } 31 | .map { friendId -> 32 | users.first { it.userId == friendId } 33 | }.toList() 34 | 35 | fun mutualFriendsOf(otherUser: SocialUser) = 36 | friends.asSequence().filter { it in otherUser.friends }.toList() 37 | 38 | fun recommendedFriends() = users.asSequence() 39 | .filter { it.userId != userId } // only look for other users 40 | .filter { it !in friends } // filter to people not already friends with 41 | .map { otherUser -> // package up mutual friends 42 | MutualFriendships(this, otherUser, mutualFriendsOf(otherUser).toList()) 43 | }.filter { it.mutualFriends.count() > 0 } // omit where no MutualFriendships exist 44 | .sortedByDescending { it.mutualFriends.count() } // sort greatest number of mutual friendships first 45 | } 46 | 47 | data class MutualFriendships(val user: SocialUser, val otherUser: SocialUser, val mutualFriends: List) 48 | 49 | val users = listOf( 50 | SocialUser(1, "John", "Scott"), 51 | SocialUser(2, "Hannah", "Earley"), 52 | SocialUser(3, "Timothy", "Tannen"), 53 | SocialUser(4, "Scott", "Marcum"), 54 | SocialUser(5, "Sid", "Maddow"), 55 | SocialUser(6, "Rachel", "Zimmerman"), 56 | SocialUser(7, "Heather", "Timmers"), 57 | SocialUser(8, "Casey", "Crane"), 58 | SocialUser(9, "Billy", "Awesome") 59 | ) 60 | 61 | val friendships = listOf( 62 | 1 to 6, 63 | 1 to 5, 64 | 1 to 8, 65 | 2 to 3, 66 | 2 to 9, 67 | 2 to 6, 68 | 3 to 8, 69 | 3 to 2, 70 | 3 to 7, 71 | 4 to 1, 72 | 4 to 2, 73 | 4 to 9, 74 | 5 to 7, 75 | 9 to 2, 76 | 8 to 4, 77 | 6 to 9 78 | ) 79 | ``` 80 | 81 | Above, we retrieve two users quickly using the `first()` operator, and then demonstrate the mutual friends between them. Finally, we show friendship recommendations for "John" using a ranking of non-friends that he has mutual friendships with. 82 | 83 | 84 | ## 4.2 Kotlin for Apache Spark 85 | 86 | To use Apache Spark with Kotlin, add the following dependency to your `pom.xml`. 87 | 88 | ```xml 89 | 90 | org.apache.spark 91 | spark-core_2.11 92 | 2.2.0 93 | 94 | ``` 95 | 96 | ## 4.2A - Basic Spark Example 97 | 98 | Since Apache Spark is a JVM library, there is no special configuration you will need to get it up-and-running. Just be sure to use a `JavaSparkContext` which Kotlin is cross-compatible with, rather than the default Scala-based `SparkContext`. 99 | 100 | ```kotlin 101 | package com.oreilly 102 | 103 | import org.apache.spark.SparkConf 104 | import org.apache.spark.api.java.JavaSparkContext 105 | 106 | fun main(args: Array) { 107 | 108 | val conf = SparkConf() 109 | .setMaster("local") //use local machine as master 110 | .setAppName("Kotlin Spark Test") 111 | 112 | val sc = JavaSparkContext(conf) 113 | 114 | val items = listOf("123/643/7563/2134/ALPHA", "2343/6356/BETA/2342/12", "23423/656/343") 115 | 116 | val input = sc.parallelize(items) 117 | 118 | val sumOfNumbers = input.flatMap { it.split("/").iterator() } 119 | .filter { it.matches(Regex("[0-9]+")) } 120 | .map { it.toInt() } 121 | .reduce {total,next -> total + next } 122 | 123 | println(sumOfNumbers) 124 | } 125 | ``` 126 | 127 | ## 4.2B - Registering Kotlin Classes to Spark 128 | 129 | If you have embraced the content in this video series so far, you will likely be using classes often to keep your data structures organized and refactorable. The recommended way to "register" your classes with Apache Spark, so that they can be used across all nodes, is to use a modern serialization solution like Kryo. Add Kryo as a dependency like so: 130 | 131 | ```xml 132 | 133 | com.esotericsoftware 134 | kryo 135 | 4.0.1 136 | 137 | ``` 138 | 139 | Since Apache Spark only recognizes Scala and Java classes, and not Kotlin ones, create a quick extension function to convert Kotlin classes to Java before registering them. 140 | 141 | ```kotlin 142 | package com.oreilly 143 | 144 | import org.apache.spark.SparkConf 145 | import org.apache.spark.api.java.JavaSparkContext 146 | import kotlin.reflect.KClass 147 | 148 | fun main(args: Array) { 149 | 150 | class MyItem(val id: Int, val value: String) 151 | 152 | val conf = SparkConf() 153 | .setMaster("local") 154 | .setAppName("Line Counter") 155 | 156 | conf.registerKryoClasses(MyItem::class) 157 | 158 | val sc = JavaSparkContext(conf) 159 | 160 | val items = listOf(MyItem(1,"Alpha"), MyItem(2,"Beta")) 161 | 162 | val input = sc.parallelize(items) 163 | 164 | val letters = input.flatMap { it.value.split(Regex("(?<=.)")).iterator() } 165 | .map(String::toUpperCase) 166 | .filter { it.matches(Regex("[A-Z]")) } 167 | 168 | println(letters.collect()) 169 | } 170 | 171 | //extension function to register Kotlin classes 172 | fun SparkConf.registerKryoClasses(vararg args: KClass<*>) = registerKryoClasses(args.map { it.java }.toTypedArray()) 173 | ``` 174 | 175 | ## 4.3 Using Kotlin Statistics 176 | 177 | Bring in "Kotlin Statistics" as a dependency: 178 | 179 | ```xml 180 | 181 | org.nield 182 | kotlinstatistics 183 | 0.3.0 184 | 185 | ``` 186 | 187 | Kotlin statistics is a a helpful library to do data analytics in a Kotlin-esque way, leveraging a combination of object-oriented and fluent functional programming. 188 | 189 | 190 | ## 4.3A - Simple Reductions 191 | 192 | It has simple reduction operations such as standard deviations for an entire data set: 193 | 194 | ```kotlin 195 | import org.nield.kotlinstatistics.standardDeviation 196 | import java.time.LocalDate 197 | 198 | data class Patient(val firstName: String, 199 | val lastName: String, 200 | val gender: Gender, 201 | val birthday: LocalDate, 202 | val whiteBloodCellCount: Int) 203 | 204 | 205 | val patients = listOf( 206 | Patient("John", "Simone", Gender.MALE, LocalDate.of(1989, 1, 7), 4500), 207 | Patient("Sarah", "Marley", Gender.FEMALE, LocalDate.of(1970, 2, 5), 6700), 208 | Patient("Jessica", "Arnold", Gender.FEMALE, LocalDate.of(1980, 3, 9), 3400), 209 | Patient("Sam", "Beasley", Gender.MALE, LocalDate.of(1981, 4, 17), 8800), 210 | Patient("Dan", "Forney", Gender.MALE, LocalDate.of(1985, 9, 13), 5400), 211 | Patient("Lauren", "Michaels", Gender.FEMALE, LocalDate.of(1975, 8, 21), 5000), 212 | Patient("Michael", "Erlich", Gender.MALE, LocalDate.of(1985, 12, 17), 4100), 213 | Patient("Jason", "Miles", Gender.MALE, LocalDate.of(1991, 11, 1), 3900), 214 | Patient("Rebekah", "Earley", Gender.FEMALE, LocalDate.of(1985, 2, 18), 4600), 215 | Patient("James", "Larson", Gender.MALE, LocalDate.of(1974, 4, 10), 5100), 216 | Patient("Dan", "Ulrech", Gender.MALE, LocalDate.of(1991, 7, 11), 6000), 217 | Patient("Heather", "Eisner", Gender.FEMALE, LocalDate.of(1994, 3, 6), 6000), 218 | Patient("Jasper", "Martin", Gender.MALE, LocalDate.of(1971, 7, 1), 6000) 219 | ) 220 | 221 | enum class Gender { 222 | MALE, 223 | FEMALE 224 | } 225 | 226 | fun main(args: Array) { 227 | 228 | val averageWbcc = 229 | patients.map { it.whiteBloodCellCount }.average() 230 | 231 | val standardDevWbcc = 232 | patients.map { it.whiteBloodCellCount }.standardDeviation() 233 | 234 | println("Average WBCC: $averageWbcc, Std Dev WBCC: $standardDevWbcc") 235 | } 236 | ``` 237 | 238 | ## 4.3B Slicing By Properties and Data classes 239 | 240 | You can take most of the reduction metrics and use their `xxxBy()` counterparts to slice on an attribute, such as Gender. 241 | 242 | ```kotlin 243 | fun main(args: Array) { 244 | 245 | val standardDevByGender = patients.standardDeviationBy( 246 | keySelector = { it.gender }, 247 | intMapper = { it.whiteBloodCellCount } 248 | ) 249 | 250 | println(standardDevByGender) 251 | } 252 | ``` 253 | 254 | You can also use a data class to slice on more than one attribute, such as `gender` and birthday `Month`. 255 | 256 | ```kotlin 257 | fun main(args: Array) { 258 | 259 | data class GenderAndMonth(val gender: Gender, val month: Month) 260 | 261 | val standardDevByGenderAndMonth = patients.standardDeviationBy( 262 | keySelector = { GenderAndMonth(it.gender, it.birthday.month) }, 263 | intMapper = { it.whiteBloodCellCount } 264 | ) 265 | 266 | standardDevByGenderAndMonth.forEach { 267 | println(it) 268 | } 269 | } 270 | ``` 271 | Of course, this data is a little to sparse for breaking up by gender and month. 272 | 273 | ## 4.3C Getting Percentiles by Gender 274 | 275 | You can expressively use Kotlin's different features to manipulate data sets fluently. For instance, you can create a `wbccPercentileCountByGender()` extension function to get a series of percentiles by gender. 276 | 277 | ```kotlin 278 | fun main(args: Array) { 279 | 280 | fun Collection.wbccPercentileByGender(percentile: Double) = 281 | percentileBy( 282 | percentile = percentile, 283 | keySelector = { it.gender }, 284 | doubleMapper = { it.whiteBloodCellCount.toDouble() } 285 | ) 286 | 287 | val percentileQuadrantsByGender = patients.let { 288 | mapOf(1.0 to it.wbccPercentileByGender(1.0), 289 | 25.0 to it.wbccPercentileByGender(25.0), 290 | 50.0 to it.wbccPercentileByGender(50.0), 291 | 75.0 to it.wbccPercentileByGender(75.0), 292 | 100.0 to it.wbccPercentileByGender(100.0) 293 | ) 294 | } 295 | 296 | percentileQuadrantsByGender.forEach(::println) 297 | } 298 | ``` 299 | 300 | The patterns behind Kotlin-Statistics have a large amount of potential, and show how Kotlin can bring new approaches to tackling data science and engineering. Follow the project to stay on top of its latest developments and hopefully you'll find its patterns to be useful in your own domain projects. 301 | 302 | https://github.com/thomasnield/kotlin-statistics 303 | 304 | 305 | ## 4.4 Doing matrix math with ND4J 306 | 307 | ND4J is Java's NumPy, and officially has interfaces for Java, Scala, and Clojure. However, it works with Kotlin out-of-the-box as well. While it may not have convenient fluent interfaces that you hopefully have grown to like in Kotlin, you can easily create your own around it. 308 | 309 | Add ND4J to your project by adding this dependency: 310 | 311 | ```xml 312 | 313 | org.nd4j 314 | nd4j-native 315 | 0.9.1 316 | 317 | ``` 318 | 319 | Below we have a `List` of `Sale` objects. We can create a `toINDArray()` extension function on any `Iterable` type, and have it accept any number of mapping arguments to turn each item into a row of values for a matrix. 320 | 321 | ```kotlin 322 | import org.nd4j.linalg.api.ndarray.INDArray 323 | import org.nd4j.linalg.factory.Nd4j 324 | import java.time.LocalDate 325 | 326 | fun main(args: Array) { 327 | 328 | val matrix = sales.toINDArray( 329 | {it.saleDate.year.toDouble()}, 330 | {it.saleDate.monthValue.toDouble()}, 331 | {it.saleDate.dayOfMonth.toDouble()}, 332 | {it.billingAmount} 333 | ) 334 | 335 | println(matrix) 336 | } 337 | 338 | data class Sale(val accountId: Int, val saleDate: LocalDate, val billingAmount: Double) 339 | 340 | val sales = listOf( 341 | Sale(1, LocalDate.of(2016,12,3), 180.0), 342 | Sale(2, LocalDate.of(2016, 7, 4), 140.2), 343 | Sale(3, LocalDate.of(2016, 6, 3), 111.4), 344 | Sale(4, LocalDate.of(2016, 1, 5), 192.7), 345 | Sale(5, LocalDate.of(2016, 5, 4), 137.9), 346 | Sale(6, LocalDate.of(2016, 3, 6), 125.6), 347 | Sale(7, LocalDate.of(2016, 12,4), 164.3), 348 | Sale(8, LocalDate.of(2016, 7,11), 144.2) 349 | ) 350 | 351 | 352 | fun Iterable.toINDArray(vararg valueSelectors: (T) -> Double): INDArray { 353 | val list = toList() 354 | 355 | val selectedValues = list.asSequence() 356 | .flatMap { item -> valueSelectors.asSequence().map { it(item) } } 357 | .toList().toDoubleArray() 358 | 359 | return Nd4j.create( 360 | selectedValues, 361 | intArrayOf(list.size, valueSelectors.size) 362 | ) 363 | } 364 | ``` 365 | 366 | ND4J is efficient and great for linear algebra and matrix work. While its interface does not take advantage of Kotlin's features, you can easily create your own extensions to do so. Hopefully it is becoming clear that Kotlin can close a gap between object-oriented programming and tabular data work by exploiting modern functional programming patterns. 367 | 368 | You can learn more about ND4J here: 369 | https://github.com/deeplearning4j/nd4j 370 | 371 | Another library of interest is Koma, which is a matrix library specifically for Kotlin: 372 | https://github.com/kyonifer/koma 373 | 374 | ## 4.5 User Interfaces and Visualization with TornadoFX 375 | 376 | TornadoFX is a comprehensive user interface library for Kotlin built around JavaFX. User interfaces are traditionally messy to build, but TornadoFX streamlines and reduces the amount of effort significantly. 377 | 378 | https://github.com/edvin/tornadofx 379 | 380 | An entire eBook guide is availabe covering TornadoFX in detail: 381 | 382 | https://www.gitbook.com/book/edvin/tornadofx-guide/details 383 | 384 | In this example, we are simply going to display a `TableView` and a `ScatterChart` showing clustering of Patients and their white blood cell count. 385 | 386 | Make sure you have Java 1.8 configured for your Kotlin plug in like so, as JavaFX only exists in Java 8 and later. 387 | 388 | ```xml 389 | 390 | 391 | org.jetbrains.kotlin 392 | kotlin-maven-plugin 393 | ${kotlin.version} 394 | 395 | 1.8 396 | 397 | ... 398 | ``` 399 | 400 | Bring in TornadoFX and Kotlin-Statistics as dependencies: 401 | 402 | ```xml 403 | 404 | org.nield 405 | kotlinstatistics 406 | 0.3.0 407 | 408 | 409 | no.tornado 410 | tornadofx 411 | 1.7.10 412 | 413 | ``` 414 | 415 | For easy launching and integration with Intellij IDEA, you can use the TornadoFX plugin for Intellij IDEA: 416 | 417 | https://plugins.jetbrains.com/plugin/8339-tornadofx 418 | 419 | 420 | Here, we will use Kotlin-Statistics clustering functionality (which is backed by Apache Commons Math) and display it quickly in a `ScatterChart`. 421 | 422 | 423 | ```kotlin 424 | package com.oreilly 425 | 426 | import javafx.scene.chart.NumberAxis 427 | import org.nield.kotlinstatistics.multiKMeansCluster 428 | import tornadofx.* 429 | import java.time.LocalDate 430 | import java.time.temporal.ChronoUnit 431 | 432 | 433 | class MyApp: App(MyView::class) 434 | 435 | class MyView : View() { 436 | override val root = scatterchart("WBCC Clustering by Age", NumberAxis(), NumberAxis()) { 437 | 438 | patients.multiKMeansCluster(k = 3, 439 | maxIterations = 10000, 440 | trialCount = 50, 441 | xSelector = { it.age.toDouble() }, 442 | ySelector = { it.whiteBloodCellCount.toDouble() } 443 | ) 444 | .forEachIndexed { index, centroid -> 445 | series("Group ${index + 1}") { 446 | centroid.points.forEach { 447 | data(it.age, it.whiteBloodCellCount) 448 | } 449 | } 450 | } 451 | } 452 | } 453 | 454 | 455 | data class Patient(val firstName: String, 456 | val lastName: String, 457 | val gender: Gender, 458 | val birthday: LocalDate, 459 | val whiteBloodCellCount: Int) { 460 | 461 | val age = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 462 | } 463 | 464 | val patients = listOf( 465 | Patient("John", "Simone", Gender.MALE, LocalDate.of(1989, 1, 7), 4500), 466 | Patient("Sarah", "Marley", Gender.FEMALE, LocalDate.of(1970, 2, 5), 6700), 467 | Patient("Jessica", "Arnold", Gender.FEMALE, LocalDate.of(1980, 3, 9), 3400), 468 | Patient("Sam", "Beasley", Gender.MALE, LocalDate.of(1981, 4, 17), 8800), 469 | Patient("Dan", "Forney", Gender.MALE, LocalDate.of(1985, 9, 13), 5400), 470 | Patient("Lauren", "Michaels", Gender.FEMALE, LocalDate.of(1975, 8, 21), 5000), 471 | Patient("Michael", "Erlich", Gender.MALE, LocalDate.of(1985, 12, 17), 4100), 472 | Patient("Jason", "Miles", Gender.MALE, LocalDate.of(1991, 11, 1), 3900), 473 | Patient("Rebekah", "Earley", Gender.FEMALE, LocalDate.of(1985, 2, 18), 4600), 474 | Patient("James", "Larson", Gender.MALE, LocalDate.of(1974, 4, 10), 5100), 475 | Patient("Dan", "Ulrech", Gender.MALE, LocalDate.of(1991, 7, 11), 6000), 476 | Patient("Heather", "Eisner", Gender.FEMALE, LocalDate.of(1994, 3, 6), 6000), 477 | Patient("Jasper", "Martin", Gender.MALE, LocalDate.of(1971, 7, 1), 6000) 478 | ) 479 | 480 | enum class Gender { 481 | MALE, 482 | FEMALE 483 | } 484 | ``` 485 | 486 | We can also bring in an interactive `TableView`. While we are at it, let's bring in ControlsFX to leverage some Table filtering functionalities. 487 | 488 | ```xml 489 | 490 | org.controlsfx 491 | controlsfx 492 | 8.40.13 493 | 494 | ``` 495 | 496 | Now let's create a `TableView` of the Patients, and make them filterable by right-clicking a column. 497 | 498 | ```kotlin 499 | package com.oreilly 500 | 501 | import javafx.geometry.Orientation 502 | import javafx.scene.chart.NumberAxis 503 | import org.controlsfx.control.table.TableFilter 504 | import org.nield.kotlinstatistics.multiKMeansCluster 505 | import tornadofx.* 506 | import java.time.LocalDate 507 | import java.time.temporal.ChronoUnit 508 | 509 | 510 | class MyApp: App(MyView::class) 511 | 512 | class MyView : View() { 513 | override val root = splitpane { 514 | orientation = Orientation.HORIZONTAL 515 | 516 | tableview { 517 | column("FIRST NAME", Patient::firstName) 518 | column("LAST NAME", Patient::lastName) 519 | column("GENDER", Patient::gender) 520 | column("BIRTHDAY", Patient::birthday) 521 | column("AGE", Patient::age) 522 | column("WBCC", Patient::whiteBloodCellCount) 523 | 524 | items.setAll(patients) 525 | TableFilter.forTableView(this).apply() 526 | } 527 | 528 | scatterchart("WBCC Clustering by Age", NumberAxis(), NumberAxis()) { 529 | 530 | patients.multiKMeansCluster(k = 3, 531 | maxIterations = 10000, 532 | trialCount = 50, 533 | xSelector = { it.age.toDouble() }, 534 | ySelector = { it.whiteBloodCellCount.toDouble() } 535 | ) 536 | .forEachIndexed { index, centroid -> 537 | series("Group ${index + 1}") { 538 | centroid.points.forEach { 539 | data(it.age, it.whiteBloodCellCount) 540 | } 541 | } 542 | } 543 | } 544 | } 545 | } 546 | 547 | 548 | data class Patient(val firstName: String, 549 | val lastName: String, 550 | val gender: Gender, 551 | val birthday: LocalDate, 552 | val whiteBloodCellCount: Int) { 553 | 554 | val age = ChronoUnit.YEARS.between(birthday, LocalDate.now()) 555 | } 556 | 557 | val patients = listOf( 558 | Patient("John", "Simone", Gender.MALE, LocalDate.of(1989, 1, 7), 4500), 559 | Patient("Sarah", "Marley", Gender.FEMALE, LocalDate.of(1970, 2, 5), 6700), 560 | Patient("Jessica", "Arnold", Gender.FEMALE, LocalDate.of(1980, 3, 9), 3400), 561 | Patient("Sam", "Beasley", Gender.MALE, LocalDate.of(1981, 4, 17), 8800), 562 | Patient("Dan", "Forney", Gender.MALE, LocalDate.of(1985, 9, 13), 5400), 563 | Patient("Lauren", "Michaels", Gender.FEMALE, LocalDate.of(1975, 8, 21), 5000), 564 | Patient("Michael", "Erlich", Gender.MALE, LocalDate.of(1985, 12, 17), 4100), 565 | Patient("Jason", "Miles", Gender.MALE, LocalDate.of(1991, 11, 1), 3900), 566 | Patient("Rebekah", "Earley", Gender.FEMALE, LocalDate.of(1985, 2, 18), 4600), 567 | Patient("James", "Larson", Gender.MALE, LocalDate.of(1974, 4, 10), 5100), 568 | Patient("Dan", "Ulrech", Gender.MALE, LocalDate.of(1991, 7, 11), 6000), 569 | Patient("Heather", "Eisner", Gender.FEMALE, LocalDate.of(1994, 3, 6), 6000), 570 | Patient("Jasper", "Martin", Gender.MALE, LocalDate.of(1971, 7, 1), 6000) 571 | ) 572 | 573 | enum class Gender { 574 | MALE, 575 | FEMALE 576 | } 577 | ``` 578 | 579 | ![](http://i.imgur.com/z5KFuTZ.png) 580 | 581 | ![](http://i.imgur.com/3bi2mYk.png) 582 | 583 | Amazing, right? You can read about the handful of common chart types that are available in the TornadoFX Guide. 584 | 585 | https://edvin.gitbooks.io/tornadofx-guide/content/8.%20Charts.html 586 | 587 | While Kotlin's static typing may reduce the amount of flexibility you have in dynamically pivoting and analyzing data, it gives you a lot more safety and control without slowing you down severely. And you can deploy TornadoFX applications safely in production. 588 | 589 | 590 | ## 4.6 Deploying your Kotlin application 591 | 592 | To deploy your Kotlin application, you will likely deploy it as a JAR file. This JAR file can be used as a library/dependency in someone else's project. It can also be used as a standalone application, which in that case you have to specify the class holding your `main()` function or TornadoFX `App` instance. 593 | 594 | To create a single, self-contained JAR file with all dependencies packaged with it, use the Maven Shade plugin as specified below in your `pom.xml`. 595 | 596 | 597 | ```xml 598 | 599 | org.apache.maven.plugins 600 | maven-shade-plugin 601 | 2.3 602 | 603 | 604 | 605 | package 606 | 607 | shade 608 | 609 | 610 | 611 | 612 | 613 | com.oreilly.MyApp 614 | 615 | 616 | 617 | 618 | 619 | 620 | ``` 621 | 622 | Bring up the Maven panel, and double-click the `package` task to generate a JAR file as shown below. 623 | 624 | ![](http://i.imgur.com/9zXSJwz.png) 625 | 626 | This JAR file can be copied and used anywhere as long as it can connect to whatever data sources it needs to. Running the JAR file on any computer with Java 8 installed should launch the application. 627 | -------------------------------------------------------------------------------- /kotlin_project_template/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | com.oreilly 7 | kotlin-tutorial 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | com.oreilly kotlin-tutorial 12 | 13 | 14 | UTF-8 15 | 1.1.4-3 16 | 4.12 17 | 18 | 19 | 20 | 21 | org.jetbrains.kotlin 22 | kotlin-stdlib 23 | ${kotlin.version} 24 | 25 | 26 | org.jetbrains.kotlin 27 | kotlin-test-junit 28 | ${kotlin.version} 29 | test 30 | 31 | 32 | junit 33 | junit 34 | ${junit.version} 35 | test 36 | 37 | 38 | 39 | 40 | src/main/kotlin 41 | 42 | 43 | 44 | org.jetbrains.kotlin 45 | kotlin-maven-plugin 46 | ${kotlin.version} 47 | 48 | 49 | compile 50 | compile 51 | 52 | compile 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /kotlin_project_template/src/main/kotlin/com/oreilly/Hello.kt: -------------------------------------------------------------------------------- 1 | package com.oreilly 2 | 3 | 4 | fun main(args: Array) { 5 | println("Hello, World") 6 | } 7 | 8 | -------------------------------------------------------------------------------- /kotlin_project_template/src/test/kotlin/com/oreilly/HelloTest.kt: -------------------------------------------------------------------------------- 1 | package com.oreilly 2 | 3 | 4 | import org.junit.Test 5 | import kotlin.test.assertEquals 6 | 7 | class HelloTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /outline.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomasnield/oreilly_kotlin_for_data_science/5662a7d55d7f9bc1ad3d59a582ee9af0ce643cd8/outline.docx -------------------------------------------------------------------------------- /outline.md: -------------------------------------------------------------------------------- 1 | # Kotlin for Data Science 2 | ## Course Outline 3 | 4 | PART 1: From Data Science to Production, with Kotlin: The Basics 5 | 6 | 1) Why Kotlin? 7 | * What is Kotlin? 8 | * Why Kotlin for Data Science? 9 | * Comparison of Kotlin vs Scala and Python 10 | 11 | 2) Setup 12 | * JDK 13 | * Intellij IDEA 14 | * Maven Setup 15 | 16 | 3) Kotlin Basics 17 | * Your first Kotlin application 18 | * Variables 19 | * Types and Operators (+ - * / == !=) 20 | * Functions 21 | * Nullable Types 22 | * Project organization, navigation, and refactoring 23 | 24 | 4) Flow Control and Classes 25 | * `if` 26 | * `when` 27 | * Classes 28 | * Data classes 29 | * Singletons 30 | * Enums 31 | 32 | 5) Collections 33 | * Ranges and loops 34 | * Arrays 35 | * Lists 36 | * Sets 37 | * Maps 38 | * Collection Operators 39 | * Factory patterns 40 | 41 | ------------------------------------------------- 42 | 43 | PART 2: Practical Data Modeling for Production, with Kotlin 44 | 45 | 1) Working with Data Sources 46 | * Reading text Files 47 | * SQL queries 48 | * Web requests and Lazy Properties 49 | 50 | 2) Functional Programming with Kotlin 51 | * Higher Order Functions and lambdas 52 | * Lambda Syntax 53 | * Generics 54 | * Sequences 55 | * let() and apply() 56 | 57 | 3) Adapting Kotlin to Your Domain 58 | * Extension Functions and Properties 59 | * Extension Operators 60 | * Leveraging DSL’s 61 | 62 | 4) Practical applications of Kotlin for data science 63 | * Ranking mutual friends in a social network 64 | * Using Kotlin with Apache Spark 65 | * Using Kotlin-Statistics 66 | * Doing matrix math with ND4J (Java's NumPy) 67 | * Interactive UI's with TornadoFX 68 | * Deploying your Kotlin project 69 | 70 | 5) Going Forward 71 | * Furthering your knowledge of Kotlin and the JVM 72 | --------------------------------------------------------------------------------