Initial commit

This commit is contained in:
github-classroom[bot] 2022-05-15 19:55:34 +00:00
commit 37fbbe37ee
54 changed files with 12704 additions and 0 deletions

29
.editorconfig Normal file
View File

@ -0,0 +1,29 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Default formatting Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
# do not trim trailing whitespace in markdown files
[*.md]
trim_trailing_whitespace = false
# explicit 4 space indentation
[*.py]
indent_size = 4
# explicit 2 space indentation
[*.{json, yml, yaml, xml, ddl, sql}]
indent_size = 2
# windows specific files
[*.{bat, cmd}]
end_of_line = crlf

5
.gitattributes vendored Normal file
View File

@ -0,0 +1,5 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

14
.github/classroom/autograding.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"tests": [
{
"name": "Run PA unit tests",
"setup": "",
"run": "gradle :Streaming:test --info",
"input": "",
"output": "",
"comparison": "included",
"timeout": 10,
"points": 2
}
]
}

15
.github/workflows/classroom.yml vendored Normal file
View File

@ -0,0 +1,15 @@
name: GitHub Classroom Workflow
on: [push]
jobs:
build:
name: Autograding
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v1
with:
java-version: '17'
- uses: education/autograding@v1

70
.gitignore vendored Normal file
View File

@ -0,0 +1,70 @@
# InelliJ IDEA files
*.iml
*.ipr
*.ids
*.iws
.idea/
# Eclipse files
.project
.metadata
.classpath
.settings/
.loadpath
bin/
# Netbeans
nbactions.xml
# Visual Studio Code
.vscode
# Maven
target/
# gradle files
.gradle
build/
# ignore logfiles
*.log*
# OS dependant files
.DS_Store
.Spotlight-V100
.Trashes
Thumbs.db
Desktop.ini
*~
# Thumbnails
._*
# compiled files
*.com
*.class
*.dll
*.exe
*.o
*.so
# packages
*.7z
#*.jar
*.rar
*.zip
*.gz
*.bzip
*.xz
*.lzma
*~$*
# package managment formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
# databases
*.sqlite

309
README.adoc Normal file
View File

