From 0b0a61e7301a7397322ff7875d9005e55597526e Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Mon, 28 Dec 2020 17:56:47 +0200 Subject: [PATCH 01/15] GP-24 Implement CrazyLambdas with comments --- .../bobocode/lambdas/level2/CrazyLambdas.java | 98 +++++++++++++++---- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/6-0-functional-programming/src/main/java/com/bobocode/lambdas/level2/CrazyLambdas.java b/6-0-functional-programming/src/main/java/com/bobocode/lambdas/level2/CrazyLambdas.java index 8c938d028..42015f8e2 100644 --- a/6-0-functional-programming/src/main/java/com/bobocode/lambdas/level2/CrazyLambdas.java +++ b/6-0-functional-programming/src/main/java/com/bobocode/lambdas/level2/CrazyLambdas.java @@ -4,6 +4,7 @@ import java.math.BigDecimal; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.*; /** @@ -21,7 +22,8 @@ public class CrazyLambdas { * @return a string supplier */ public static Supplier helloSupplier() { - throw new ExerciseNotCompletedException(); + // because supplier method has NO PARAMETERS, a lambda starts with empty brackets + return () -> "Hello"; } /** @@ -30,7 +32,9 @@ public static Supplier helloSupplier() { * @return a string predicate */ public static Predicate isEmptyPredicate() { - throw new ExerciseNotCompletedException(); + // have a string parameter we can call isEmpty() and return result, e.g `str -> str.isEmpty()` + // so if we only call a method, it's better to provide a reference to that method instead of lambda expression + return String::isEmpty; } /** @@ -40,7 +44,11 @@ public static Predicate isEmptyPredicate() { * @return function that repeats Strings */ public static BiFunction stringMultiplier() { - throw new ExerciseNotCompletedException(); + // Bi means two parameters (str, n), and we can implement this method using a lambda with two params + // e.g. `(str, n) -> str.repeat(n)`, however in this case it's also better to provide a reference instead. + // BiFunction method `apply` has two params, and String method `repeat` has only one, but when you use a static + // method reference to a non-static method it's first parameter becomes `this` + return String::repeat; } /** @@ -50,7 +58,8 @@ public static BiFunction stringMultiplier() { * @return function that converts adds dollar sign */ public static Function toDollarStringFunction() { - throw new ExerciseNotCompletedException(); + // Function is a classic lambda, where parameter and return types are different + return val -> "$" + val; } /** @@ -62,7 +71,11 @@ public static Function toDollarStringFunction() { * @return a string predicate */ public static Predicate lengthInRangePredicate(int min, int max) { - throw new ExerciseNotCompletedException(); + // A lambda has one string parameter and we need to compare its length with provided min and max values. + // Please note, that `min` and `max` must be "effectively final" if we want to use them in lambda expression. + // Try to uncomment the line below + // min = 1; + return str -> str.length() >= min && str.length() < max; } /** @@ -71,7 +84,8 @@ public static Predicate lengthInRangePredicate(int min, int max) { * @return int supplier */ public static IntSupplier randomIntSupplier() { - throw new ExerciseNotCompletedException(); + // This is a special Supplier for int primitive. Its method has no arguments and supplies an int value. + return () -> ThreadLocalRandom.current().nextInt(); } @@ -81,7 +95,9 @@ public static IntSupplier randomIntSupplier() { * @return int operation */ public static IntUnaryOperator boundedRandomIntSupplier() { - throw new ExerciseNotCompletedException(); + // IntUnaryOperator is just an UnaryOperator for int primitives. Its method accepts int and returns int. + // So a parameter is a bound that should be used when generating a random integer + return bound -> ThreadLocalRandom.current().nextInt(bound); } /** @@ -90,7 +106,8 @@ public static IntUnaryOperator boundedRandomIntSupplier() { * @return square operation */ public static IntUnaryOperator intSquareOperation() { - throw new ExerciseNotCompletedException(); + // a classical example of lambda, we use parameter and return its square + return x -> x * x; } /** @@ -99,7 +116,9 @@ public static IntUnaryOperator intSquareOperation() { * @return binary sum operation */ public static LongBinaryOperator longSumOperation() { - throw new ExerciseNotCompletedException(); + // LongBinaryOperator is a binary operator for long primitive. + // It can be done using lambda with two params like `(a, b) -> a + b` but it's better to use method reference + return Long::sum; } /** @@ -108,7 +127,9 @@ public static LongBinaryOperator longSumOperation() { * @return string to int converter */ public static ToIntFunction stringToIntConverter() { - throw new ExerciseNotCompletedException(); + // ToIntFunction is a special form of Function that returns an int primitive. In this case we also use a simple + // method reference instead of a longer lambda `str -> Integer.parseInt(str)` + return Integer::parseInt; } /** @@ -119,7 +140,11 @@ public static ToIntFunction stringToIntConverter() { * @return a function supplier */ public static Supplier nMultiplyFunctionSupplier(int n) { - throw new ExerciseNotCompletedException(); + // As you can see we have Supplier that supplies IntUnaryOperator, which means we'll need a nested lambda. + // If it looks complex, you can start by implementing an inner lambda which is `x -> n * x`. Then on top of that + // you just need to implement a supplier that supplies that lambda above. + // Or you can start by implementing a supplier like `() -> ...` and then add inner lambda instead of three dots. + return () -> x -> n * x; } /** @@ -128,7 +153,13 @@ public static Supplier nMultiplyFunctionSupplier(int n) { * @return function that composes functions with trim() function */ public static UnaryOperator> composeWithTrimFunction() { - throw new ExerciseNotCompletedException(); + // UnaryOperator has the same parameter and return type. In our case it's a function. So our job is to use + // that function and compose it with another function called `trim` + // As you can see Function provides some additional default methods, and one of them is `compose`. + // So we have one parameter and we'll call compose, like `strFunction -> strFunction.compose(...)` then + // instead of three dots, we need to pass another function(lambda) trim, you can pass `s -> s.trim()`, or just + // use a method reference to `trim` + return strFunction -> strFunction.compose(String::trim); } /** @@ -139,7 +170,16 @@ public static UnaryOperator> composeWithTrimFunction() * @return a thread supplier */ public static Supplier runningThreadSupplier(Runnable runnable) { - throw new ExerciseNotCompletedException(); + // Having a runnable you can create and start a thread. And in this case you need to implement a supplier that + // will supply this running thread. The main point is that THREAD WON'T BE CREATED AND STARTED until + // method `get` of the supplier is called. + // In this case you need to do multiple operations like create thread, call start and return it, so we need to + // use lambda body with curly brackets and return statement + return () -> { + Thread t = new Thread(runnable); + t.start(); + return t; + }; } /** @@ -148,7 +188,10 @@ public static Supplier runningThreadSupplier(Runnable runnable) { * @return a runnable consumer */ public static Consumer newThreadRunnableConsumer() { - throw new ExerciseNotCompletedException(); + // In this case runnable is a parameter of a Consumer method. We use that parameter to create Thread + // and start it. Since consumer does not return any value (void), we call method `start` right within + // lambda expression. (Method `start` also returns `void`) + return runnable -> new Thread(runnable).start(); } /** @@ -158,7 +201,13 @@ public static Consumer newThreadRunnableConsumer() { * @return a function that transforms runnable into a thread supplier */ public static Function> runnableToThreadSupplierFunction() { - throw new ExerciseNotCompletedException(); + // This method is very similar to `runningThreadSupplier`. But in this case we should implement a function + // that accepts a runnable and then does exactly what we did before in `runningThreadSupplier`. + return runnable -> () -> { + Thread t = new Thread(runnable); + t.start(); + return t; + }; } /** @@ -171,7 +220,13 @@ public static Function> runnableToThreadSupplierFunct * @return a binary function that receiver predicate and function and compose them to create a new function */ public static BiFunction functionToConditionalFunction() { - throw new ExerciseNotCompletedException(); + // BiFunction accepts two parameters, so you can start from implementing this part + // `(intFunction, condition) -> ...` then the return type is `IntUnaryOperator`, and in order to implement + // this result `IntUnaryOperator` we need a lambda with parameter e.g. `x`, so we can add it like + // `(intFunction, condition) -> x -> ...`. Now we should check the condition for `x` + // `(intFunction, condition) -> x -> condition.test(x) ? ...` if it's true, we call provided `intFunction` + // and return result, otherwise we just return `x` + return (intFunction, condition) -> x -> condition.test(x) ? intFunction.applyAsInt(x) : x; } /** @@ -182,16 +237,21 @@ public static BiFunction funct * @return a high-order function that fetches a function from a function map by a given name or returns identity() */ public static BiFunction, String, IntUnaryOperator> functionLoader() { - throw new ExerciseNotCompletedException(); + // This BiFunction accepts a map of functions and a function name, so we start form this + // `(functionMap, functionName) -> ...` then using a name we need to extract a function from map and return it + // or return `IntUnaryOperator.identity()` if no function was found. For this use case there is a default method + // of a class `Map` called `getOrDefault` + return (functionMap, functionName) -> functionMap.getOrDefault(functionName, IntUnaryOperator.identity()); } /** - * Returns {@link Supplier} of {@link Supplier} of {@link Supplier} of {@link String} "WELL DONE". + * Returns {@link Supplier} of {@link Supplier} of {@link Supplier} of {@link String} "WELL DONE!". * * @return a supplier instance */ public static Supplier>> trickyWellDoneSupplier() { - throw new ExerciseNotCompletedException(); + // You just need to create a couple of nested lambdas like `() -> () -> ...` + return () -> () -> () -> "WELL DONE!"; } } From 06339fac2a3015c046905982061617abe52b6be2 Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Mon, 28 Dec 2020 19:07:16 +0200 Subject: [PATCH 02/15] GP-24 Implement CrazyOptionals and copied impl of CrazyStreams --- .../bobocode/optionals/CrazyOptionals.java | 99 ++++++++++++++++--- .../com/bobocode/streams/CrazyStreams.java | 77 +++++++++++---- 2 files changed, 142 insertions(+), 34 deletions(-) diff --git a/6-0-functional-programming/src/main/java/com/bobocode/optionals/CrazyOptionals.java b/6-0-functional-programming/src/main/java/com/bobocode/optionals/CrazyOptionals.java index 3ec4d4ea6..3655a9a29 100644 --- a/6-0-functional-programming/src/main/java/com/bobocode/optionals/CrazyOptionals.java +++ b/6-0-functional-programming/src/main/java/com/bobocode/optionals/CrazyOptionals.java @@ -16,6 +16,8 @@ import java.util.Optional; import java.util.OptionalDouble; +import static java.util.Comparator.comparing; + /** * {@link CrazyOptionals} is an exercise class. Each method represents some operation with a {@link Account} and * should be implemented using Optional API. Every method that is not implemented yet throws @@ -24,7 +26,6 @@ * TODO: remove exception and implement each method of this class using Optional API */ public class CrazyOptionals { - /** * Creates an instance of {@link Optional} using a text parameter * @@ -32,7 +33,9 @@ public class CrazyOptionals { * @return optional object that holds text */ public static Optional optionalOfString(@Nullable String text) { - throw new ExerciseNotCompletedException(); + // Since `text` can be null we should use `Optional.ofNullable()`. It will wrap `text` with `Optional` if it's + // not null or use `Optional.empty()` if it's is `null` + return Optional.ofNullable(text); } /** @@ -42,7 +45,14 @@ public static Optional optionalOfString(@Nullable String text) { * @param amount money to deposit */ public static void deposit(AccountProvider accountProvider, BigDecimal amount) { - throw new ExerciseNotCompletedException(); + // One of the ideas behind Optional API is to provide a declarative "if" statements. E.g. typically when we need + // to check if account is not null and then perform some logic, we would write an "if" statement and then + // do some logic inside. + // But class `Optional` provides a special method for this use case. E.g. if you have an optional account, + // you can call `ifPresent` method and provide a `Consumer` of that account which will be used only + // if optional is not empty + accountProvider.getAccount() + .ifPresent(account -> account.setBalance(account.getBalance().add(amount))); } /** @@ -52,7 +62,9 @@ public static void deposit(AccountProvider accountProvider, BigDecimal amount) { * @return optional object that holds account */ public static Optional optionalOfAccount(@Nonnull Account account) { - throw new ExerciseNotCompletedException(); + // Since account must not be null, we use `Optional.of()` that will throw `NullPointerException` on creation + // if passed object is null + return Optional.of(account); } /** @@ -64,7 +76,10 @@ public static Optional optionalOfAccount(@Nonnull Account account) { * @return account from provider or defaultAccount */ public static Account getAccount(AccountProvider accountProvider, Account defaultAccount) { - throw new ExerciseNotCompletedException(); + // As you can see Optional API is a bunch of useful methods for different use cases inside `Optional` class. + // If you need to provide a default value that will be used if optional is empty, you can use method `orElse`. + return accountProvider.getAccount() + .orElse(defaultAccount); } /** @@ -75,7 +90,11 @@ public static Account getAccount(AccountProvider accountProvider, Account defaul * @param accountService */ public static void processAccount(AccountProvider accountProvider, AccountService accountService) { - throw new ExerciseNotCompletedException(); + // We already saw a declarative "if". Now it's a declarative "if-else". + // A method `ifPresentOrElse` accepts a consumer that will be used if optional is not empty, and a runnable that + // will be called otherwise + accountProvider.getAccount() + .ifPresentOrElse(accountService::processAccount, accountService::processWithNoAccount); } /** @@ -86,7 +105,15 @@ public static void processAccount(AccountProvider accountProvider, AccountServic * @return provided or generated account */ public static Account getOrGenerateAccount(AccountProvider accountProvider) { - throw new ExerciseNotCompletedException(); + // In case you need to provide a default value, but it's computation is an expansive operation + // (e.g. calling other microservice), you SHOULD NOT USE `Optional#orElse()`. Because it FORCES YOU TO COMPUTE + // A DEFAULT VALUE regardless if optional is empty or not. + // For such cases it's better to use `Optional#orElseGet()` that functionally works exactly the same, but + // is based on lazy initialization using `Supplier` interface. Which means that default value + // will not be computed (created) until supplier method `get()` is called. In this case, + // A DEFAULT VALUE WILL BE ONLY COMPUTED WHEN OPTIONAL IS EMPTY + return accountProvider.getAccount() + .orElseGet(Accounts::generateAccount); } /** @@ -96,7 +123,10 @@ public static Account getOrGenerateAccount(AccountProvider accountProvider) { * @return optional balance */ public static Optional retrieveBalance(AccountProvider accountProvider) { - throw new ExerciseNotCompletedException(); + // When you have an optional object, and want to access its field. In that case you can use `Optional#map`. + // Which is a null-safe mapping that transforms an optional object into its optional field. + return accountProvider.getAccount() + .map(Account::getBalance); } /** @@ -107,7 +137,9 @@ public static Optional retrieveBalance(AccountProvider accountProvid * @return provided account */ public static Account getAccount(AccountProvider accountProvider) { - throw new ExerciseNotCompletedException(); + // Un case Optional is empty and you want to throw a custom exception, you can use `orElseThrow` + return accountProvider.getAccount() + .orElseThrow(() -> new AccountNotFoundException("No Account provided!")); } /** @@ -117,7 +149,10 @@ public static Account getAccount(AccountProvider accountProvider) { * @return optional credit balance */ public static Optional retrieveCreditBalance(CreditAccountProvider accountProvider) { - throw new ExerciseNotCompletedException(); + // In case your getter already return Optional, you cannot use `Optional#map` because it will wrap it with + // another `Optional` like `Optional>`. In this case `Optional#flatMap` should be used. + return accountProvider.getAccount() + .flatMap(CreditAccount::getCreditBalance); } @@ -129,7 +164,12 @@ public static Optional retrieveCreditBalance(CreditAccountProvider a * @return optional gmail account */ public static Optional retrieveAccountGmail(AccountProvider accountProvider) { - throw new ExerciseNotCompletedException(); + // Sometimes you need to check if an optional object meets some criteria and you want to do that in + // a null-safe manner. For that purpose you can use `Optional#filter` that will check a provided condition + // only if optional is not empty, and then if condition is true, it will keep the object wrapped with optional, + // or return empty optional otherwise + return accountProvider.getAccount() + .filter(account -> account.getEmail().split("@")[1].equals("gmail.com")); } /** @@ -142,7 +182,12 @@ public static Optional retrieveAccountGmail(AccountProvider accountProv * @return account got from either accountProvider or fallbackProvider */ public static Account getAccountWithFallback(AccountProvider accountProvider, AccountProvider fallbackProvider) { - throw new ExerciseNotCompletedException(); + // In case you have an alternative optional value, you can use `Optional#or`. It will be used only if main + // optional is empty. Then if you want to throw exception if optional is empty, but don't need a custom one, you + // can call `Optional#orElseThrow` + return accountProvider.getAccount() + .or(fallbackProvider::getAccount) + .orElseThrow(); } /** @@ -153,7 +198,10 @@ public static Account getAccountWithFallback(AccountProvider accountProvider, Ac * @return account with the highest balance */ public static Account getAccountWithMaxBalance(List accounts) { - throw new ExerciseNotCompletedException(); + // Optionals are used in Stream API. E.e. `Stream#min` and `Stream#max` return `Optional` + return accounts.stream() + .max(comparing(Account::getBalance)) // returns Optional + .orElseThrow(); // throws NoSuchElementException if optional is empty } /** @@ -163,7 +211,13 @@ public static Account getAccountWithMaxBalance(List accounts) { * @return the lowest balance values */ public static OptionalDouble findMinBalanceValue(List accounts) { - throw new ExerciseNotCompletedException(); + // As well as Stream API, an Optional API provides special classes for primitives. So in case you work with + // stream of primitives and call a method that returns an optional, like `min`, a return type will be + // primitive optional + return accounts.stream() + .map(Account::getBalance) // map all stream accounts to balances + .mapToDouble(BigDecimal::doubleValue) // map all balances to primitive double values (returns DoubleStream) + .min(); // Optional API provides special classes for primitives as well } /** @@ -173,7 +227,12 @@ public static OptionalDouble findMinBalanceValue(List accounts) { * @param accountService */ public static void processAccountWithMaxBalance(List accounts, AccountService accountService) { - throw new ExerciseNotCompletedException(); + // Using Steam API and Optional API allows you to write concise declarative expressions. E.g. find an account + // with max balance and process it if it exists. You use Stream method `max` that returns Optional + // and then use `Optional#ifPreset` to provide a logic we want to execute for found account. + accounts.stream() + .max(comparing(Account::getBalance)) // returns Optional + .ifPresent(accountService::processAccount); // declarative if statement that accepts Consumer } /** @@ -183,7 +242,15 @@ public static void processAccountWithMaxBalance(List accounts, AccountS * @return total credit balance */ public static double calculateTotalCreditBalance(List accounts) { - throw new ExerciseNotCompletedException(); + // If you have a stream of optionals and you want to filter empty ones, you can do the trick and call + // `Stream#flatMap` and pass `Optional#sream`. This logic transforms each optional object into a stream of either + // one of zero elements and then all those streams are flattened into one using `flatMap` which automatically + // filters all empty optional + return accounts.stream() + .map(CreditAccount::getCreditBalance) // transforms each element of stream into Optional + .flatMap(Optional::stream) // uses special Optional#stream() to filter all elements that are empty + .mapToDouble(BigDecimal::doubleValue) // transform BigDecimal into primitive double (returns DoubleStream) + .sum(); // calculates a sum of primitive double } } diff --git a/6-0-functional-programming/src/main/java/com/bobocode/streams/CrazyStreams.java b/6-0-functional-programming/src/main/java/com/bobocode/streams/CrazyStreams.java index 223e06c9a..3df8617c2 100644 --- a/6-0-functional-programming/src/main/java/com/bobocode/streams/CrazyStreams.java +++ b/6-0-functional-programming/src/main/java/com/bobocode/streams/CrazyStreams.java @@ -1,6 +1,7 @@ package com.bobocode.streams; import com.bobocode.model.Account; +import com.bobocode.model.Sex; import com.bobocode.streams.exception.EntityNotFoundException; import com.bobocode.util.ExerciseNotCompletedException; import lombok.AllArgsConstructor; @@ -8,6 +9,11 @@ import java.math.BigDecimal; import java.time.Month; import java.util.*; +import java.util.stream.Stream; + +import static java.util.Comparator.comparing; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.*; /** * {@link CrazyStreams} is an exercise class. Each method represent some operation with a collection of accounts that @@ -26,7 +32,8 @@ public class CrazyStreams { * @return account with max balance wrapped with optional */ public Optional findRichestPerson() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .max(comparing(Account::getBalance)); } /** @@ -36,7 +43,9 @@ public Optional findRichestPerson() { * @return a list of accounts */ public List findAccountsByBirthdayMonth(Month birthdayMonth) { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .filter(a -> a.getBirthday().getMonth().equals(birthdayMonth)) + .collect(toList()); } /** @@ -46,7 +55,8 @@ public List findAccountsByBirthdayMonth(Month birthdayMonth) { * @return a map where key is true or false, and value is list of male, and female accounts */ public Map> partitionMaleAccounts() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(partitioningBy(a -> a.getSex().equals(Sex.MALE))); } /** @@ -56,7 +66,8 @@ public Map> partitionMaleAccounts() { * @return a map where key is an email domain and value is a list of all account with such email */ public Map> groupAccountsByEmailDomain() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(groupingBy(a -> a.getEmail().split("@")[1])); } /** @@ -65,7 +76,9 @@ public Map> groupAccountsByEmailDomain() { * @return total number of letters of first and last names of all accounts */ public int getNumOfLettersInFirstAndLastNames() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .mapToInt(a -> a.getFirstName().length() + a.getLastName().length()) + .sum(); } /** @@ -74,7 +87,9 @@ public int getNumOfLettersInFirstAndLastNames() { * @return total balance of all accounts */ public BigDecimal calculateTotalBalance() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .map(Account::getBalance) + .reduce(BigDecimal.ZERO, BigDecimal::add); } /** @@ -83,7 +98,10 @@ public BigDecimal calculateTotalBalance() { * @return list of accounts sorted by first and last names */ public List sortByFirstAndLastNames() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .sorted(comparing(Account::getFirstName) + .thenComparing(Account::getLastName)) + .collect(toList()); } /** @@ -93,7 +111,9 @@ public List sortByFirstAndLastNames() { * @return true if there is an account that has an email with provided domain */ public boolean containsAccountWithEmailDomain(String emailDomain) { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .map(Account::getEmail) + .anyMatch(email -> email.split("@")[1].equals(emailDomain)); } /** @@ -104,7 +124,11 @@ public boolean containsAccountWithEmailDomain(String emailDomain) { * @return account balance */ public BigDecimal getBalanceByEmail(String email) { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .filter(account -> account.getEmail().equals(email)) + .findFirst() + .map(Account::getBalance) + .orElseThrow(() -> new EntityNotFoundException(String.format("Cannot find Account by email=%s", email))); } /** @@ -113,7 +137,8 @@ public BigDecimal getBalanceByEmail(String email) { * @return map of accounts by its ids */ public Map collectAccountsById() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(toMap(Account::getId, identity())); } /** @@ -124,17 +149,20 @@ public Map collectAccountsById() { * @return map of account by its ids the were created in a particular year */ public Map collectBalancesByEmailForAccountsCreatedOn(int year) { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .filter(account -> account.getCreationDate().getYear() == year) + .collect(toMap(Account::getEmail, Account::getBalance)); } /** * Returns a {@link Map} where key is {@link Account#lastName} and values is a {@link Set} that contains first names * of all accounts with a specific last name. * - * @return a map where key is a last name and value is a set of first names + * @return a map where key is a first name and value is a set of first names */ public Map> groupFirstNamesByLastNames() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(groupingBy(Account::getLastName, mapping(Account::getFirstName, toSet()))); } /** @@ -144,7 +172,9 @@ public Map> groupFirstNamesByLastNames() { * @return a map where a key is a birthday month and value is comma-separated first names */ public Map groupCommaSeparatedFirstNamesByBirthdayMonth() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(groupingBy(a -> a.getBirthday().getMonth(), + mapping(Account::getFirstName, joining(", ")))); } /** @@ -154,7 +184,10 @@ public Map groupCommaSeparatedFirstNamesByBirthdayMonth() { * @return a map where key is a creation month and value is total balance of all accounts created in that month */ public Map groupTotalBalanceByCreationMonth() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .collect(groupingBy(a -> a.getCreationDate().getMonth(), + mapping(Account::getBalance, + reducing(BigDecimal.ZERO, BigDecimal::add)))); } /** @@ -164,7 +197,11 @@ public Map groupTotalBalanceByCreationMonth() { * @return a map where key is a letter and value is its count in all first names */ public Map getCharacterFrequencyInFirstNames() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .map(Account::getFirstName) + .flatMapToInt(String::chars) + .mapToObj(c -> (char) c) + .collect(groupingBy(identity(), counting())); } /** @@ -174,8 +211,12 @@ public Map getCharacterFrequencyInFirstNames() { * @return a map where key is a letter and value is its count ignoring case in all first and last names */ public Map getCharacterFrequencyIgnoreCaseInFirstAndLastNames() { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .flatMap(a -> Stream.of(a.getFirstName(), a.getLastName())) + .map(String::toLowerCase) + .flatMapToInt(String::chars) + .mapToObj(c -> (char) c) + .collect(groupingBy(identity(), counting())); } - } From 9329d5d7780a0c279a4b2a1b7357a13f4731e045 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 5 Jan 2021 15:59:53 +0200 Subject: [PATCH 03/15] GP-26 add the exercise solution with a tail and extra methods --- .../com/bobocode/linked_list/LinkedList.java | 126 ++++++++++++++++-- 1 file changed, 112 insertions(+), 14 deletions(-) diff --git a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/linked_list/LinkedList.java b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/linked_list/LinkedList.java index f0a808334..f44843f4d 100644 --- a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/linked_list/LinkedList.java +++ b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/linked_list/LinkedList.java @@ -1,7 +1,8 @@ package com.bobocode.linked_list; - -import com.bobocode.util.ExerciseNotCompletedException; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; /** * {@link LinkedList} is a list implementation that is based on singly linked generic nodes. A node is implemented as @@ -10,6 +11,9 @@ * @param generic type parameter */ public class LinkedList implements List { + private Node head; + private Node tail; + private int size; /** * This method creates a list of provided elements @@ -19,7 +23,9 @@ public class LinkedList implements List { * @return a new list of elements the were passed as method parameters */ public static List of(T... elements) { - throw new ExerciseNotCompletedException(); // todo: implement this method + LinkedList linkedList = new LinkedList<>(); + Stream.of(elements).forEach(linkedList::add); + return linkedList; } /** @@ -29,7 +35,7 @@ public static List of(T... elements) { */ @Override public void add(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + add(size, element); } /** @@ -41,7 +47,51 @@ public void add(T element) { */ @Override public void add(int index, T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node newNode = Node.valueOf(element); + if (index == 0) { + addAsHead(newNode); + } else if (index == size) { + addAsTail(newNode); + } else { + add(index, newNode); + } + size++; + } + + private void addAsHead(Node newNode) { + newNode.next = head; + head = newNode; + if (head.next == null) { + tail = head; + } + } + + private void addAsTail(Node newNode) { + tail.next = newNode; + tail = newNode; + } + + private void add(int index, Node newNode) { + Node node = findNodeByIndex(index - 1); + newNode.next = node.next; + node.next = newNode; + } + + private Node findNodeByIndex(int index) { + Objects.checkIndex(index, size); + if (index == size - 1) { + return tail; + } else { + return nodeAt(index); + } + } + + private Node nodeAt(int index) { + Node currentNode = head; + for (int i = 0; i < index; i++) { + currentNode = currentNode.next; + } + return currentNode; } /** @@ -53,7 +103,8 @@ public void add(int index, T element) { */ @Override public void set(int index, T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node node = findNodeByIndex(index); + node.value = element; } /** @@ -65,7 +116,8 @@ public void set(int index, T element) { */ @Override public T get(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node node = findNodeByIndex(index); + return node.value; } /** @@ -76,7 +128,8 @@ public T get(int index) { */ @Override public T getFirst() { - throw new ExerciseNotCompletedException(); // todo: implement this method + checkElementsExist(); + return head.value; } /** @@ -87,7 +140,14 @@ public T getFirst() { */ @Override public T getLast() { - throw new ExerciseNotCompletedException(); // todo: implement this method + checkElementsExist(); + return tail.value; + } + + private void checkElementsExist() { + if (head == null) { + throw new NoSuchElementException(); + } } /** @@ -95,12 +155,29 @@ public T getLast() { * throws {@link IndexOutOfBoundsException} * * @param index element index + * @return an element value */ @Override public void remove(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (index == 0) { + Objects.checkIndex(index, size); + removeHead(); + } else { + Node previousNode = findNodeByIndex(index - 1); + previousNode.next = previousNode.next.next; + if (index == size - 1) { + tail = previousNode; + } + } + size--; } + private void removeHead() { + head = head.next; + if (head == null) { + tail = null; + } + } /** * Checks if a specific exists in he list @@ -109,7 +186,14 @@ public void remove(int index) { */ @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node currentNode = head; + while (currentNode != null) { + if (currentNode.value.equals(element)) { + return true; + } + currentNode = currentNode.next; + } + return false; } /** @@ -119,7 +203,7 @@ public boolean contains(T element) { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return head == null; } /** @@ -129,7 +213,7 @@ public boolean isEmpty() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -137,6 +221,20 @@ public int size() { */ @Override public void clear() { - throw new ExerciseNotCompletedException(); // todo: implement this method + head = tail = null; + size = 0; + } + + static class Node { + private T value; + private Node next; + + private Node(T value) { + this.value = value; + } + + public static Node valueOf(T value) { + return new Node<>(value); + } } } From 760dbfc6437645c3a4417a8500aca1a53c9f30c5 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 5 Jan 2021 20:47:34 +0200 Subject: [PATCH 04/15] GP-31 migrate java oop --- .../bobocode/flight_search/data/FlightDao.java | 9 ++++----- .../factory/FlightServiceFactory.java | 5 +++-- .../flight_search/service/FlightService.java | 17 ++++++++++++----- .../bobocode/flight_search/service/Flights.java | 9 +++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/Flights.java diff --git a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/data/FlightDao.java b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/data/FlightDao.java index 17ee14cb9..f980891cb 100644 --- a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/data/FlightDao.java +++ b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/data/FlightDao.java @@ -1,6 +1,6 @@ package com.bobocode.flight_search.data; -import com.bobocode.util.ExerciseNotCompletedException; +import com.bobocode.flight_search.service.Flights; import java.util.HashSet; import java.util.Set; @@ -12,7 +12,7 @@ * todo: 1. Implement a method {@link FlightDao#register(String)} that store new flight number into the set * todo: 2. Implement a method {@link FlightDao#findAll()} that returns a set of all flight numbers */ -public class FlightDao { +public class FlightDao implements Flights { private Set flights = new HashSet<>(); /** @@ -22,7 +22,7 @@ public class FlightDao { * @return {@code true} if a flight number was stored, {@code false} otherwise */ public boolean register(String flightNumber) { - throw new ExerciseNotCompletedException();// todo: implement this method + return flights.add(flightNumber); } /** @@ -31,7 +31,6 @@ public boolean register(String flightNumber) { * @return a set of flight numbers */ public Set findAll() { - throw new ExerciseNotCompletedException();// todo: implement this method + return flights; } - } diff --git a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/factory/FlightServiceFactory.java b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/factory/FlightServiceFactory.java index d7bac6fad..4ac00cdc5 100644 --- a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/factory/FlightServiceFactory.java +++ b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/factory/FlightServiceFactory.java @@ -1,7 +1,7 @@ package com.bobocode.flight_search.factory; +import com.bobocode.flight_search.data.FlightDao; import com.bobocode.flight_search.service.FlightService; -import com.bobocode.util.ExerciseNotCompletedException; /** * {@link FlightServiceFactory} is used to create an instance of {@link FlightService} @@ -16,6 +16,7 @@ public class FlightServiceFactory { * @return FlightService */ public FlightService creteFlightService() { - throw new ExerciseNotCompletedException(); + return new FlightService(new FlightDao()); } } + diff --git a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/FlightService.java b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/FlightService.java index 20a3d79ef..86f3f7d95 100644 --- a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/FlightService.java +++ b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/FlightService.java @@ -1,10 +1,9 @@ package com.bobocode.flight_search.service; -import com.bobocode.flight_search.data.FlightDao; -import com.bobocode.util.ExerciseNotCompletedException; - import java.util.List; +import static java.util.stream.Collectors.toList; + /** * {@link FlightService} provides an API that allows to manage flight numbers *

@@ -13,6 +12,12 @@ */ public class FlightService { + private Flights flights; + + public FlightService(Flights flights) { + this.flights = flights; + } + /** * Adds a new flight number * @@ -20,7 +25,7 @@ public class FlightService { * @return {@code true} if a flight number was added, {@code false} otherwise */ public boolean registerFlight(String flightNumber) { - throw new ExerciseNotCompletedException(); + return flights.register(flightNumber); } /** @@ -30,6 +35,8 @@ public boolean registerFlight(String flightNumber) { * @return a list of found flight numbers */ public List searchFlights(String query) { - throw new ExerciseNotCompletedException(); + return flights.findAll().stream() + .filter(flightNum -> flightNum.toUpperCase().contains(query.toUpperCase())) + .collect(toList()); } } diff --git a/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/Flights.java b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/Flights.java new file mode 100644 index 000000000..b6df8ee3c --- /dev/null +++ b/4-0-object-oriented-programming/src/main/java/com/bobocode/flight_search/service/Flights.java @@ -0,0 +1,9 @@ +package com.bobocode.flight_search.service; + +import java.util.Set; + +public interface Flights { + boolean register(String flightNumber); + + Set findAll(); +} From 49adec34ef0005001df9e97ae8574b3127e65287 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 5 Jan 2021 17:07:33 +0200 Subject: [PATCH 05/15] GP-28 Add File Reader solution to exercise/completed --- .../file_reader/FileReaderException.java | 7 ++++ .../com/bobocode/file_reader/FileReaders.java | 34 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaderException.java diff --git a/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaderException.java b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaderException.java new file mode 100644 index 000000000..42c440465 --- /dev/null +++ b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaderException.java @@ -0,0 +1,7 @@ +package com.bobocode.file_reader; + +public class FileReaderException extends RuntimeException { + public FileReaderException(String message, Exception e) { + super(message, e); + } +} diff --git a/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java index 685d2d473..5e48f4cf2 100644 --- a/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java +++ b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java @@ -2,6 +2,17 @@ import com.bobocode.util.ExerciseNotCompletedException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; + /** * {@link FileReaders} provides an API that allow to read whole file into a {@link String} by file name. */ @@ -14,6 +25,27 @@ public class FileReaders { * @return string that holds whole file content */ public static String readWholeFile(String fileName) { - throw new ExerciseNotCompletedException(); //todo + Path filePath = createPathFromFileName(fileName); + try (Stream fileLinesStream = openFileLinesStream(filePath)) { + return fileLinesStream.collect(joining("\n")); + } + } + + private static Path createPathFromFileName(String fileName) { + Objects.requireNonNull(fileName); + URL fileUrl = FileReaders.class.getClassLoader().getResource(fileName); + try { + return Paths.get(fileUrl.toURI()); + } catch (URISyntaxException e) { + throw new FileReaderException("Invalid file URL",e); + } + } + + private static Stream openFileLinesStream(Path filePath) { + try { + return Files.lines(filePath); + } catch (IOException e) { + throw new FileReaderException("Cannot create stream of file lines!", e); + } } } From b9173969454b27429b4f1d6d9d677111a2139e6c Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Wed, 6 Jan 2021 12:09:50 +0200 Subject: [PATCH 06/15] GP-28 fix imports --- .../src/main/java/com/bobocode/file_reader/FileReaders.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java index 5e48f4cf2..060113fd7 100644 --- a/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java +++ b/3-0-java-core/src/main/java/com/bobocode/file_reader/FileReaders.java @@ -1,7 +1,5 @@ package com.bobocode.file_reader; -import com.bobocode.util.ExerciseNotCompletedException; - import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -37,7 +35,7 @@ private static Path createPathFromFileName(String fileName) { try { return Paths.get(fileUrl.toURI()); } catch (URISyntaxException e) { - throw new FileReaderException("Invalid file URL",e); + throw new FileReaderException("Invalid file URL", e); } } From 947b71eeaee292b2cb08e16818d17739c9c8a52f Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Wed, 6 Jan 2021 14:56:37 +0200 Subject: [PATCH 07/15] GP-25 add new README.md for functional programming basics --- 5-0-functional-programming/README.md | 84 +++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/5-0-functional-programming/README.md b/5-0-functional-programming/README.md index a44069946..bfef3f968 100644 --- a/5-0-functional-programming/README.md +++ b/5-0-functional-programming/README.md @@ -1 +1,83 @@ -# Functional Programming +# Java Functional Programming Basics :muscle: +Learn Java functional programming + +### Into +You know that **Java is an Object-Oriented programming language**, we use objects everywhere (except primitives 😉) and +**functions are not first-class citizens**. So you **cannot store function as variables, pass as arguments or return +as method results❗️** + +Yes, however... 🙂 + +Having functions as **first-class citizens is useful!** When building real application quite often **you need to pass +some logic around as function.** But in Java you have to create an object, usually anonymous. + +### Case Study: Search by criteria +Suppose you have a list of accounts, and you need to build a search method that will allow you to find account by +a certain criteria. In java, you will need to create something like this +```java +interface Criteria { + boolean passed(T obj); +} +``` +then you can use that in your search method like this: +```java +public List searchBy(Criteria criteria){ + List foundAccount = new ArrayList<>(); + for (Account a: accounts){ + if (criteria.passed(a)){ + foundAccount.add(a); + } + } + return foundAccounts; +} +``` +Agree? Then if you want to use this method, it will look like this +```java +searchBy(new Criteria() { + @Override + public boolean passed(Account a) { + return a.getEmail().endsWith("@gmail.com"); + } +}); +``` +Most probably you've seen similar examples where you pass a `Runnable` implementation to execute some logic in +a new thread, or you pass `Comparator` implementation to sort a list of objects. That's why having a function +as first-class citizens is not a bad idea. In all cases mentioned **we pass objects, but what we want to pass +is a function❗️** How would passing a function look like? 🤔 Maybe something like +```java +searchBy(a -> a.getEmail().endsWith("@gmail.com")); +``` +### Summary +The main idea is to keep Java OO language but **enable some functional programming features**. It does two things: +* makes the code more concise +* allows easier parallelization + +Java SE 8+ provides a rich API that enables functional programming features based on +* Functional Interfaces +* Lambdas +* Stream API +* Optional API + +Once you've mastered all those stuff, instead of this +```java +public List findAllGmailAccounts(List accounts) { + List gmailAccounts = new ArrayList<>(); + for (Account account : accounts) { + if (account.getEmail().endsWith("@gmail.com")) { + gmailAccounts.add(account); + } + } + return gmailAccounts; +} +``` +you'll be writing this +```java +public List findAllGmailAccounts(List accounts) { + return accounts.stream() + .filter(a -> a.getEmail().endsWith("@gmail.com")) + .collect(toList()); +} +``` + +⚡️ Nowadays **every Java developer uses some functional programming features in an everyday job**. So make sure you've +check out the rest of the materials in this module and build strong skills using these language features! 💪 \ No newline at end of file From 47923cb91ec17725045db8d1cb41f05464ac3c06 Mon Sep 17 00:00:00 2001 From: Taras Boychuk Date: Wed, 6 Jan 2021 15:11:01 +0200 Subject: [PATCH 08/15] GP-25 add Lear or Skip section --- 5-0-functional-programming/README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/5-0-functional-programming/README.md b/5-0-functional-programming/README.md index bfef3f968..8c21c5d4f 100644 --- a/5-0-functional-programming/README.md +++ b/5-0-functional-programming/README.md @@ -1,7 +1,7 @@ # Java Functional Programming Basics :muscle: Learn Java functional programming -### Into +### Intro You know that **Java is an Object-Oriented programming language**, we use objects everywhere (except primitives 😉) and **functions are not first-class citizens**. So you **cannot store function as variables, pass as arguments or return as method results❗️** @@ -80,4 +80,25 @@ public List findAllGmailAccounts(List accounts) { ``` ⚡️ Nowadays **every Java developer uses some functional programming features in an everyday job**. So make sure you've -check out the rest of the materials in this module and build strong skills using these language features! 💪 \ No newline at end of file +check out the rest of the materials in this module and build strong skills using these language features! 💪 + +### Learn or skip ? +Think you're cool enough to skip this topic? 😎 Hand on a sec...☝️ Can you easily understand and write lambdas like this? +```java +runnable -> () -> { + Thread t = new Thread(runnable); + t.start(); + return t; +}; +``` +or stream pipelines like this ? 🧐 +```java +accounts.stream() + .flatMap(a -> Stream.of(a.getFirstName(), a.getLastName())) + .map(String::toLowerCase) + .flatMapToInt(String::chars) + .mapToObj(c -> (char) c) + .collect(groupingBy(identity(), counting())); +``` +No worries if you don't! Be patient, **do the exercises in this module**, and you will be able to do not only this 👆. +**You will understand and use functional programming techniques far beyond your expectations** 🔥 \ No newline at end of file From b563231cdcf584fd7b47ef47db270a079b9b063c Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 13 Jan 2021 13:18:45 +0200 Subject: [PATCH 09/15] GP-32 Update Stack(0-2 + 0-6) for completed --- .../java/com/bobocode/stack/LinkedStack.java | 48 +++++++-- .../bobocode/stack/EmptyStackException.java | 5 + .../java/com/bobocode/stack/LinkedStack.java | 101 ++++++++++++++++++ .../java/com/bobocode/stack/StackTest.java | 73 ++++++++++++- 4 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 6-0-test-driven-development/src/main/java/com/bobocode/stack/EmptyStackException.java create mode 100644 6-0-test-driven-development/src/main/java/com/bobocode/stack/LinkedStack.java diff --git a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/stack/LinkedStack.java b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/stack/LinkedStack.java index 3f55d096e..c4b6f49c5 100644 --- a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/stack/LinkedStack.java +++ b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/stack/LinkedStack.java @@ -1,7 +1,9 @@ package com.bobocode.stack; import com.bobocode.stack.exception.EmptyStackException; -import com.bobocode.util.ExerciseNotCompletedException; + +import java.util.Objects; +import java.util.stream.Stream; /** * {@link LinkedStack} represents a last-in-first-out (LIFO) stack of objects that is based on singly linked generic nodes. @@ -10,6 +12,21 @@ * @param generic type parameter */ public class LinkedStack implements Stack { + private static class Node { + T element; + Node next; + + public static Node valueOf(T element) { + return new Node<>(element); + } + + private Node(T element) { + this.element = element; + } + } + + private Node head; + private int size = 0; /** * This method creates a stack of provided elements @@ -19,7 +36,9 @@ public class LinkedStack implements Stack { * @return a new stack of elements that were passed as method parameters */ public static LinkedStack of(T... elements) { - throw new ExerciseNotCompletedException(); // todo: implement this method + LinkedStack linkedStack = new LinkedStack<>(); + Stream.of(elements).forEach(linkedStack::push); + return linkedStack; } /** @@ -30,7 +49,13 @@ public static LinkedStack of(T... elements) { */ @Override public void push(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Objects.requireNonNull(element); + Node newNode = Node.valueOf(element); + if (head != null) { + newNode.next = head; + } + head = newNode; + size++; } /** @@ -42,7 +67,18 @@ public void push(T element) { */ @Override public T pop() { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (head != null) { + size--; + return retrieveHead(); + } else { + throw new EmptyStackException(); + } + } + + private T retrieveHead() { + T element = head.element; + this.head = head.next; + return element; } /** @@ -52,7 +88,7 @@ public T pop() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -62,6 +98,6 @@ public int size() { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return head == null; } } diff --git a/6-0-test-driven-development/src/main/java/com/bobocode/stack/EmptyStackException.java b/6-0-test-driven-development/src/main/java/com/bobocode/stack/EmptyStackException.java new file mode 100644 index 000000000..68b78a2f1 --- /dev/null +++ b/6-0-test-driven-development/src/main/java/com/bobocode/stack/EmptyStackException.java @@ -0,0 +1,5 @@ +package com.bobocode.stack; + +public class EmptyStackException extends RuntimeException{ + +} diff --git a/6-0-test-driven-development/src/main/java/com/bobocode/stack/LinkedStack.java b/6-0-test-driven-development/src/main/java/com/bobocode/stack/LinkedStack.java new file mode 100644 index 000000000..a710d3306 --- /dev/null +++ b/6-0-test-driven-development/src/main/java/com/bobocode/stack/LinkedStack.java @@ -0,0 +1,101 @@ +package com.bobocode.stack; + +import java.util.Objects; +import java.util.stream.Stream; + +/** + * {@link LinkedStack} represents a last-in-first-out (LIFO) stack of objects that is based on singly linked generic nodes. + * A node is implemented as inner static class {@link Node}. + * + * @param generic type parameter + */ +public class LinkedStack implements Stack { + private static class Node { + T element; + Node next; + + public static Node valueOf(T element) { + return new Node<>(element); + } + + private Node(T element) { + this.element = element; + } + } + + private Node head; + private int size = 0; + + /** + * This method creates a stack of provided elements + * + * @param elements elements to add + * @param generic type + * @return a new stack of elements that were passed as method parameters + */ + public static LinkedStack of(T... elements) { + LinkedStack linkedStack = new LinkedStack<>(); + Stream.of(elements).forEach(linkedStack::push); + return linkedStack; + } + + /** + * The method pushes an element onto the top of this stack. This has exactly the same effect as: + * addElement(item) + * + * @param element elements to add + */ + @Override + public void push(T element) { + Objects.requireNonNull(element); + Node newNode = Node.valueOf(element); + if (head != null) { + newNode.next = head; + } + head = newNode; + size++; + } + + /** + * This method removes the object at the top of this stack + * and returns that object as the value of this function. + * + * @return The object at the top of this stack + * @throws EmptyStackException - if this stack is empty + */ + @Override + public T pop() { + if (head != null) { + size--; + return retrieveHead(); + } else { + throw new EmptyStackException(); + } + } + + private T retrieveHead() { + T element = head.element; + this.head = head.next; + return element; + } + + /** + * Returns the number of elements in the stack + * + * @return number of elements + */ + @Override + public int size() { + return size; + } + + /** + * Checks if a stack is empty + * + * @return {@code true} if a stack is empty, {@code false} otherwise + */ + @Override + public boolean isEmpty() { + return head == null; + } +} diff --git a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java index ca2774b0e..20c37cb75 100644 --- a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java +++ b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java @@ -1,4 +1,75 @@ package com.bobocode.stack; -public class StackTest { +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class StackTest { + + private Stack intStack = new LinkedStack<>(); + + @Test + void pushAndPopElementOntoEmptyStack() { + intStack.push(243); + + assertThat(intStack.pop()).isEqualTo(243); + } + + @Test + void popElementFromEmptyStack() { + assertThrows(EmptyStackException.class, () -> intStack.pop()); + } + + @Test + void pushElements() { + intStack = LinkedStack.of(23, 35, 72); + + intStack.push(55); + + assertThat(intStack.pop()).isEqualTo(55); + } + + @Test + void popElements() { + intStack = LinkedStack.of(87, 53, 66); + + intStack.pop(); + intStack.push(234); + Integer lastElement = intStack.pop(); + + assertThat(lastElement).isEqualTo(234); + } + + @Test + void size() { + intStack = LinkedStack.of(87, 53, 66); + + int actualSize = intStack.size(); + + assertThat(actualSize).isEqualTo(3); + } + + @Test + void sizeOnEmptyStack() { + int actualSize = intStack.size(); + + assertThat(actualSize).isEqualTo(0); + } + + @Test + void isEmpty() { + intStack = LinkedStack.of(87, 53, 66); + + boolean stackEmpty = intStack.isEmpty(); + + assertThat(stackEmpty).isEqualTo(false); + } + + @Test + void isEmptyOnEmptyStack() { + boolean stackEmpty = intStack.isEmpty(); + + assertThat(stackEmpty).isEqualTo(true); + } } From 4daf746188cd1f2316f28807ab7d97150489ae3c Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 14 Jan 2021 11:43:11 +0200 Subject: [PATCH 10/15] GP-32 Fix the tdd StackTest --- .../src/test/java/com/bobocode/stack/StackTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java index 20c37cb75..cb6c06f8c 100644 --- a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java +++ b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java @@ -5,7 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -class StackTest { +public class StackTest { private Stack intStack = new LinkedStack<>(); From d55d67a1e86598f2c0f2d54047c383f5396e467d Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 13 Jan 2021 17:12:28 +0200 Subject: [PATCH 11/15] GP-33 Add ArrayList completed solution --- .../com/bobocode/array_list/ArrayList.java | 87 +++++++++++++++---- 1 file changed, 72 insertions(+), 15 deletions(-) diff --git a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java index 81de865a8..e7a3f1551 100644 --- a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java +++ b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java @@ -1,7 +1,10 @@ package com.bobocode.array_list; import com.bobocode.linked_list.List; -import com.bobocode.util.ExerciseNotCompletedException; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Objects; /** * {@link ArrayList} is an implementation of {@link List} interface. This resizable data structure @@ -9,6 +12,10 @@ */ public class ArrayList implements List { + public static final int DEFAULT_CAPACITY = 5; + private Object[] elementData; + private int size; + /** * This constructor creates an instance of {@link ArrayList} with a specific capacity of an array inside. * @@ -16,7 +23,11 @@ public class ArrayList implements List { * @throws IllegalArgumentException – if the specified initial capacity is negative or 0. */ public ArrayList(int initCapacity) { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (initCapacity > 0) { + elementData = new Object[initCapacity]; + } else { + throw new IllegalArgumentException(); + } } /** @@ -24,7 +35,7 @@ public ArrayList(int initCapacity) { * A default size of inner array is 5; */ public ArrayList() { - throw new ExerciseNotCompletedException(); // todo: implement this method + elementData = new Object[DEFAULT_CAPACITY]; } /** @@ -34,7 +45,11 @@ public ArrayList() { * @return new instance */ public static List of(T... elements) { - throw new ExerciseNotCompletedException(); // todo: implement this method + List list = new ArrayList<>(elements.length); + for (T element : elements) { + list.add(element); + } + return list; } /** @@ -44,7 +59,15 @@ public static List of(T... elements) { */ @Override public void add(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + increaseDataArrayIfFull(); + elementData[size] = element; + size++; + } + + private void increaseDataArrayIfFull() { + if (elementData.length <= size) { + elementData = getTrimmedArrayToSize(elementData.length * 2); + } } /** @@ -55,7 +78,10 @@ public void add(T element) { */ @Override public void add(int index, T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + increaseDataArrayIfFull(); + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; } /** @@ -66,8 +92,10 @@ public void add(int index, T element) { * @return en element */ @Override + @SuppressWarnings("unchecked") public T get(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Objects.checkIndex(index, size); + return (T) elementData[index]; } /** @@ -77,8 +105,12 @@ public T get(int index) { * @throws java.util.NoSuchElementException if list is empty */ @Override + @SuppressWarnings("unchecked") public T getFirst() { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (isEmpty()) { + throw new NoSuchElementException(); + } + return (T) elementData[0]; } /** @@ -88,8 +120,12 @@ public T getFirst() { * @throws java.util.NoSuchElementException if list is empty */ @Override + @SuppressWarnings("unchecked") public T getLast() { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (isEmpty()) { + throw new NoSuchElementException(); + } + return (T) elementData[size - 1]; } /** @@ -101,7 +137,8 @@ public T getLast() { */ @Override public void set(int index, T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Objects.checkIndex(index, size); + elementData[index] = element; } /** @@ -112,7 +149,12 @@ public void set(int index, T element) { */ @Override public void remove(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (index == size - 1) { + elementData = getTrimmedArrayToSize(size - 1); + } else { + System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); + } + size--; } /** @@ -123,7 +165,16 @@ public void remove(int index) { */ @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (isEmpty()) { + return false; + } else { + for (Object elem : elementData) { + if (elem.equals(element)) { + return true; + } + } + } + return false; } /** @@ -133,7 +184,12 @@ public boolean contains(T element) { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size == 0; + } + + @SuppressWarnings("unchecked") + private T[] getTrimmedArrayToSize(int size) { + return (T[]) Arrays.copyOf(elementData, size); } /** @@ -141,7 +197,7 @@ public boolean isEmpty() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -149,6 +205,7 @@ public int size() { */ @Override public void clear() { - throw new ExerciseNotCompletedException(); // todo: implement this method + elementData = new Object[DEFAULT_CAPACITY]; + size = 0; } } From d574ffd1aae7058a7b292bc5e59e23b31df7a127 Mon Sep 17 00:00:00 2001 From: Maksym Stasiuk Date: Sat, 16 Jan 2021 15:25:45 +0200 Subject: [PATCH 12/15] GP-29: complete FileStats.java --- .../com/bobocode/file_stats/FileStats.java | 76 +++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/3-0-java-core/src/main/java/com/bobocode/file_stats/FileStats.java b/3-0-java-core/src/main/java/com/bobocode/file_stats/FileStats.java index b5d3328a2..c29839451 100644 --- a/3-0-java-core/src/main/java/com/bobocode/file_stats/FileStats.java +++ b/3-0-java-core/src/main/java/com/bobocode/file_stats/FileStats.java @@ -1,10 +1,28 @@ package com.bobocode.file_stats; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.counting; +import static java.util.stream.Collectors.groupingBy; + /** * {@link FileStats} provides an API that allow to get character statistic based on text file. All whitespace characters * are ignored. */ public class FileStats { + private final Map characterCountMap; + private final char mostPopularCharacter; + /** * Creates a new immutable {@link FileStats} objects using data from text file received as a parameter. * @@ -12,7 +30,47 @@ public class FileStats { * @return new FileStats object created from text file */ public static FileStats from(String fileName) { - throw new UnsupportedOperationException("It's your job to make it work!"); //todo + return new FileStats(fileName); + } + + private FileStats(String fileName) { + Path filePath = getFilePath(fileName); + characterCountMap = computeCharacterMap(filePath); + mostPopularCharacter = findMostPopularCharacter(characterCountMap); + } + + private Path getFilePath(String fileName) { + Objects.requireNonNull(fileName); + URL fileUrl = getFileUrl(fileName); + try { + return Paths.get(fileUrl.toURI()); + } catch (URISyntaxException e) { + throw new FileStatsException("Wrong file path", e); + } + } + + private URL getFileUrl(String fileName) { + URL fileUrl = getClass().getClassLoader().getResource(fileName); + if (fileUrl == null) { + throw new FileStatsException("Wrong file path"); + } + return fileUrl; + } + + private Map computeCharacterMap(Path filePath) { + try (Stream lines = Files.lines(filePath)) { + return collectCharactersToCountMap(lines); + } catch (IOException e) { + throw new FileStatsException("Cannot read the file", e); + } + } + + private Map collectCharactersToCountMap(Stream linesStream) { + return linesStream + .flatMapToInt(String::chars) + .filter(a -> a != 32) // filter whitespace + .mapToObj(c -> (char) c) + .collect(groupingBy(identity(), counting())); } /** @@ -22,7 +80,7 @@ public static FileStats from(String fileName) { * @return a number that shows how many times this character appeared in a text file */ public int getCharCount(char character) { - throw new UnsupportedOperationException("It's your job to make it work!"); //todo + return characterCountMap.get(character).intValue(); } /** @@ -31,7 +89,15 @@ public int getCharCount(char character) { * @return the most frequently appeared character */ public char getMostPopularCharacter() { - throw new UnsupportedOperationException("It's your job to make it work!"); //todo + return mostPopularCharacter; + } + + private char findMostPopularCharacter(Map characterCountMap) { + return characterCountMap.entrySet() + .stream() + .max(Comparator.comparing(Map.Entry::getValue)) + .get() + .getKey(); } /** @@ -41,6 +107,6 @@ public char getMostPopularCharacter() { * @return {@code true} if this character has appeared in the text, and {@code false} otherwise */ public boolean containsCharacter(char character) { - throw new UnsupportedOperationException("It's your job to make it work!"); //todo + return characterCountMap.containsKey(character); } -} +} \ No newline at end of file From 3a9ca66cfae02c84aba4a6a159dfee671d4f42ac Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 20 Jan 2021 17:30:29 +0200 Subject: [PATCH 13/15] GP-41 Update completed methods --- .../main/java/com/bobocode/array_list/ArrayList.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java index a186e2c59..8c750f0a3 100644 --- a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java +++ b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java @@ -149,13 +149,13 @@ public void set(int index, T element) { * @return deleted element */ @Override + @SuppressWarnings("unchecked") public T remove(int index) { - if (index == size - 1) { - elementData = getTrimmedArrayToSize(size - 1); - } else { - System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); - } + Objects.checkIndex(index, size); + T deletedElement = (T) elementData[index]; + System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); size--; + return deletedElement; } /** From 0185b13840028d64576ec7f6e432d7e3f4e7ce06 Mon Sep 17 00:00:00 2001 From: Serhii Date: Wed, 20 Jan 2021 19:26:16 +0200 Subject: [PATCH 14/15] GP-41 Update completed solution --- .../src/main/java/com/bobocode/array_list/ArrayList.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java index 8c750f0a3..416ea8ad2 100644 --- a/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java +++ b/2-0-data-structures-and-algorithms/src/main/java/com/bobocode/array_list/ArrayList.java @@ -12,7 +12,7 @@ */ public class ArrayList implements List { - public static final int DEFAULT_CAPACITY = 5; + private static final int DEFAULT_CAPACITY = 5; private Object[] elementData; private int size; @@ -65,8 +65,8 @@ public void add(T element) { } private void increaseDataArrayIfFull() { - if (elementData.length <= size) { - elementData = getTrimmedArrayToSize(elementData.length * 2); + if (elementData.length == size) { + elementData = Arrays.copyOf(elementData, size * 2); } } From a8cbabf88bd001134a3ff8d12eccc93608acd943 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 21 Jan 2021 09:59:25 +0200 Subject: [PATCH 15/15] GP-38 Fix test name in tdd completed --- .../src/test/java/com/bobocode/stack/StackTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java index cb6c06f8c..2c4a0a514 100644 --- a/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java +++ b/6-0-test-driven-development/src/test/java/com/bobocode/stack/StackTest.java @@ -10,7 +10,7 @@ public class StackTest { private Stack intStack = new LinkedStack<>(); @Test - void pushAndPopElementOntoEmptyStack() { + void pushElementOntoEmptyStack() { intStack.push(243); assertThat(intStack.pop()).isEqualTo(243);