@ -0,0 +1,309 @@
:source-highlighter: coderay
:icons: font
:experimental:
:!sectnums:
:imagesdir: ./images/
:codedir: ./code/
:logo: IT.PROG2 -
ifdef::backend-html5[]
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
endif::[]
ifdef::backend-pdf[]
:logo:
endif::[]
ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]
// references
:java-api-functional: https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/function/package-summary.html
:stepik-functional-course: https://stepik.org/course/1595/syllabus[Java. Functional programming]
:chain-refactoring: https://refactoring.guru/design-patterns/chain-of-responsibility
:disjunction: https://en.wikipedia.org/wiki/Logical_disjunction#Truth_table
:stepik-func-identify-lambdas: https://stepik.org/lesson/34714/step/1?unit=14153[2.2 Identify the correct lambdas and method references]
:stepik-func-write-simple-lambdas: https://stepik.org/lesson/35020/step/1?unit=14444[2.3 Writing simple lambda expressions]
:stepik-func-too-many-arguments: https://stepik.org/lesson/42581/step/1?tunit=20812[2.4 Too many arguments]
:stepik-stream-calc-product: https://stepik.org/lesson/35224/step/1?unit=14582[2.5 Calculating production of all numbers in the range]
:stepik-stream-distinct-strings: https://stepik.org/lesson/36408/step/1?unit=15452[2.6 Getting distinct strings]
:stepik-func-closure: https://stepik.org/lesson/35833/step/1?unit=14996[2.7 Writing closures]
:stepik-func-replace-class: https://stepik.org/lesson/34715/step/1?unit=14154[3.2 Replacing anonymous classes with lambda expressions]
:stepik-func-match-interface: https://stepik.org/lesson/35021/step/1?unit=14446[3.3 Matching the functional interfaces]
:stepik-func-your-own: https://stepik.org/lesson/42582/step/1?unit=20813[3.5 Your own functional interface]
:stepik-stream-compose-function: https://stepik.org/lesson/35128/step/1?unit=14532[3.7 Composing predicates]
:stepik-chain: https://stepik.org/lesson/46943/step/1?unit=24990[3.9 The chain of responsibility pattern in the functional style]
:stepik-number-filter: https://stepik.org/lesson/36158/step/1?unit=15266[4.6 Numbers filtering]
:javadoc-intstream-concat: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/stream/IntStream.html#concat(java.util.stream.IntStream,java.util.stream.IntStream)[IntStream.concat]
:stepik-factorial: https://stepik.org/lesson/35849/step/1?unit=15014[4.8 Calculating a factorial]
:stepik-odd-numbers: https://stepik.org/lesson/36144/step/1?unit=15253[4.9 The sum of odd numbers]
:stepik-collector-product: https://stepik.org/lesson/35859/step/1?unit=15021[5.3 Collectors in practice: the product of squares]
:stepik-collector-transaction: https://stepik.org/lesson/36018/step/1?unit=15142[5.5 Almost like a SQL: the total sum of transactions by each account]
:java-tutorial-reduction: https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html[Tutorial über Reduction mit Streams]
= {logo} Praktikum Functional Programming
== Einleitung
Ziele dieses Praktikums sind:
* Sie können Lambda Expressions schreiben
* Sie können eigene funktionale Interfaces schreiben und verwenden
* Sie können `Optional` sinnvoll anwenden
* Sie kennen Methoden-Referenzen und können diese einsetzen
* Sie kennen die wichtigsten Klassen und Methoden aus `java.util.stream` und `java.util.function`
und können diese anwenden
=== Voraussetzungen
* Vorlesung Programmieren 2 Functional Programming
=== Tooling
* Installiertes JDK 17+
* Gradle 7.4+
=== Struktur
Das Praktikum enthält verschiedene Arten von Aufgaben, die wie folgt gekennzeichnet sind:
[TU] Theoretische Übung::
Dient der Repetition bzw. Vertiefung des Stoffes aus der Vorlesung und als Vorbereitung für die nachfolgenden Übungen.
[PU] Praktische Übung::
Übungsaufgaben zur praktischen Vertiefung von Teilaspekten des behandelten Themas.
[PA] Pflichtaufgabe::
Übergreifende Aufgabe zum Abschluss. Das Lösen dieser Aufgaben ist Pflicht. Sie muss bis zum definierten Zeitpunkt abgegeben werden, wird bewertet und ist Teil der Vornote.
=== Zeit und Bewertung
Für das Praktikum stehen die Wochen gemäss den Angaben in Moodle zur Verfügung. +
Je nach Kenntnis- und Erfahrungsstufe benötigen Sie mehr oder weniger Zeit.
Nutzen Sie die Gelegenheit den Stoff zu vertiefen, auszuprobieren, Fragen zu stellen und Lösungen zu diskutieren.
Falls Sie das Thema schon beherrschen, müssen Sie nur die Pflichtaufgaben lösen und
bis zum angegebenen Zeitpunkt abgeben (Fast-Track).
Die Pflichtaufgaben werden mit 0 bis 2 Punkten bewertet (siehe _Leistungsnachweise_ auf Moodle).
[TIP]
Auch wenn Sie das Thema schon beherrschen, prüfen Sie bitte Ihr Wissen über
das Design Pattern _Chain of responsibility_.
:sectnums:
:sectnumlevels: 1
// Beginn des Aufgabenblocks
== Functional Interfaces [TU]
[NOTE]
Sie können Textantworten in der Datei `solutions-sheet.adoc` (eine Muster-Datei ist im Code-Verzeichnis) oder
`solutions-sheet.md` im Root-Verzeichnis der Übung sammeln.
Java bietet für viele Zwecke im {java-api-functional}[Package java.util.functional] Functional Interfaces.
[loweralpha]
. Welche Interfaces aus dem Package `java.util.function` können Sie alles nutzen, um
- die mathematische Funktion f(x) = x ^ 2 - 3 für Zahlen des Typs `long` abzubilden?
- um den Zinsfaktor (double) für n (int) Jahre bei einem Zinssatz von p Prozent (float) zu berechnen mit der Formel
zf = (1 + p / 100)^n ?
- ein Objekt vom Typ `Person` (ohne Parameter) zu generieren?
. Welche Eigenschaft muss eine Funktion haben, damit Sie ein eigenes Interface schreiben müssen,
also keines der in `java.util.function` vorhandenen Interfaces verwenden können?
. Welche der Aussagen stimmen für ein funktionales Interface?
** [ ] Es ist ein Java-Interface (Schlüsselwort `interface` im Code)
** [ ] Es hat **genau eine** abstrakte Methode
** [ ] Das Interface **muss** mit `@FunctionalInterface` markiert sein
** [ ] Es hat **keine** default-Methoden (Schlüsselwort `default`)
. Welche Aussagen stimmen?
** [ ] Zu **jedem** funktionalen Interface können Lambda-Ausdrücke (_lambda expressions_) geschrieben werden
** [ ] Ein Lambda-Ausdruck kann **ohne** passendes funktionales Interface erstellt werden
** [ ] Eine Variable vom Typ `Optional` kann nie `null` sein.
== Übungen auf der Stepik-Plattform [PU]
Starten Sie den Kurs {stepik-functional-course}. Dazu müssen Sie dort ein Konto anlegen.
Die Plattform ist von der ZHAW unabhängig.
[TIP]
Sie können dort alle Aufgaben direkt im Browser lösen.
Oft ist es aber zweckmässig, den Code in die IDE zu übernehmen und die Lösung dort zu entwickeln.
Auf dieser Plattform wird Ihre Lösung online geprüft und Sie erhalten Feedback, ob Ihre Lösung alle Tests erfüllt.
[TIP]
Wenn Sie eine funktionierende Lösung abgegeben haben, erhalten Sie Zugriff auf Kommentare und Lösungen anderer Personen.
Vergleichen Sie Ihre Lösung, Sie können viel von anderen Lösungen lernen.
=== Übungen zu Functional Interface und Lambda Expression
Lösen Sie die folgenden Übungen:
[loweralpha]
. {stepik-func-identify-lambdas}
. {stepik-func-write-simple-lambdas}
. {stepik-func-too-many-arguments}
. {stepik-func-closure}
. {stepik-func-replace-class}
. {stepik-func-match-interface}
. {stepik-func-your-own}
=== Übungen mit Streams
[loweralpha, start=8]
. Lösen Sie {stepik-stream-calc-product}
+
Tipp: Verwenden Sie die passend Methode `.reduce(...)`
. Lösen Sie {stepik-stream-distinct-strings}
. Lösen Sie die Übung {stepik-stream-compose-function}.
Die Aufgabe verlangt, dass Sie ein `IntPredicate` erstellen, das alle `IntPredicate` aus übergebenen Liste `predicates` mit der {disjunction}[Oder-Funktion (or)] verknüpft.
Eine mögliche Lösung ist
+
[source, Java]
----
class Predicate {
public static IntPredicate disjunctAll(List<IntPredicate> predicates) {
IntPredicate disjunct = x -> false;
for(IntPredicate currentPredicate: predicates) {
disjunct = disjunct.or(currentPredicate);
}
return disjunct;
}
}
----
+
Eine Anwendung könnte sein:
+
[source, Java]
----
class Predicate {
public static void main(String[] args) {
IntPredicate isEven = x -> x % 2 == 0;
IntPredicate isDividableBy3 = x -> x % 3 == 0;
List<IntPredicate> predicateList = List.of(isEven, isDividableBy3);
IntPredicate disPredicate = disjunctAll(predicateList);
IntStream.range(1, 10).forEach(i ->
System.out.printf("%2d -> %s%n", i, disPredicate.test(i)));
}
}
----
+
Suchen Sie jedoch eine Lösung, die mit Streams arbeitet. Sie finden Tests und ein Gerüst für die
Aufgabe in `code/Stepik` in der Klasse `ComposingPredicate`.
[TIP]
Wenn Sie eine Lösung gefunden haben,
überlegen Sie sich, wie viele Funktionen (`IntPredicate`) beim Aufruf von `.test()` ausgewertet werden.
Lässt sich dies reduzieren?
. Lösen Sie die folgenden Aufgaben mit Streams:
** {stepik-number-filter} - beachten Sie die Methode {javadoc-intstream-concat}
** {stepik-factorial}
** {stepik-odd-numbers}
** {stepik-collector-product}
+
In den Folien der Vorlesung sind die `Stream.reduce()`-Methoden aufgeführt.
In der Aufgabe wird aber ``Stream.collect(``_collector_``)`` verwendet und Sie müssen nur den _collector_ angeben.
Die entsprechenden Funktionen in der Collectors-Klasse heissen `Collectors.reducing()`.
Ihre Lösung lautet also `Collectors.reducing(...)`
** {stepik-collector-transaction}
+
Tipp: Auch wenn steht, dass die Form `Collectors.reducing` verwendet werden kann, ist die Methode `reducing` nicht die Lösung, sie benötigen eine andere Methode aus der Klasse `Collectors`.
== Design Pattern _Chain of responsibility_ [PU]
Lernen Sie das Pattern {chain-refactoring}[Chain of Responsibility] kennen.
In der Übung {stepik-chain} setzen Sie dieses Pattern funktional um.
[TIP]
Das ist eine aufwändige Aufgabe, nehmen Sie sich Zeit dafür.
== Company Payroll [PA]
[CAUTION]
Bei dieser Aufgabe geht es darum alles mit Streams zu lösen.
Verwenden Sie keine for-, do-, oder while-Schleifen.
Im Package `ch.zhaw.prog2.functional.streaming` finden Sie einige Klassen. Diese ermöglichen einer Firma den Angestellten die Löhne auszubezahlen.
Zu den Klassen sind auch passende Tests für die Klassen vorhanden.
Für die Tests werden die Objekte mit generierten Daten angereichert.
[TIP]
====
Sie sollen nur die folgenden Klassen anpassen:
* `Company`
* `PayrollCreator`
* `PayrollCreatorTest**Student**` (do not modify `PayrollCreatorTest`)
* `CompanyTest**Student**` (do not modify `CompanyTest`)
====
Lösen Sie mit Hilfe von Streams und basierend auf diesem existierenden Code die folgenden Aufgaben:
[loweralpha]
. Mit `Company.allEmployees()` erhalten Sie alle Angestellten.
+
Implementieren Sie die Methoden `Company.getDistinctFirstnamesOfEmployees()` und
`Company.getDistinctLastnamesOfEmployees()`.
+
Die dazugehörigen Tests sind in `CompanyTest` bereits vorhanden.
+
[TIP]
Die Implementation benötigt keine Hilfsvariablen.
Sie können die Implementation mit `return getAllEmployees().stream()` starten.
. Mit `Employee.isWorkingForCompany` können Sie prüfen, ob der Angestellte noch für die Firma tätig ist.
Implementieren Sie `Company.getEmployeesWorkingForCompany()`. +
Der dazugehörige Test ist in `CompanyTest` bereits vorhanden.
. Als Nächstes sollen alle Angestellten mit dem Attribut `Employee.isFemale` ermittelt werden.
Da dies zu ähnlichem Code wie in der vorherigen Aufgabe führt, realisieren Sie eine generischere Methode `Company.getEmployeesByPredicate(Predicate<Employee>)`.
Die dazugehörigen Tests schreiben Sie in der Testklasse `CompanyTestStudent`.
Als Tests schlage ich vor zu prüfen, ob die Summe der Angestellten mit dem Attribut `isFemale` und ohne dieses Attribut gleich der Summe aller Angestellten ist.
. Nachdem `Company` uns Methoden für den Zugriff auf die Angestellten bietet, kümmern wir uns um die Lohnzahlungen.
Die Klasse `Payroll` sammelt `Payment` in einer Liste.
In der Klasse `PayrollCreator` schreiben Sie die dazu nötigen Methoden.
+
Implementieren Sie die Methode `PayrollCreator.getPayrollForAll()`, die eine `Payroll` für alle Angestellten erstellt, für die `Employee.isWorkingForCompany` gesetzt ist.
Verwenden Sie die Methode `Company.getPayments`.
+
Einen passenden Test finden Sie in `PayrollCreatorTest`.
. Wie hoch ist nun die Lohnsumme?
Implementieren Sie `PayrollCreator.payrollValueCHF()`.
+
Da verschiedene Währungen verwendet werden, müssen die `Payment` mit der Methode `CurrencyChange.getInNewCurrency` zu CHF gewandelt werden.
. Nun sollen noch die Summen pro Währung ermittelt werden.
Implementieren Sie die Methode `PayrollCreator.payrollAmountByCurrency`.
+
Ein Ansatz dazu kann Ihnen das {java-tutorial-reduction} geben.
+
Schreiben Sie einen Test dazu in `PayrollCreatorTestStudent`. Verwenden Sie Mocking.
Ein Positiv-Test, der prüft, dass die Währungen bei der Summenbildung korrekt berücksichtigt werden, reicht für diese Aufgabe aus.
. In der Methode `Company.getPayments(Predicate)` ist bisher nicht berücksichtigt, dass der 13. Monatslohn nicht gleichmässig über das Jahr ausbezahlt wird.
+
[NOTE]
Bei einer Anstellung mit einem 13. Monatslohn wird zu den 12 monatlichen Lohnzahlungen ein weiteres Monatsgehalt ausbezahlt.
Das monatliche Gehalt ist dann 1/13 des Jahresgehalts.
In der Regel wird der 13. Monatslohn im Dezember ausbezahlt.
+
Der 13. Monatslohn soll nur im Dezember ausbezahlt werden.
Zudem gibt es gelegentlich andere Anpassungen, z.B. 5% firmenweite Sondervergütung.
Um dies flexibel definieren zu können, soll die anzuwendende Lohnberechnung in einer Funktion übergeben werden.
+
Orientieren Sie sich an der Funktion `Company.getPayments(Predicate)` und implementieren Sie `Company.getPayments(Predicate, Function)`.
+
Implementieren Sie auch die dazu passenden Funktionen `Company.paymentForEmployeeDecember`
für Zahlungen mit dem 13. Monatslohn und `Company.paymentForEmployeeJanuary` für Zahlungen ohne 13. Monatslohn.
Die dazu nötigen Deklarationen finden Sie in `Company` am Anfang der Klasse.
// Ende des Aufgabenblocks
:!sectnums:
== Abschluss
Stellen Sie sicher, dass die Tests mit `gradle test` erfolgreich laufen und pushen Sie die Lösung vor der Deadline in Ihr Abgaberepository.

51
code/Stepik/build.gradle Normal file
View File

@ -0,0 +1,51 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
}
// Project/Module information
description = 'Lab06 Stepik'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
// Junit 5 dependencies
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
// Mockito dependencies
testImplementation 'org.mockito:mockito-core:4.3.+'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
// Output results of individual tests
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}

View File

@ -0,0 +1,30 @@
package ch.zhaw.prog2.functional.stepik;
import java.util.List;
import java.util.function.IntPredicate;
public class ComposingPredicate {
/**
* Write a solution which is using streams.
*
* @see #disjunctAllNoStream(List)
*/
public static IntPredicate disjunctAll(List<IntPredicate> predicates) {
throw new UnsupportedOperationException(); // TODO: remove this line and implement your solution
}
/**
* Classical implementation provided by lecturer to help you solve this exercise.
* <p>
* This solution works, but you have to search a solution using streams which will lead you
* to a solution with less lines of code.
*/
public static IntPredicate disjunctAllNoStream(List<IntPredicate> predicates) {
IntPredicate disjunct = x -> false;
for (IntPredicate currentPredicate : predicates) {
disjunct = disjunct.or(currentPredicate);
}
return disjunct;
}
}

View File

@ -0,0 +1,58 @@
package ch.zhaw.prog2.functional.stepik;
import ch.zhaw.prog2.functional.stepik.ComposingPredicate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.*;
/**
* Do not modify this test class
*/
class ComposingPredicateTest {
private static final IntPredicate isEven = x -> x % 2 == 0;
private static final IntPredicate isDividableBy3 = x -> x % 3 == 0;
private static final List<IntPredicate> predicateList = List.of(isEven, isDividableBy3);
private List<Integer> expected;
private IntStream testIntegers;
/*
* This tests your solution
*/
@Test
@Disabled("This exercise is not mandatory. Enable it, if you solve this exercise.")
void disjunctAll() {
assertDoesNotThrow(
() -> ComposingPredicate.disjunctAll(List.of(x -> true)),
"You have to implement ComposingPredicate.disjunctAll"
);
IntPredicate alwaysTrue = ComposingPredicate.disjunctAll(List.of(x -> true));
assertTrue(alwaysTrue.test(1), "Test with one predicate only");
IntPredicate dividableBy2Or3 = ComposingPredicate.disjunctAll(predicateList);
assertArrayEquals(expected.toArray(), testIntegers.filter(dividableBy2Or3).boxed().toArray());
}
/*
* This tests the given classical solution without streams
*/
@Test
void disjunctAllNoStream() {
IntPredicate alwaysTrue = ComposingPredicate.disjunctAllNoStream(List.of(x -> true));
assertTrue(alwaysTrue.test(1), "Test with one predicate only");
IntPredicate dividableBy2Or3 = ComposingPredicate.disjunctAllNoStream(predicateList);
assertArrayEquals(expected.toArray(), testIntegers.filter(dividableBy2Or3).boxed().toArray());
}
@BeforeEach
void setUp() {
testIntegers = IntStream.range(1, 10);
expected = List.of(2, 3, 4, 6, 8, 9);
}
}

View File

@ -0,0 +1,51 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
}
// Project/Module information
description = 'Lab06 Streaming'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
// Junit 5 dependencies
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
// Mockito dependencies
testImplementation 'org.mockito:mockito-core:4.3.+'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
// Output results of individual tests
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}

View File

@ -0,0 +1,122 @@
package ch.zhaw.prog2.functional.streaming;
import ch.zhaw.prog2.functional.streaming.finance.CurrencyAmount;
import ch.zhaw.prog2.functional.streaming.finance.Payment;
import ch.zhaw.prog2.functional.streaming.humanresource.Employee;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* This classe models a Company with all its Employees.
* There might be Employees not working for the Company (e.g. temporally)
* This class should be worked on by students.
*/
public class Company {
private final List<Employee> employeeList;
public Company(List<Employee> employeeList) {
Objects.requireNonNull(employeeList);
this.employeeList = employeeList;
}
/**
* This method is provided by lecturer - do not change
* Getter for all employees.
*
* @return List of employees, never {@code null}
*/
public List<Employee> getAllEmployees() {
return Collections.unmodifiableList(employeeList);
}
/*
* Aufgabe a1)
*/
public List<String> getDistinctFirstnamesOfEmployees() {
return null;
}
/*
* Aufgabe a2)
*/
public String[] getDistinctLastnamesOfEmployees() {
return null;
}
/*
* Aufgabe b)
* There might be Employees not working for the Company (e.g. temporally)
*/
public List<Employee> getEmployeesWorkingForCompany() {
return null;
}
/*
* Aufgabe c) - Test in Klasse CompanyTestStudent
*/
public List<Employee> getEmployeesByPredicate(Predicate<Employee> filterPredicate) {
return null;
}
/**
* This method is provided by lecturer - do not change
* Create List of payments for employees which are selected by the employeePredicate
*
* @param employeePredicate Predicate-Function that returns true for all Employee which
* get a payment
* @return list of Payments
*/
public List<Payment> getPayments(Predicate<Employee> employeePredicate) {
List<Payment> paymentList = new ArrayList<>();
for(Employee employee: employeeList) {
if (employeePredicate.test(employee)) {
Payment payment = new Payment();
CurrencyAmount salary = employee.getYearlySalary();
int paymentsPerYear = employee.getPaymentsPerYear().getValue();
salary = salary.createModifiedAmount(amount -> amount / paymentsPerYear);
payment.setCurrencyAmount(salary).setBeneficiary(employee).setTargetAccount(employee.getAccount());
paymentList.add(payment);
}
}
return paymentList;
}
/**
* Aufgabe g1)
*
* This Method calculates a List of Payments using a (delegate) Function.
* @param employeePredicate - predicate for Employees eligible for a Payements
* @param paymentForEmployee - (delegate) Function Calculating a Payment for an Employee
* @return a List of Payments based on predicate and payment function
*/
public List<Payment> getPayments(Predicate<Employee> employeePredicate, Function<Employee, Payment> paymentForEmployee) {
return null;
}
/**
* Aufgabe g2)
*
* Function calculating Payment for January.
*/
public static final Function<Employee, Payment> paymentForEmployeeJanuary = employee -> {
return null;
};
/*
* Aufgabe g3)
*
* Fuction calculating Payment for December, where Employees having 13 Payments per year will get the double amount.
*/
public static final Function<Employee, Payment> paymentForEmployeeDecember = employee -> {
return null;
};
}

View File

@ -0,0 +1,64 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Currency;
/**
* Information needed to transfer money: IBAN-Number and Currency
*/
public class BankAccount {
public static final String RELAXED_IABN_REGEX = "[A-Z][A-Z][0-9][0-9][A-Z0-9]{1,30}";
private Currency currency = Currency.getInstance("CHF");
private String ibanNumber;
/**
* Check if {@link #setIbanNumber(String) setIbanNumber} will accept the given ibanNumber.
*
* @param ibanNumber the IBAN Number to check
* @return true, if ibanNumber will be accepted
*/
public static boolean isIbanAccepted(String ibanNumber) {
return removeSpaces(ibanNumber).matches(RELAXED_IABN_REGEX);
}
private static String removeSpaces(String in) {
return in.replace(" ", "");
}
public Currency getCurrency() {
return currency;
}
public BankAccount setCurrency(Currency currency) {
this.currency = currency;
return this;
}
public String getIbanNumber() {
return ibanNumber;
}
/**
* Setter method
*
* @param ibanNumber must be acceptable, see {@link #isIbanAccepted(String)}
* @return this
* @throws IllegalIbanNumber if ibanNumber can not be accepted
*/
public BankAccount setIbanNumber(String ibanNumber) throws IllegalIbanNumber {
if (isIbanAccepted(ibanNumber)) {
this.ibanNumber = ibanNumber;
} else {
throw new IllegalArgumentException("IBAN is not accepted");
}
return this;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("BankAccount{");
sb.append("currency=").append(currency);
sb.append(", ibanNumber='").append(ibanNumber).append('\'');
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,55 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Currency;
import java.util.StringJoiner;
import java.util.function.IntUnaryOperator;
/**
* Bind currency to the amount
*/
public class CurrencyAmount {
public static final Currency CHF = Currency.getInstance("CHF");
private final int amount;
private final Currency currency;
public CurrencyAmount(int amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
/**
* Use {@link #CHF} as currency
*
* @param amount
*/
public CurrencyAmount(int amount) {
this(amount, CHF);
}
public int getAmount() {
return amount;
}
public Currency getCurrency() {
return currency;
}
/**
* Creates a new CurrencyAmount based on this object, modified by the modifying function.
* @param modifyFunction - function to modify this amount.
* @return the new modified CurrencyAmount.
*/
public CurrencyAmount createModifiedAmount(IntUnaryOperator modifyFunction) {
int newAmount = modifyFunction.applyAsInt(this.amount);
return new CurrencyAmount(newAmount, this.currency);
}
@Override
public String toString() {
return new StringJoiner(", ", CurrencyAmount.class.getSimpleName() + "[", "]")
.add("amount=" + amount)
.add("currency=" + currency)
.toString();
}
}

View File

@ -0,0 +1,42 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Currency;
import java.util.Map;
import java.util.Objects;
/**
* Helper class to change currency
*/
public class CurrencyChange {
private static final Map<String, Double> CURRENCYISOCODE_FACTOR_TO_CHF = Map.of(
"CHF", 1.00,
"USD", 1.04,
"GBP", 0.83,
"EUR", 0.94
);
private static double factorFor(String fromIsoCode, String toIsoCode) {
return CURRENCYISOCODE_FACTOR_TO_CHF.get(toIsoCode) / CURRENCYISOCODE_FACTOR_TO_CHF.get(fromIsoCode);
}
/**
* Convert to a new Currency
*
* Example:
* <code>
* CurrencyAmount old = new CurrencyAmount (12345, Currency.getInstance("EUR"));
* CurrencyAmount newCurrencyAmount = CurrencyChange.getInNewCurrency(old, Currency.getInstance("USD"));
* </code>
*
* @param currencyAmount an amount in given currency
* @param newCurrency the target currency
* @return new instance with an equivalent value but in the new currency
*/
public static CurrencyAmount getInNewCurrency(CurrencyAmount currencyAmount, Currency newCurrency) {
Objects.requireNonNull(currencyAmount);
Objects.requireNonNull(newCurrency);
double factor = factorFor(currencyAmount.getCurrency().getCurrencyCode(), newCurrency.getCurrencyCode());
long newAmount = Math.round(currencyAmount.getAmount() * factor);
return new CurrencyAmount((int) newAmount, newCurrency);
}
}

View File

@ -0,0 +1,7 @@
package ch.zhaw.prog2.functional.streaming.finance;
/**
* Thrown when an illegal IBAN (International Bank Account Number) is provided.
*/
public class IllegalIbanNumber extends Throwable {
}

View File

@ -0,0 +1,55 @@
package ch.zhaw.prog2.functional.streaming.finance;
import ch.zhaw.prog2.functional.streaming.humanresource.Person;
import java.util.Optional;
import java.util.StringJoiner;
/**
* All information needed to pay an amount to a BankAccount of a Person.
*/
public class Payment {
private BankAccount targetAccount;
private CurrencyAmount currencyAmount;
private Person beneficiary;
public BankAccount getTargetAccount() {
return targetAccount;
}
public Payment setTargetAccount(BankAccount targetAccount) {
this.targetAccount = targetAccount;
return this;
}
public Payment setTargetAccount(Optional<BankAccount> targetAccount) {
return setTargetAccount(targetAccount.orElse(null));
}
public CurrencyAmount getCurrencyAmount() {
return currencyAmount;
}
public Payment setCurrencyAmount(CurrencyAmount currencyAmount) {
this.currencyAmount = currencyAmount;
return this;
}
public Person getBeneficiary() {
return beneficiary;
}
public Payment setBeneficiary(Person beneficiary) {
this.beneficiary = beneficiary;
return this;
}
@Override
public String toString() {
return new StringJoiner(", ", Payment.class.getSimpleName() + "[", "]")
.add("targetAccount=" + targetAccount)
.add("currencyAmount=" + currencyAmount)
.add("beneficiary=" + beneficiary)
.toString();
}
}

View File

@ -0,0 +1,16 @@
package ch.zhaw.prog2.functional.streaming.finance;
public enum PaymentsPerYear {
TWELVE(12),
THIRTEEN(13);
private final int value;
PaymentsPerYear(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

View File

@ -0,0 +1,59 @@
package ch.zhaw.prog2.functional.streaming.finance;
import ch.zhaw.prog2.functional.streaming.humanresource.Person;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Stream;
/**
* A Payroll (Lohnabrechnung) is principally a List of Payments for the Company.
* In this Payroll only one Payment for each beneficiary is allowed
*/
public class Payroll {
private final List<Payment> paymentList = new ArrayList<>();
public List<Payment> getPaymentList() {
return Collections.unmodifiableList(paymentList);
}
/**
* This Method will add more Payments to this Payroll an throw an IllegalArgumentException
* if we already have a Payment beloging to the same Person in this Payroll.
* @param morePayments
*/
public void addPayments(List<Payment> morePayments) {
if (hasSameBeneficiaryInefficient(morePayments)) {
throw new IllegalArgumentException("Duplicate Beneficiary detected");
} else {
paymentList.addAll(morePayments);
}
}
// this method is inefficient and should be rewritten by staff (not students)
private boolean hasSameBeneficiaryInefficient(List<Payment> paymentListToVerify) {
boolean res = false;
for (Payment payment : paymentListToVerify) {
Person beneficiary = payment.getBeneficiary();
for (Payment checkPayment : paymentList) {
if (beneficiary.equals(checkPayment.getBeneficiary())) {
res = true;
}
}
}
return res;
}
public Stream<Payment> stream() {
return paymentList.stream();
}
@Override
public String toString() {
return new StringJoiner(", ", Payroll.class.getSimpleName() + "[", "]")
.add("paymentList=" + paymentList)
.toString();
}
}

View File

@ -0,0 +1,46 @@
package ch.zhaw.prog2.functional.streaming.finance;
import ch.zhaw.prog2.functional.streaming.Company;
import java.util.List;
/**
* This Class creates a Payroll (Lohabrechnung) for a whole Company
* and supplies some Utility Methods for a a Payroll.
* This class should be worked on by students.
*/
public class PayrollCreator {
private final Company company;
/**
* Opens a Payroll for a company.
* @param company
*/
public PayrollCreator(Company company) {
this.company = company;
}
/*
* Aufgabe d) - Test dazu exisitert in PayrollCreatorTest
*/
public Payroll getPayrollForAll() {
return new Payroll();
}
/*
* Aufgabe e) - Test dazu existiert in PayrollCreatorTest
*/
public static int payrollValueCHF(Payroll payroll) {
return 0;
}
/**
* Aufgabe f) - schreiben Sie einen eigenen Test in PayrollCreatorTestStudent
* @return a List of total amounts in this currency for each currency in the payroll
*/
public static List<CurrencyAmount> payrollAmountByCurrency(Payroll payroll) {
return null;
}
}

View File

@ -0,0 +1,73 @@
package ch.zhaw.prog2.functional.streaming.humanresource;
import ch.zhaw.prog2.functional.streaming.finance.BankAccount;
import ch.zhaw.prog2.functional.streaming.finance.CurrencyAmount;
import ch.zhaw.prog2.functional.streaming.finance.PaymentsPerYear;
import java.util.Optional;
public class Employee extends Person {
private CurrencyAmount yearlySalary;
private PaymentsPerYear paymentsPerYear = PaymentsPerYear.THIRTEEN;
private BankAccount account;
private boolean isWorkingForCompany;
public Employee(String firstName, String lastName) {
super(firstName, lastName);
}
/**
* There might be Employees not working for the Company (e.g. temporally)
*/
public boolean isWorkingForCompany() {
return isWorkingForCompany;
}
/**
* There might be Employees not working for the Company (e.g. temporally)
* @param workingForCompany true if working
* @return
*/
public Employee setWorkingForCompany(boolean workingForCompany) {
isWorkingForCompany = workingForCompany;
return this;
}
public CurrencyAmount getYearlySalary() {
return yearlySalary;
}
public Employee setYearlySalary(CurrencyAmount yearlySalary) {
this.yearlySalary = yearlySalary;
return this;
}
public PaymentsPerYear getPaymentsPerYear() {
return paymentsPerYear;
}
public Employee setPaymentsPerYear(PaymentsPerYear paymentsPerYear) {
this.paymentsPerYear = paymentsPerYear;
return this;
}
public Optional<BankAccount> getAccount() {
return Optional.ofNullable(account);
}
public Employee setAccount(BankAccount account) {
this.account = account;
return this;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Employee{");
sb.append("yearlySalaryCHF=").append(yearlySalary);
sb.append(", paymentsPerYear=").append(paymentsPerYear);
sb.append(", account=").append(account);
sb.append(", person=").append(super.toString());
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,104 @@
package ch.zhaw.prog2.functional.streaming.humanresource;
import java.util.StringJoiner;
import java.util.UUID;
/**
* Information about a person.
*/
public class Person {
private final String firstName;
private final String lastName;
private boolean isFemale;
private boolean isAlive = true;
private UUID uuid;
private Person father;
private Person mother;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
constructNonFinalFields();
}
private void constructNonFinalFields() {
this.uuid = UUID.randomUUID();
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public UUID getUuid() {
return uuid;
}
public boolean isAlive() {
return isAlive;
}
public Person setAlive(boolean alive) {
isAlive = alive;
return this;
}
public Person getFather() {
return father;
}
/**
* @param father, can be {@code null}
* @return this
*/
public Person setFather(Person father) {
this.father = father;
return this;
}
public Person getMother() {
return mother;
}
/**
* @param mother, can be {@code null}
* @return this
*/
public Person setMother(Person mother) {
this.mother = mother;
return this;
}
/**
* @return persons name, never {@code null}
*/
public String getName() {
return firstName + " " + lastName;
}
public boolean isFemale() {
return isFemale;
}
public Person setFemale(boolean female) {
isFemale = female;
return this;
}
@Override
public String toString() {
return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
.add("firstName='" + firstName + "'")
.add("lastName='" + lastName + "'")
.add("isFemale=" + isFemale)
.add("isAlive=" + isAlive)
.add("uuid=" + uuid)
.add("father=" + father)
.add("mother=" + mother)
.toString();
}
}

View File

@ -0,0 +1,28 @@
package ch.zhaw.prog2.functional.streaming;
import ch.zhaw.prog2.functional.streaming.humanresource.Employee;
import ch.zhaw.prog2.functional.streaming.humanresource.EmployeeSupplier;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CompanySupplier implements Supplier<Company> {
private final int employeeCount;
private final EmployeeSupplier employeeSupplier;
public CompanySupplier(Random random, int employeeCount) {
Objects.requireNonNull(random);
this.employeeCount = employeeCount;
employeeSupplier = new EmployeeSupplier(random);
}
@Override
public Company get() {
List<Employee> employeeList = Stream.generate(employeeSupplier).limit(employeeCount).collect(Collectors.toList());
return new Company(employeeList);
}
}

View File

@ -0,0 +1,102 @@
package ch.zhaw.prog2.functional.streaming;
import ch.zhaw.prog2.functional.streaming.finance.Payment;
import ch.zhaw.prog2.functional.streaming.humanresource.Employee;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.function.Predicate;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
class CompanyTest {
// These variables are not private because some tests are in the class CompanyTestStudent.
static final long RANDOM_SEED = 113L;
static final int EMPLOYEE_COUNT = 500;
private Company testCompany;
@BeforeEach
void setUp() {
Random random = new Random(RANDOM_SEED);
CompanySupplier companySupplier = new CompanySupplier(random, EMPLOYEE_COUNT);
testCompany = companySupplier.get();
}
@Test
void constructor() {
assertThrows(NullPointerException.class,
() -> new Company(null), "null in constuctor not allowed");
List<Employee> employeeList = testCompany.getAllEmployees();
assertNotNull(employeeList);
assertEquals(EMPLOYEE_COUNT, testCompany.getAllEmployees().size());
}
@Test
void getAllEmployees() {
assertEquals(EMPLOYEE_COUNT, testCompany.getAllEmployees().size(), "testCompany has given count of employees");
}
@Test
void getPayments() {
List<Payment> paymentList = testCompany.getPayments(employee -> false);
assertNotNull(paymentList, "You have to implement the tested method");
assertEquals(0, paymentList.size(), "No payments if Predicate evaluates always true");
}
@Test
void getDistinctFirstnamesOfEmployees() {
List<String> names = testCompany.getDistinctFirstnamesOfEmployees();
assertNotNull(names, "You have to implement the tested method");
assertEquals(28, names.size(), "default company has given number of distinct first names");
}
@Test
void getDistinctLastnamesOfEmployees() {
String[] names = testCompany.getDistinctLastnamesOfEmployees();
assertNotNull(names, "You have to implement the tested method");
assertEquals(21, names.length, "default company has given number of distinct last names");
}
@Test
void getEmployeesWorkingForCompany() {
List<Employee> workingEmployees = testCompany.getEmployeesWorkingForCompany();
assertNotNull(workingEmployees, "You have to implement the tested method");
assertTrue(workingEmployees.size() >= 400, "default company has at least 400 working employees");
}
@Test
void testGetPayments() {
Payment dummyPayment = mock(Payment.class);
List<Payment> paymentList = testCompany.getPayments(employee -> false, employee -> dummyPayment);
assertNotNull(paymentList, "You have to implement the tested method");
assertEquals(List.of(), paymentList, "no employees");
Predicate<Employee> allEmployee = employee -> true;
paymentList = testCompany.getPayments(allEmployee, employee -> dummyPayment);
assertEquals(EMPLOYEE_COUNT, paymentList.size(), "every employee gets payment");
assertTrue(paymentList.stream().allMatch(payment -> payment == dummyPayment), "all payments are dummy payments");
long januaryAmountSum = getAmountSum(testCompany.getPayments(allEmployee, Company.paymentForEmployeeJanuary));
long decemberAmountSum = getAmountSum(testCompany.getPayments(allEmployee, Company.paymentForEmployeeDecember));
long sumByMonth = 11 * januaryAmountSum + decemberAmountSum;
Function<Employee, Payment> yearlySalary = employee -> new Payment()
.setCurrencyAmount(employee.getYearlySalary())
.setBeneficiary(employee);
paymentList = testCompany.getPayments(employee -> true, yearlySalary);
long yearlyAmountSum = CompanyTest.this.getAmountSum(paymentList);
assertEquals(sumByMonth, yearlyAmountSum, EMPLOYEE_COUNT * 12, "sum of monthly payments have to match yearly sum");
}
private long getAmountSum(List<Payment> paymentList) {
return paymentList.stream()
.mapToInt(payment -> payment.getCurrencyAmount().getAmount())
.sum();
}
}

View File

@ -0,0 +1,35 @@
package ch.zhaw.prog2.functional.streaming;
import ch.zhaw.prog2.functional.streaming.humanresource.Employee;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* This test class is for all test methods written by students for easier review by lecturers.
* In a real application these test would be in the class CompanyTest.
*
* This class should be worked on by students.
*/
public class CompanyTestStudent {
private Company testCompany;
@BeforeEach
void setUp() {
Random random = new Random(CompanyTest.RANDOM_SEED);
CompanySupplier companySupplier = new CompanySupplier(random, CompanyTest.EMPLOYEE_COUNT);
testCompany = companySupplier.get();
}
/*
* Aufgabe c)
*/
@Test
void getEmployeesByPredicate() {
// TODO write your test
}
}

View File

@ -0,0 +1,33 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Currency;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
public class BankAccountSupplier implements Supplier<BankAccount> {
private final Random random;
private final CurrencySupplier currencySupplier;
public BankAccountSupplier(Random random) {
Objects.requireNonNull(random);
this.random = new Random(random.nextLong());
currencySupplier = new CurrencySupplier(random);
}
@Override
public BankAccount get() {
BankAccount bankAccount = new BankAccount();
Currency currency = currencySupplier.get();
try {
StringBuilder iban = new StringBuilder(currency.getCurrencyCode().substring(0, 2));
iban.append(random.nextInt(90) + 10).append(" ");
iban.append(random.nextInt(1_000_000_000)).append(" ");
iban.append(random.nextInt(1_000_000_000));
bankAccount.setCurrency(currency).setIbanNumber(iban.toString());
} catch (IllegalIbanNumber illegalIbanNumber) {
illegalIbanNumber.printStackTrace();
}
return bankAccount;
}
}

View File

@ -0,0 +1,22 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.Test;
import java.util.Random;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class BankAccountSupplierTest {
private static final long RANDOM_SEED = 119L;
@Test
void get() {
Random random = new Random(RANDOM_SEED);
BankAccountSupplier bankAccountSupplier = new BankAccountSupplier(random);
int sampleSize = 10;
long distinct = Stream.generate(bankAccountSupplier).limit(sampleSize).limit(sampleSize)
.map(account -> account.getIbanNumber()).distinct().count();
assertEquals(sampleSize, distinct, "all generated iban number have to differ");
}
}

View File

@ -0,0 +1,26 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class BankAccountTest {
@Test
void isIbanAccepted_valid() {
List<String> accepted = List.of("BE71 0961 2345 6769", "FR76 3000 6000 0112 3456 7890 189" ,
"DE91 1000 0000 0123 4567 89", "CH10 00230 00A109822346");
accepted.forEach(iban ->
assertTrue(BankAccount.isIbanAccepted(iban), iban + " is a valid IBAN"));
}
@Test
void isIbanAccepted_invalid() {
List<String> invalid = List.of("BE71 0961 2345 6769 9999 8888 7777 6666 123", "BE71", "CH10");
invalid.forEach(iban ->
assertFalse(BankAccount.isIbanAccepted(iban), iban + " is not a valid IBAN"));
}
}

View File

@ -0,0 +1,23 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Objects;
import java.util.Random;
import java.util.function.Supplier;
public class CurrencyAmountSupplier implements Supplier<CurrencyAmount> {
private final Random random;
private final int maxAmount;
private final CurrencySupplier currencySupplier;
public CurrencyAmountSupplier(Random random, int maxAmount) {
Objects.requireNonNull(random);
this.random = random;
this.maxAmount = maxAmount;
this.currencySupplier = new CurrencySupplier(random);
}
@Override
public CurrencyAmount get() {
return new CurrencyAmount(random.nextInt(maxAmount), currencySupplier.get());
}
}

View File

@ -0,0 +1,25 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CurrencyAmountTest {
public static final int TEST_AMOUNT = 4321;
@Test
void constructor() {
CurrencyAmount currencyAmount = new CurrencyAmount(TEST_AMOUNT);
assertEquals(CurrencyAmount.CHF, currencyAmount.getCurrency());
assertEquals(TEST_AMOUNT, currencyAmount.getAmount());
}
@Test
void createModifiedAmount() {
CurrencyAmount currencyAmount = new CurrencyAmount(TEST_AMOUNT);
int factor = 17;
CurrencyAmount newAmount = currencyAmount.createModifiedAmount(x -> x * factor);
assertEquals(TEST_AMOUNT * factor, newAmount.getAmount());
assertEquals(currencyAmount.getCurrency(), newAmount.getCurrency());
}
}

View File

@ -0,0 +1,31 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.Test;
import java.util.Currency;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CurrencyChangeTest {
private static final CurrencyAmount gbp113 = new CurrencyAmount(113, Currency.getInstance("GBP"));
private static final Currency CURRENCY_CHF = Currency.getInstance("CHF");
private static final Currency CURRENCY_USD = Currency.getInstance("USD");
private static final Currency CURRENCY_GBP = Currency.getInstance("GBP");
@Test
void getInNewCurrency() {
CurrencyAmount unchanged = CurrencyChange.getInNewCurrency(gbp113, CURRENCY_GBP);
assertEquals(gbp113.getAmount(), unchanged.getAmount(), "no conversion keeps value");
assertEquals(CURRENCY_GBP, unchanged.getCurrency(), "target currency is GBP");
CurrencyAmount newCurrencyAmount = CurrencyChange.getInNewCurrency(gbp113, CURRENCY_CHF);
// TODO fix magic numbers - CurrencyChange should get map with factors in constructor
assertEquals(113 / 0.83, newCurrencyAmount.getAmount() * 1.0, 0.5);
assertEquals("CHF", newCurrencyAmount.getCurrency().getCurrencyCode());
newCurrencyAmount = CurrencyChange.getInNewCurrency(gbp113, CURRENCY_USD);
// TODO fix magic numbers - CurrencyChange should get map with factors in constructor
assertEquals(113 / 0.83 * 1.04, newCurrencyAmount.getAmount() * 1.0, 0.5);
assertEquals("USD", newCurrencyAmount.getCurrency().getCurrencyCode());
}
}

View File

@ -0,0 +1,24 @@
package ch.zhaw.prog2.functional.streaming.finance;
import java.util.Currency;
import java.util.Random;
import java.util.Set;
import java.util.function.Supplier;
public class CurrencySupplier implements Supplier<Currency> {
// Java 14 knows 228 Currencies - we only want some of them
private static final Set<String> CURRENCY_ISOCODES = Set.of("CHF", "EUR", "GBP", "USD");
private static final Currency[] CURRENCIES = Currency.getAvailableCurrencies().stream()
.filter(currency -> CurrencySupplier.CURRENCY_ISOCODES.contains(currency.getCurrencyCode()))
.toArray(Currency[]::new);
private final Random random;
public CurrencySupplier(Random random) {
this.random = random;
}
@Override
public Currency get() {
return CURRENCIES[random.nextInt(CURRENCIES.length)];
}
}

View File

@ -0,0 +1,21 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.Test;
import java.util.Random;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CurrencySupplierTest {
private static final long RANDOM_SEED = 1173L;
@Test
void get() {
Random random = new Random(RANDOM_SEED);
CurrencySupplier currencySupplier = new CurrencySupplier(random);
int sampleSize = 10;
long distinct = Stream.generate(currencySupplier).limit(sampleSize).distinct().count();
assertTrue(distinct > 2, "At least two different currencies expected");
}
}

View File

@ -0,0 +1,30 @@
package ch.zhaw.prog2.functional.streaming.finance;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class PaymentTest {
private Payment payment;
@BeforeEach
void setUp() {
payment = new Payment();
}
@Test
void setTargetAccount() {
Optional<BankAccount> account = Optional.empty();
payment.setTargetAccount(account);
assertNull(payment.getTargetAccount());
BankAccount realAccount = new BankAccount();
account = Optional.of(realAccount);
payment.setTargetAccount(account);
assertEquals(realAccount, payment.getTargetAccount(), "get stored account value");
}
}

View File

@ -0,0 +1,42 @@
package ch.zhaw.prog2.functional.streaming.finance;
import ch.zhaw.prog2.functional.streaming.Company;
import ch.zhaw.prog2.functional.streaming.CompanySupplier;
import ch.zhaw.prog2.functional.streaming.humanresource.Employee;
import ch.zhaw.prog2.functional.streaming.humanresource.EmployeeSupplier;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PayrollCreatorTest {
// not private, so they can be used student test class PayRollCreatorTestStudent
static final long RANDOM_SEED = 5113L;
static final int EMPLOYEE_COUNT = 400;
@Test
void getPayrollForAll() {
EmployeeSupplier employeeSupplier = new EmployeeSupplier(new Random(RANDOM_SEED));
// non working employee
Employee employee = employeeSupplier.get();
employee.setWorkingForCompany(true);
PayrollCreator payrollCreatorOneEmployee = new PayrollCreator(new Company(List.of(employee)));
Payroll payroll = payrollCreatorOneEmployee.getPayrollForAll();
assertEquals(1, payroll.getPaymentList().size(), "one working employee, one payment");
employee.setWorkingForCompany(false);
payroll = payrollCreatorOneEmployee.getPayrollForAll();
assertEquals(0, payroll.getPaymentList().size(), "no working employees, no payments");
}
@Test
void payrollValueCHF() {
Company testCompany = new CompanySupplier(new Random(RANDOM_SEED), EMPLOYEE_COUNT).get();
PayrollCreator payrollCreator = new PayrollCreator(testCompany);
Payroll payroll = payrollCreator.getPayrollForAll();
int paysum = PayrollCreator.payrollValueCHF(payroll);
assertTrue(paysum > 100000);
}
}

View File

@ -0,0 +1,12 @@
package ch.zhaw.prog2.functional.streaming.finance;
/**
* This test class is for all test methods written by students for easier review by lecturers.
* In a real application these test would be in the class PayrollCreatorTest.
*
* This class should be worked on by students.
*/
public class PayrollCreatorTestStudent {
}

View File

@ -0,0 +1,27 @@
package ch.zhaw.prog2.functional.streaming.finance;
import ch.zhaw.prog2.functional.streaming.humanresource.Person;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class PayrollTest {
private static final Person adam = new Person("Adam", "First");
@Test
void addPayments() {
Payment firstPayment = new Payment().setBeneficiary(adam);
Payroll payroll = new Payroll();
List<Payment> paymentList = new ArrayList<>(1);
paymentList.add(firstPayment);
payroll.addPayments(paymentList);
assertIterableEquals(paymentList, payroll.getPaymentList());
assertThrows(IllegalArgumentException.class, () ->
payroll.addPayments(paymentList), "detect duplicate beneficiary");
}
}

View File

@ -0,0 +1,60 @@
package ch.zhaw.prog2.functional.streaming.humanresource;
import ch.zhaw.prog2.functional.streaming.finance.BankAccountSupplier;
import ch.zhaw.prog2.functional.streaming.finance.CurrencyAmountSupplier;
import ch.zhaw.prog2.functional.streaming.finance.PaymentsPerYear;
import java.util.Objects;
import java.util.Random;
import java.util.function.IntPredicate;
import java.util.function.Supplier;
public class EmployeeSupplier implements Supplier<Employee> {
public static final short PERCENTAGE_OF_WORKING_EMPLOYEES = 90;
public static final short PERCENTAGE_FEMALE = 50;
private static final short PERCENTAGE_13_MONTH_PAYMENT = 50;
private static final int SALARY_MAX = 100000;
private static final String[] FIRSTNAMES = {
"Lowri", "Molly", "Ria", "Irene", "Hazel", "Yasmin", "Alexia",
"Kenneth", "Yasin", "Gerald", "Ciaran", "Rocco", "Glenn", "Bailey",
"Evelyn", "Penelope", "Darcie", "Ellie-May", "Rhonda", "Lana", "Heather",
"Raphael", "Oscar", "Liam", "Robert", "Declan", "Leroy", "Aiden"
};
private static final String[] LASTNAMES = {
"Lamb", "Evans", "Rowe", "Ford", "Paul", "Turner", "Miller",
"Peters", "Wang", "Davis", "Burton", "Faulkner", "Griffiths", "Owens",
"O'Reilly", "Jacobs", "Sherman", "Howells", "Walters", "Warner", "Schroeder"
};
private final Random random;
private final IntPredicate randomTrueForPercentage;
private final CurrencyAmountSupplier currencyAmountSupplier;
private final BankAccountSupplier accountSupplier;
public EmployeeSupplier(Random random) {
Objects.requireNonNull(random);
this.random = new Random(random.nextLong());
accountSupplier = new BankAccountSupplier(new Random(random.nextLong()));
currencyAmountSupplier = new CurrencyAmountSupplier(new Random(random.nextLong()), SALARY_MAX);
randomTrueForPercentage = percentTrue -> this.random.nextInt(100) + 1 <= percentTrue;
}
private String selectOne(String[] values) {
int index = random.nextInt(values.length);
return values[index];
}
@Override
public Employee get() {
PaymentsPerYear paymentsPerYear = randomTrueForPercentage.test(PERCENTAGE_13_MONTH_PAYMENT) ?
PaymentsPerYear.THIRTEEN : PaymentsPerYear.TWELVE;
Employee newEmployee = new Employee(selectOne(FIRSTNAMES), selectOne(LASTNAMES))
.setWorkingForCompany(randomTrueForPercentage.test(PERCENTAGE_OF_WORKING_EMPLOYEES))
.setPaymentsPerYear(paymentsPerYear)
.setYearlySalary(currencyAmountSupplier.get())
.setAccount(accountSupplier.get());
newEmployee.setFemale(randomTrueForPercentage.test(PERCENTAGE_FEMALE));
return newEmployee;
}
}

View File

@ -0,0 +1,20 @@
package ch.zhaw.prog2.functional.streaming.humanresource;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
class EmployeeSupplierTest {
private static final long RANDOM_SEED = 42L;
@Test
void get() {
Random random = new Random(RANDOM_SEED);
EmployeeSupplier employeeSupplier = new EmployeeSupplier(random);
Employee firstEmployee = employeeSupplier.get();
Employee secondEmployee = employeeSupplier.get();
assertNotEquals(firstEmployee.getName(), secondEmployee.getName(), "Generated Employees have to differ");
}
}

View File

@ -0,0 +1,33 @@
package ch.zhaw.prog2.functional.streaming.humanresource;
import ch.zhaw.prog2.functional.streaming.finance.BankAccount;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
class EmployeeTest {
private static final String TEST_FIRSTNAME = "Valeria";
private static final String TEST_LASTNAME = "Sherman";
private final Employee defaultEmployee = new Employee(TEST_FIRSTNAME, TEST_LASTNAME);
@Test
void getYearlySalary() {
assertNull(defaultEmployee.getYearlySalary());
}
@Test
void getAccount() {
Optional<BankAccount> account = defaultEmployee.getAccount();
assertTrue(account.isEmpty());
}
@Test
void setAccount() {
BankAccount account = new BankAccount();
Employee employee = new Employee(TEST_FIRSTNAME, TEST_LASTNAME);
employee.setAccount(account);
assertEquals(account, employee.getAccount().orElse(null));
}
}

87
code/solutions-sheet.adoc Normal file
View File

@ -0,0 +1,87 @@
:source-highlighter: coderay
:icons: font
= Lösungsblatt zum Praktikum Functional Programming
Diese Datei ist als Hilfsmittel für Sie gedacht.
Sie brauchen die Datei nicht zu verwenden, wenn Sie nicht möchten.
== 1. Die Functional Interfaces
[loweralpha]
. Welche Interfaces aus dem Package `java.util.function` können Sie alles nutzen, um
- die mathematische Funktion f(x) = x ^ 2 - 3 für Zahlen des Typs `long` abzubilden?
[numbered]
.. Lösung 1
.. Lösung 2
.. Lösung 3
.. Lösung 4
.. Lösung 5
- um den Zinsfaktor (double) für n (int) Jahre bei einem Zinssatz von p Prozent (float) zu berechnen mit der Formel
zf = (1 + p / 100)^n ?
[numbered]
.. Lösung 1
.. Lösung 2
- ein Objekt vom Typ `Person` (ohne Parameter) zu generieren?
[numbered]
.. Lösung
- Welche Eigenschaft muss eine Funktion haben, damit Sie ein eigenes Interface schreiben müssen,
also keines der in `java.util.function` vorhandenen Interfaces verwenden können?
[numbered]
.. Lösung
. Welche der Aussagen stimmen für ein funktionales Interface?
** [x] Ankreuzen mit x in [ ]
** [ ] Es ist ein Java-Interface (Schlüsselwort `interface` im Code)
** [ ] Es hat **genau eine** abstrakte Methode
** [ ] Das Interface **muss** mit `@FunctionalInterface` markiert sein
** [ ] Es hat **keine** default-Methoden (Schlüsselwort `default`)
. Welche Aussagen stimmen?
** [ ] Zu **jedem** funktionalen Interface können Lambda-Ausdrücke (_lambda expressions_) geschrieben werden
** [ ] Ein Lambda-Ausdruck kann **ohne** passendes funktionales Interface erstellt werden
** [ ] Eine Variable vom Typ `Optional` kann nie `null` sein.
== 2. Übungen auf der Stepik-Plattform
=== Übungen zu Functional Interface und Lambda Expression
. Identify the correct lambdas and method references
Korrekt sind
* ...
. Writing simple lambda expressions
+
[source, Java]
----
// java function
----
. Too many arguments
+
[source, Java]
----
// java function
----
=== Übungen mit Streams
. Composing predicates
+
[source, Java]
----
// java code
----
== 3. Design Pattern _Chain of responsibility_
[source, Java]
----
// java code
----
== 4. Company Payroll
Lösung im Code-Repository.

7
gradle.properties Normal file
View File

@ -0,0 +1,7 @@
# Used to set properties for gradle builds
# (see https://dev.to/jmfayard/configuring-gradle-with-gradle-properties-211k)
# gradle configuration
# (https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties)
#org.gradle.warning.mode=(all,fail,summary,none) default: summary
org.gradle.warning.mode=all

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Executable file
View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
images/PROG2-300x300.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

45
settings.gradle Normal file
View File

@ -0,0 +1,45 @@
/*
* Dynamic Multi-Module project structure
* automatically adds each exercise as a sub-project (module)
*/
// use current directory name as root project name
rootProject.name = file('.').name
// dynamically add sub-projects in handout folder
File handoutDir = file('code')
if (handoutDir.isDirectory()) {
handoutDir.eachDir { dir ->
String subProjectName = ":${dir.name}"
include(subProjectName)
project(subProjectName).projectDir = dir
}
}
// dynamically add sub-projects in solutions* folders
//List<File> solutionDirs = List.of(file('.').listFiles((File dir, String name) -> name.startsWith("solutions")))
file('.').eachDirMatch( name -> name.startsWith('solutions')) { solutionDir ->
if (solutionDir.isDirectory()) {
solutionDir.eachDir { dir ->
if (!dir.name.equals('images')) {
String subProjectName = ":${dir.name}-sol"
include(subProjectName)
project(subProjectName).projectDir = dir
}
}
}
}
// lab preparation tasks
File classroomDir = file('classroom')
if (classroomDir.isDirectory()) {
String subProjectName = ":${classroomDir.name}"
include(subProjectName)
}
// Example: manually adding sub-project with name == folder
//include 'module1'
// Example: manually adding sub-projects with different name & folder
//include(':lab00-module1')
//project(':lab00-module1').projectDir = file('handout/module1')

View File

@ -0,0 +1,51 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
}
// Project/Module information
description = 'Lab06 Stepik Solution'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
// Junit 5 dependencies
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
// Mockito dependencies
testImplementation 'org.mockito:mockito-core:4.3.+'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
// Output results of individual tests
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}

View File

@ -0,0 +1,87 @@
package ch.zhaw.prog2.functional;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Scanner;
/**
* Class taken from <a href="https://stepik.org/lesson/46943/step/2?unit=24990">Stepik exercise</a>
*/
class ChainOfResponsibilityDemo {
/**
* Accepts a request and returns new request with data wrapped in the tag <transaction>...</transaction>
*/
static RequestHandler wrapInTransactionTag = req ->
new Request(String.format("<transaction>%s</transaction>", req.getData()));
/**
* Accepts a request and returns a new request with calculated digest inside the tag <digest>...</digest>
*/
static RequestHandler createDigest = req -> {
String digest = "";
try {
final MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[] digestBytes = md5.digest(req.getData().getBytes("UTF-8"));
digest = new String(Base64.getEncoder().encode(digestBytes));
} catch (Exception ignored) {
System.out.println("An error occurred");
}
return new Request(req.getData() + String.format("<digest>%s</digest>", digest));
};
/**
* Accepts a request and returns a new request with data wrapped in the tag <request>...</request>
*/
static RequestHandler wrapInRequestTag = req ->
new Request(String.format("<request>%s</request>", req.getData()));
/**
* It should represents a chain of responsibility combined from another handlers.
* The format: commonRequestHandler = handler1.setSuccessor(handler2.setSuccessor(...))
* The combining method setSuccessor may has another name
*/
static RequestHandler commonRequestHandler = // !!! write a combination of existing handlers here
wrapInRequestTag.wrapFirst(createDigest.wrapFirst(wrapInTransactionTag));
/**
* It represents a handler and has two methods: one for handling requests and other for combining handlers
*/
@FunctionalInterface
interface RequestHandler {
// !!! write a method handle that accept request and returns new request here
// it allows to use lambda expressions for creating handlers below
Request handle(Request request);
// !!! write a default method for combining this and other handler single one
// the order of execution may be any but you need to consider it when composing handlers
// the method may has any name
default RequestHandler wrapFirst(RequestHandler otherHandler) {
return request -> handle(otherHandler.handle(request));
}
}
/**
* Immutable class for representing requests.
* If you need to change the request data then create new request.
*/
static class Request {
private final String data;
public Request(String requestData) {
this.data = requestData;
}
public String getData() {
return data;
}
}
// Don't change the code below
public static void main(String[] args) throws Exception {
final Scanner scanner = new Scanner(System.in);
final String requestData = scanner.nextLine();
final Request notCompletedRequest = new Request(requestData);
System.out.println(commonRequestHandler.handle(notCompletedRequest).getData());
}
}

View File

@ -0,0 +1,36 @@
package ch.zhaw.prog2.functional.stepik;
import java.util.List;
import java.util.function.IntPredicate;
public class ComposingPredicate {
/**
* The method represents a disjunct operator for a list of predicates.
* For an empty list it returns the always false predicate.
*/
public static IntPredicate disjunctAll(List<IntPredicate> predicates) {
return predicates.stream().reduce(x -> false, (a, b) -> a.or(b));
}
/**
* Using anyMatch to reduce compute time if possible
*/
public static IntPredicate disjunctAllFaster(List<IntPredicate> predicates) {
return i -> predicates.stream().anyMatch(p -> p.test(i));
}
/**
* Classical implementation provided by lecturer to help you solve this exercise.
* <p>
* This solution works, but you have to search a solution using streams which will lead you
* to a solution with less lines of code.
*/
public static IntPredicate disjunctAllNoStream(List<IntPredicate> predicates) {
IntPredicate disjunct = x -> false;
for (IntPredicate currentPredicate : predicates) {
disjunct = disjunct.or(currentPredicate);
}
return disjunct;
}
}

View File

@ -0,0 +1,68 @@
package ch.zhaw.prog2.functional.stepik;
import ch.zhaw.prog2.functional.stepik.ComposingPredicate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.*;
/**
* Do not modify this test class
*/
class ComposingPredicateTest {
private static final IntPredicate isEven = x -> x % 2 == 0;
private static final IntPredicate isDividableBy3 = x -> x % 3 == 0;
private static final List<IntPredicate> predicateList = List.of(isEven, isDividableBy3);
private List<Integer> expected;
private IntStream testIntegers;
/*
* This tests your solution
*/
@Test
void disjunctAll() {
assertDoesNotThrow(
() -> ComposingPredicate.disjunctAll(List.of(x -> true)),
"You have to implement ComposingPredicate.disjunctAll"
);
IntPredicate alwaysTrue = ComposingPredicate.disjunctAll(List.of(x -> true));
assertTrue(alwaysTrue.test(1), "Test with one predicate only");
IntPredicate dividableBy2Or3 = ComposingPredicate.disjunctAll(predicateList);
assertArrayEquals(expected.toArray(), testIntegers.filter(dividableBy2Or3).boxed().toArray());
}
/*
* This tests the alternative solution which tries to reduce compute time
*/
@Test
void disjunctAllFaster() {
IntPredicate alwaysTrue = ComposingPredicate.disjunctAllFaster(List.of(x -> true));
assertTrue(alwaysTrue.test(1), "Test with one predicate only");
IntPredicate dividableBy2Or3 = ComposingPredicate.disjunctAllFaster(predicateList);
assertArrayEquals(expected.toArray(), testIntegers.filter(dividableBy2Or3).boxed().toArray());
}
/*
* This tests the given classical solution without streams
*/
@Test
void disjunctAllNoStream() {
IntPredicate alwaysTrue = ComposingPredicate.disjunctAllNoStream(List.of(x -> true));
assertTrue(alwaysTrue.test(1), "Test with one predicate only");
IntPredicate dividableBy2Or3 = ComposingPredicate.disjunctAllNoStream(predicateList);
assertArrayEquals(expected.toArray(), testIntegers.filter(dividableBy2Or3).boxed().toArray());
}
@BeforeEach
void setUp() {
testIntegers = IntStream.range(1, 10);
expected = List.of(2, 3, 4, 6, 8, 9);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -0,0 +1,239 @@
:source-highlighter: coderay
:icons: font
:experimental:
:!sectnums:
:imagesdir: ./images/
:codedir: ./code/
:logo: IT.PROG2 -
ifdef::backend-html5[]
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
endif::[]
ifdef::backend-pdf[]
:logo:
endif::[]
ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]
= {logo} Lösungen zu den Übungsaufgaben Functional Programming
:sectnums:
:sectnumlevels: 1
// Beginn des Aufgabenblocks
== Functional Interfaces [TU]
[loweralpha]
. Welche Interfaces aus dem Package `java.util.function` können Sie alles nutzen, um
- die mathematische Funktion f(x) = x ^ 2 - 3 für Zahlen des Typs `long` abzubilden?
[numbered]
.. `LongUnaryOperator`
.. `LongFunction<R>` als `LongFunction<Long>`
.. `ToLongFunction<T>` als `ToLongFunction<Long>`
.. `UnaryOperator<T>` als `UnaryOperator<Long>`
.. `Function<T,R>` als `Function<Long,Long>`
- um den Zinsfaktor (double) für n (int) Jahre bei einem Zinssatz von p Prozent (float) zu berechnen mit der Formel
zf = (1 + p / 100)^n ?
[numbered]
.. `ToDoubleBiFunction<T,U>` als `ToDoubleBiFunction<Integer,Float>`
.. `BiFunction<T,U,R>` als `BiFunction<Integer,Float,Double>`
- ein Objekt vom Typ `Person` (ohne Parameter) zu generieren?
[numbered]
.. `Supplier<T>` als `Supplier<Person>`
. Welche Eigenschaft muss eine Funktion haben, damit Sie ein eigenes Interface schreiben müssen,
also keines der in `java.util.function` vorhandenen Interfaces verwenden können?
[numbered]
.. Sie muss mehr als zwei Parameter haben
. Welche der Aussagen stimmen für ein funktionales Interface?
** [x] Es ist ein Java-Interface (Schlüsselwort `interface` im Code)
** [x] Es hat **genau eine** abstrakte Methode
** [ ] Das Interface **muss** mit `@FunctionalInterface` markiert sein
** [ ] Es hat **keine** default-Methoden (Schlüsselwort `default`)
. Welche Aussagen stimmen?
** [x] Zu **jedem** funktionalen Interface können Lambda-Ausdrücke (_lambda expressions_) geschrieben werden
** [ ] Ein Lambda-Ausdruck kann **ohne** passendes funktionales Interface erstellt werden
** [ ] Eine Variable vom Typ `Optional` kann nie `null` sein.
== Übungen auf der Stepik-Plattform [PU]
=== Übungen zu Functional Interface und Lambda Expression
[loweralpha]
. Identify the correct lambdas and method references
+
Korrekt sind:
* `x -> { }`
* `() -> 3`
. Writing simple lambda expressions
+
[source]
----
(x, y) -> (x > y?x:y);
----
. Too many arguments
+
`String.join()` dürfte effizienter sein als das Zusammenfügen von Strings mit `+`.
+
[source]
----
(a, b, c, d, e, f, g) -> String.join("", a, b, c, d, e, f, g).toUpperCase();
----
. Writing closures
+
[source]
----
x -> a*x*x + b*x+c;
----
. Replacing anonymous classes with lambda expressions
Alles korrekt, ausser `Iterator<Integer> iterator = new Iterator<Integer>() ...`
. Matching the functional interfaces
+
[%header]
|===
|function | lambda expression
|IntSupplier | `() \-> 3`
|Consumer<String> | `System.out::println`
|BiPredicate<Integer,Integer>|`(x,y) \-> x % y == 0`
|DoubleUnaryOperator|`Math::sin`
|Function<Double,String>|`(x) \-> String.valueOf(x*x)`
|===
+
. Your own functional interface
+
[source, Java]
----
class Solution {
@FunctionalInterface
public interface TernaryIntPredicate {
boolean test(int a, int b, int c);
}
public static final TernaryIntPredicate allValuesAreDifferentPredicate =
(x, y, z) -> (x != y && y != z && z != x);
}
----
=== Übungen mit Streams
[loweralpha, start=8]
. Calculating production of all numbers in the range
+
[source]
----
(l,r) -> LongStream.rangeClosed(l,r).reduce(1, (x,y) -> x*y);
----
. Getting distinct strings
+
[source]
----
list -> list.stream().distinct().collect(Collectors.toList());
----
. Composing predicates
+
[source, Java]
----
class Solution {
public static IntPredicate disjunctAll(List<IntPredicate> predicates) {
return predicates.stream().reduce(x -> false, (a, b) -> a.or(b));
}
}
----
Sie können auch den zweiten Parameter in Reduce durch `IntPredicate::or` ersetzen.
+
Oder mit meistens weniger Rechenaufwand:
+
[source, Java]
----
class Solution {
public static IntPredicate disjunctAllAnyMatch(List<IntPredicate> predicates) {
return i -> predicates.stream().anyMatch(p -> p.test(i));
}
}
----
. Lösen Sie die folgenden Aufgaben mit Streams:
** Numbers filtering
+
[source, Java]
----
class Solution {
public static IntStream createFilteringStream(IntStream evenStream, IntStream oddStream) {
IntStream res = IntStream.concat(evenStream, oddStream);
return res.filter(n -> n % 15 == 0).sorted().skip(2);
}
}
----
** Calculating a factorial
+
[source, Java]
----
class Solution {
public static long factorial(long n) {
return LongStream.rangeClosed(1L,n).reduce(1L, (a,b) -> a*b);
}
}
----
** The sum of odd numbers
+
[source]
----
return LongStream.rangeClosed(start, end).filter(n -> n%2 == 1).sum();
----
** Collectors in practice: the product of squares
+
[source]
----
Collectors.reducing(1, (a, b) -> a * b*b);
----
** Almost like a SQL: the total sum of transactions by each account
+
[source]
----
Collectors.groupingBy(
transaction -> transaction.getAccount().getNumber(),
Collectors.summingLong(Transaction::getSum));
----
== Design Pattern _Chain of responsibility_ [PU]
[source, Java]
----
class Solution {
@FunctionalInterface
interface RequestHandler {
Request handle(Request request);
default RequestHandler wrapFirst(RequestHandler otherHandler) {
return request -> handle(otherHandler.handle(request));
}
}
final static RequestHandler commonRequestHandler =
wrapInRequestTag.wrapFirst(createDigest.wrapFirst(wrapInTransactionTag));
}
----
== Company Payroll [PA]
****
Die Lösungen zu den bewerteten Pflichtaufgaben erhalten Sie nach der Abgabe und Bewertung aller Klassen.
****

File diff suppressed because one or more lines are too long