diff --git a/0-0-intro/src/main/java/com/bobocode/intro/ExerciseIntroduction.java b/0-0-intro/src/main/java/com/bobocode/intro/ExerciseIntroduction.java index 35d925636..102e94732 100644 --- a/0-0-intro/src/main/java/com/bobocode/intro/ExerciseIntroduction.java +++ b/0-0-intro/src/main/java/com/bobocode/intro/ExerciseIntroduction.java @@ -1,6 +1,6 @@ package com.bobocode.intro; -import com.bobocode.util.ExerciseNotCompletedException; +import java.util.Base64; /** * Welcome! This is an introduction exercise that will show you a simple example of Bobocode exercises. @@ -23,8 +23,7 @@ public class ExerciseIntroduction { * @return "The key to efficient learning is practice!" */ public String getWelcomeMessage() { - // todo: implement a method and return a message according to javadoc - throw new ExerciseNotCompletedException(); + return "The key to efficient learning is practice!"; } /** @@ -39,7 +38,6 @@ public String getWelcomeMessage() { * @return encoded message */ public String encodeMessage(String message) { - // todo: switch to branch "completed" in order to see how it should be implemented - throw new ExerciseNotCompletedException(); + return Base64.getEncoder().encodeToString(message.getBytes()); } } diff --git a/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/Box.java b/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/Box.java index 5a2d860ee..180603a28 100644 --- a/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/Box.java +++ b/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/Box.java @@ -7,18 +7,18 @@ *

* todo: refactor this class so it uses generic type "T" and run {@link com.bobocode.basics.BoxTest} to verify it */ -public class Box { - private Object value; +public class Box { + private T value; - public Box(Object value) { + public Box(T value) { this.value = value; } - public Object getValue() { + public T getValue() { return value; } - public void setValue(Object value) { + public void setValue(T value) { this.value = value; } } diff --git a/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/BoxDemoApp.java b/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/BoxDemoApp.java index bc12174ee..da2997c74 100644 --- a/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/BoxDemoApp.java +++ b/1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/BoxDemoApp.java @@ -9,13 +9,12 @@ */ public class BoxDemoApp { public static void main(String[] args) { - Box intBox = new Box(123); - Box intBox2 = new Box(321); - System.out.println((int) intBox.getValue() + (int) intBox2.getValue()); + Box intBox = new Box<>(123); + Box intBox2 = new Box<>(321); + System.out.println((int) intBox.getValue() + intBox2.getValue()); intBox.setValue(222); - intBox.setValue("abc"); // this should not be allowed - // the following code will compile, but will throw runtime exception - System.out.println((int) intBox.getValue() + (int) intBox2.getValue()); +// intBox.setValue("abc"); // this should not be allowed + System.out.println((int) intBox.getValue() + intBox2.getValue()); } } diff --git a/1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/CrazyGenerics.java b/1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/CrazyGenerics.java index 751d5899f..5c63d64a6 100644 --- a/1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/CrazyGenerics.java +++ b/1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/CrazyGenerics.java @@ -1,14 +1,12 @@ package com.bobocode.basics; import com.bobocode.basics.util.BaseEntity; -import com.bobocode.util.ExerciseNotCompletedException; import lombok.Data; +import lombok.val; import java.io.Serializable; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.function.Predicate; /** * {@link CrazyGenerics} is an exercise class. It consists of classes, interfaces and methods that should be updated @@ -33,8 +31,8 @@ public class CrazyGenerics { * @param – value type */ @Data - public static class Sourced { // todo: refactor class to introduce type parameter and make value generic - private Object value; + public static class Sourced { + private T value; private String source; } @@ -45,11 +43,10 @@ public static class Sourced { // todo: refactor class to introduce type paramete * @param – actual, min and max type */ @Data - public static class Limited { - // todo: refactor class to introduce type param bounded by number and make fields generic numbers - private final Object actual; - private final Object min; - private final Object max; + public static class Limited { + private final T actual; + private final T min; + private final T max; } /** @@ -59,8 +56,8 @@ public static class Limited { * @param – source object type * @param - converted result type */ - public interface Converter { // todo: introduce type parameters - // todo: add convert method + public interface Converter { + R convert(T obj); } /** @@ -70,10 +67,10 @@ public interface Converter { // todo: introduce type parameters * * @param – value type */ - public static class MaxHolder { // todo: refactor class to make it generic - private Object max; + public static class MaxHolder> { // todo: refactor class to make it generic + private T max; - public MaxHolder(Object max) { + public MaxHolder(T max) { this.max = max; } @@ -82,11 +79,13 @@ public MaxHolder(Object max) { * * @param val a new value */ - public void put(Object val) { - throw new ExerciseNotCompletedException(); // todo: update parameter and implement the method + public void put(T val) { + if (val.compareTo(max) > 0) { + max = val; + } } - public Object getMax() { + public T getMax() { return max; } } @@ -97,8 +96,8 @@ public Object getMax() { * * @param – the type of objects that can be processed */ - interface StrictProcessor { // todo: make it generic - void process(Object obj); + interface StrictProcessor> { // todo: make it generic + void process(T obj); } /** @@ -108,10 +107,10 @@ interface StrictProcessor { // todo: make it generic * @param – a type of the entity that should be a subclass of {@link BaseEntity} * @param – a type of any collection */ - interface CollectionRepository { // todo: update interface according to the javadoc - void save(Object entity); + interface CollectionRepository> { // todo: update interface according to the javadoc + void save(T entity); - Collection getEntityCollection(); + C getEntityCollection(); } /** @@ -120,7 +119,7 @@ interface CollectionRepository { // todo: update interface according to the java * * @param – a type of the entity that should be a subclass of {@link BaseEntity} */ - interface ListRepository { // todo: update interface according to the javadoc + interface ListRepository extends CollectionRepository> { // todo: update interface according to the javadoc } /** @@ -133,12 +132,14 @@ interface ListRepository { // todo: update interface according to the javadoc * * @param a type of collection elements */ - interface ComparableCollection { // todo: refactor it to make generic and provide a default impl of compareTo + interface ComparableCollection extends Collection, Comparable> { + + @Override + default int compareTo(Collection o) { + return Integer.compare(this.size(), o.size()); + } } - /** - * {@link CollectionUtil} is an util class that provides various generic helper methods. - */ static class CollectionUtil { static final Comparator CREATED_ON_COMPARATOR = Comparator.comparing(BaseEntity::getCreatedOn); @@ -147,8 +148,7 @@ static class CollectionUtil { * * @param list */ - public static void print(List list) { - // todo: refactor it so the list of any type can be printed, not only integers + public static void print(List list) { list.forEach(element -> System.out.println(" – " + element)); } @@ -160,8 +160,9 @@ public static void print(List list) { * @param entities provided collection of entities * @return true if at least one of the elements has null id */ - public static boolean hasNewEntities(Collection entities) { - throw new ExerciseNotCompletedException(); // todo: refactor parameter and implement method + public static boolean hasNewEntities(Collection entities) { + return entities.stream() + .anyMatch(e -> e.getUuid() == null); } /** @@ -173,8 +174,10 @@ public static boolean hasNewEntities(Collection entities) { * @param validationPredicate criteria for validation * @return true if all entities fit validation criteria */ - public static boolean isValidCollection() { - throw new ExerciseNotCompletedException(); // todo: add method parameters and implement the logic + public static boolean isValidCollection(Collection entities, + Predicate validationPredicate) { + return entities.stream() + .allMatch(validationPredicate); } /** @@ -187,8 +190,10 @@ public static boolean isValidCollection() { * @param entity type * @return true if entities list contains target entity more than once */ - public static boolean hasDuplicates() { - throw new ExerciseNotCompletedException(); // todo: update method signature and implement it + public static boolean hasDuplicates(Collection entities, T targetEntity) { + return entities.stream() + .filter(e -> e.getUuid().equals(targetEntity.getUuid())) + .count() > 1; } /** @@ -200,7 +205,20 @@ public static boolean hasDuplicates() { * @param type of elements * @return optional max value */ - // todo: create a method and implement its logic manually without using util method from JDK + public static Optional findMax(Iterable elements, Comparator comparator) { + var iterator = elements.iterator(); + if (!iterator.hasNext()) { + return Optional.empty(); + } + var max = iterator.next(); + while (iterator.hasNext()) { + var element = iterator.next(); + if (comparator.compare(element, max) > 0) { + max = element; + } + } + return Optional.of(max); + } /** * findMostRecentlyCreatedEntity is a generic util method that accepts a collection of entities and returns the @@ -214,7 +232,10 @@ public static boolean hasDuplicates() { * @param entity type * @return an entity from the given collection that has the max createdOn value */ - // todo: create a method according to JavaDoc and implement it using previous method + public static T findMostRecentlyCreatedEntity(Collection entities) { + return findMax(entities, CREATED_ON_COMPARATOR) + .orElseThrow(); + } /** * An util method that allows to swap two elements of any list. It changes the list so the element with the index @@ -228,8 +249,13 @@ public static boolean hasDuplicates() { public static void swap(List elements, int i, int j) { Objects.checkIndex(i, elements.size()); Objects.checkIndex(j, elements.size()); - throw new ExerciseNotCompletedException(); // todo: complete method implementation + swapHelper(elements, i, j); } + private static void swapHelper(List elements, int i, int j) { + T temp = elements.get(i); + elements.set(i, elements.get(j)); + elements.set(j, temp); + } } } diff --git a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java index 9ef839910..6afbec0e7 100644 --- a/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java +++ b/1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java @@ -1,7 +1,11 @@ package com.bobocode.basics; +import java.util.Comparator; +import java.util.HashMap; import java.util.Map; +import static java.util.Objects.requireNonNull; + /** * {@link HeterogeneousMaxHolder} is a multi-type container that holds maximum values per each type. It's kind of a * key/value map, where the key is a type and the value is the maximum among all values of this type that were put. @@ -15,9 +19,10 @@ * @author Taras Boychuk */ public class HeterogeneousMaxHolder { + private Map, Object> typeToMaxValueMap = new HashMap<>(); /** - * A method put stores a provided value by its type, if the value is greater than the current maximum. In other words, the logic + * Stores a provided value by its type, if the value is greater than the current maximum. In other words, the logic * of this method makes sure that only max value is stored and everything else is ignored. *

* If the current max value is less than a provided one, or if it's null, then a provided value gets stored and the old @@ -30,7 +35,9 @@ public class HeterogeneousMaxHolder { * @param value type parameter * @return a smaller value among the provided value and the current maximum */ - // todo: implement a method according to javadoc + public > T put(Class key, T value) { + return put(key, value, Comparator.naturalOrder()); + } /** * An overloaded method put implements the same logic using a custom comparator. A given comparator is wrapped with @@ -44,14 +51,27 @@ public class HeterogeneousMaxHolder { * @param value type parameter * @return a smaller value among the provided value and the current maximum */ - // todo: implement a method according to javadoc + public T put(Class key, T value, Comparator comparator) { + var currentMax = getMax(requireNonNull(key)); + var nullSafeComparator = Comparator.nullsFirst(requireNonNull(comparator)); + requireNonNull(value); + if (nullSafeComparator.compare(value, currentMax) > 0) { + typeToMaxValueMap.put(key, value); + return currentMax; + } + return value; + } /** - * A method getMax returns a max value by the given type. If no value is stored by this type, then it returns null. + * Returns a max value by the given type. If no value is stored by this type, then it returns null. * * @param key a provided value type * @param value type parameter * @return current max value or null */ - // todo: implement a method according to javadoc + public T getMax(Class key) { + requireNonNull(key); + var currentMax = typeToMaxValueMap.get(key); + return key.cast(currentMax); + } } diff --git a/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/Exercise.java b/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/Exercise.java new file mode 100644 index 000000000..70066cbff --- /dev/null +++ b/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/Exercise.java @@ -0,0 +1,14 @@ +package com.bobocode.basics; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Exercise { + String value(); + + Level complexityLevel() default Level.BASIC; +} diff --git a/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/HelloAnnotationsExercise.java b/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/HelloAnnotationsExercise.java index 4dc8c4b22..268ee071d 100644 --- a/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/HelloAnnotationsExercise.java +++ b/1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/HelloAnnotationsExercise.java @@ -13,5 +13,6 @@ * * @author Taras Boychuk */ +@Exercise("hello-annotation-basic") public class HelloAnnotationsExercise { // todo: mark class with the annotation according to the javadoc } diff --git a/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Node.java b/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Node.java index b01a4acfb..97c0c5a82 100644 --- a/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Node.java +++ b/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Node.java @@ -9,5 +9,10 @@ * @author Taras Boychuk */ public class Node { - // todo: + T element; + Node next; + + public Node(T element) { + this.element = element; + } } diff --git a/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Nodes.java b/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Nodes.java index 5321aa53a..cfe213778 100644 --- a/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Nodes.java +++ b/2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Nodes.java @@ -1,7 +1,5 @@ package com.bobobode.cs; -import com.bobocode.util.ExerciseNotCompletedException; - /** * A class that consists of static methods only and provides util methods for {@link Node}. *

@@ -22,7 +20,7 @@ private Nodes() { * @return a new instance of {@link Node} */ public static Node create(T element) { - throw new ExerciseNotCompletedException(); // todo: + return new Node<>(element); } /** @@ -33,7 +31,7 @@ public static Node create(T element) { * @param a genetic type */ public static void link(Node first, Node second) { - throw new ExerciseNotCompletedException(); // todo: + first.next = second; } /** @@ -46,7 +44,9 @@ public static void link(Node first, Node second) { * @return a reference to a first node created based on firstElement */ public static Node pairOf(T firstElement, T secondElement) { - throw new ExerciseNotCompletedException(); // todo: + Node firstNode = new Node<>(firstElement); + firstNode.next = new Node<>(secondElement); + return firstNode; } /** @@ -60,7 +60,11 @@ public static Node pairOf(T firstElement, T secondElement) { * @return a reference to the first node */ public static Node closedPairOf(T firstElement, T secondElement) { - throw new ExerciseNotCompletedException(); // todo: + Node firstNode = new Node<>(firstElement); + Node secondNode = new Node<>(secondElement); + firstNode.next = secondNode; + secondNode.next = firstNode; + return firstNode; } /** @@ -72,7 +76,13 @@ public static Node closedPairOf(T firstElement, T secondElement) { * @return a reference to the first element of the chain */ public static Node chainOf(T... elements) { - throw new ExerciseNotCompletedException(); // todo: + Node firstNode = new Node<>(elements[0]); + Node current = firstNode; + for (int i = 1; i < elements.length; i++) { + current.next = new Node<>(elements[i]); + current = current.next; + } + return firstNode; } /** @@ -85,6 +95,13 @@ public static Node chainOf(T... elements) { * @return a reference to the first element of the chain */ public static Node circleOf(T... elements) { - throw new ExerciseNotCompletedException(); // todo: + Node firstNode = new Node<>(elements[0]); + Node current = firstNode; + for (int i = 1; i < elements.length; i++) { + current.next = new Node<>(elements[i]); + current = current.next; + } + current.next = firstNode; + return firstNode; } } diff --git a/2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/LinkedStack.java b/2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/LinkedStack.java index 05b0c724d..4a15e0814 100644 --- a/2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/LinkedStack.java +++ b/2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/LinkedStack.java @@ -1,7 +1,8 @@ package com.bobocode.cs; import com.bobocode.cs.exception.EmptyStackException; -import com.bobocode.util.ExerciseNotCompletedException; +import java.util.Objects; +import java.util.stream.Stream; /** * {@link LinkedStack} is a stack implementation that is based on singly linked generic nodes. @@ -15,6 +16,21 @@ * @author Serhii Hryhus */ 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 @@ -24,7 +40,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; } /** @@ -35,7 +53,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++; } /** @@ -47,7 +71,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; } /** @@ -57,7 +92,7 @@ public T pop() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -67,7 +102,7 @@ public int size() { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return head == null; } } diff --git a/2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/LinkedQueue.java b/2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/LinkedQueue.java index 7b4a79667..ce9bd4911 100644 --- a/2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/LinkedQueue.java +++ b/2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/LinkedQueue.java @@ -1,7 +1,5 @@ package com.bobocode.cs; -import com.bobocode.util.ExerciseNotCompletedException; - /** * {@link LinkedQueue} implements FIFO {@link Queue}, using singly linked nodes. Nodes are stores in instances of nested * class Node. In order to perform operations {@link LinkedQueue#add(Object)} and {@link LinkedQueue#poll()} @@ -15,6 +13,22 @@ * @author Ivan Virchenko */ public class LinkedQueue implements Queue { + static final class Node { + private T element; + private Node next; + + static Node valueOf(T element) { + return new Node<>(element); + } + + private Node(T element) { + this.element = element; + } + } + + private Node head; + private Node tail; + private int size; /** * Adds an element to the end of the queue. @@ -22,7 +36,14 @@ public class LinkedQueue implements Queue { * @param element the element to add */ public void add(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node newNode = Node.valueOf(element); + if (head == null) { + head = tail = newNode; + } else { + tail.next = newNode; + tail = newNode; + } + size++; } /** @@ -31,7 +52,17 @@ public void add(T element) { * @return an element that was retrieved from the head or null if queue is empty */ public T poll() { - throw new ExerciseNotCompletedException(); // todo: implement this method + if (head != null) { + T element = head.element; + head = head.next; + if (head == null) { + tail = null; + } + size--; + return element; + } else { + return null; + } } /** @@ -40,7 +71,7 @@ public T poll() { * @return an integer value that is a size of queue */ public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -49,6 +80,6 @@ public int size() { * @return {@code true} if the queue is empty, returns {@code false} if it's not */ public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return head == null; } } diff --git a/2-0-data-structures-and-algorithms/2-2-4-linked-list/src/main/java/com/bobocode/cs/LinkedList.java b/2-0-data-structures-and-algorithms/2-2-4-linked-list/src/main/java/com/bobocode/cs/LinkedList.java index 502fefbdf..0c8f5f15e 100644 --- a/2-0-data-structures-and-algorithms/2-2-4-linked-list/src/main/java/com/bobocode/cs/LinkedList.java +++ b/2-0-data-structures-and-algorithms/2-2-4-linked-list/src/main/java/com/bobocode/cs/LinkedList.java @@ -1,7 +1,8 @@ package com.bobocode.cs; - -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 @@ -15,6 +16,9 @@ * @author Serhii Hryhus */ public class LinkedList implements List { + private Node first; + private Node last; + private int size; /** * This method creates a list of provided elements @@ -24,7 +28,9 @@ public class LinkedList implements List { * @return a new list of elements the were passed as method parameters */ public static LinkedList of(T... elements) { - throw new ExerciseNotCompletedException(); // todo: implement this method + LinkedList linkedList = new LinkedList<>(); + Stream.of(elements).forEach(linkedList::add); + return linkedList; } /** @@ -34,7 +40,14 @@ public static LinkedList of(T... elements) { */ @Override public void add(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node newNode = new Node<>(element); + if (size == 0) { + first = last = newNode; + } else { + last.next = newNode; + last = newNode; + } + size++; } /** @@ -46,7 +59,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 = first; + first = newNode; + if (first.next == null) { + last = first; + } + } + + private void addAsTail(Node newNode) { + last.next = newNode; + last = 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 last; + } else { + return nodeAt(index); + } + } + + private Node nodeAt(int index) { + Node currentNode = first; + for (int i = 0; i < index; i++) { + currentNode = currentNode.next; + } + return currentNode; } /** @@ -58,7 +115,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; } /** @@ -70,7 +128,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; } /** @@ -81,7 +140,8 @@ public T get(int index) { */ @Override public T getFirst() { - throw new ExerciseNotCompletedException(); // todo: implement this method + checkElementsExist(); + return first.value; } /** @@ -92,7 +152,14 @@ public T getFirst() { */ @Override public T getLast() { - throw new ExerciseNotCompletedException(); // todo: implement this method + checkElementsExist(); + return last.value; + } + + private void checkElementsExist() { + if (first == null) { + throw new NoSuchElementException(); + } } /** @@ -104,9 +171,28 @@ public T getLast() { */ @Override public T remove(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + T deletedElement; + if (index == 0 && !isEmpty()) { + deletedElement = first.value; + removeHead(); + } else { + Node previousNode = findNodeByIndex(index - 1); + deletedElement = previousNode.next.value; + previousNode.next = previousNode.next.next; + if (index == size - 1) { + last = previousNode; + } + } + size--; + return deletedElement; } + private void removeHead() { + first = first.next; + if (first == null) { + last = null; + } + } /** * Checks if a specific exists in he list @@ -115,7 +201,14 @@ public T remove(int index) { */ @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + Node currentNode = first; + while (currentNode != null) { + if (currentNode.value.equals(element)) { + return true; + } + currentNode = currentNode.next; + } + return false; } /** @@ -125,7 +218,7 @@ public boolean contains(T element) { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return first == null; } /** @@ -135,7 +228,7 @@ public boolean isEmpty() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -143,6 +236,20 @@ public int size() { */ @Override public void clear() { - throw new ExerciseNotCompletedException(); // todo: implement this method + first = last = 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); + } } } diff --git a/2-0-data-structures-and-algorithms/2-2-5-array-list/src/main/java/com/bobocode/cs/ArrayList.java b/2-0-data-structures-and-algorithms/2-2-5-array-list/src/main/java/com/bobocode/cs/ArrayList.java index 384ca81e5..10be12b87 100644 --- a/2-0-data-structures-and-algorithms/2-2-5-array-list/src/main/java/com/bobocode/cs/ArrayList.java +++ b/2-0-data-structures-and-algorithms/2-2-5-array-list/src/main/java/com/bobocode/cs/ArrayList.java @@ -1,6 +1,8 @@ package com.bobocode.cs; -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 @@ -12,6 +14,9 @@ * @author Serhii Hryhus */ public class ArrayList implements List { + private 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. @@ -20,7 +25,10 @@ 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) { + throw new IllegalArgumentException(); + } + elementData = new Object[initCapacity]; } /** @@ -28,7 +36,7 @@ public ArrayList(int initCapacity) { * A default size of inner array is 5; */ public ArrayList() { - throw new ExerciseNotCompletedException(); // todo: implement this method + this(DEFAULT_CAPACITY); } /** @@ -38,7 +46,10 @@ public ArrayList() { * @return new instance */ public static List of(T... elements) { - throw new ExerciseNotCompletedException(); // todo: implement this method + ArrayList list = new ArrayList<>(elements.length); + list.elementData = Arrays.copyOf(elements, elements.length); + list.size = elements.length; + return list; } /** @@ -48,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 = Arrays.copyOf(elementData, size * 2); + } } /** @@ -59,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++; } /** @@ -70,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]; } /** @@ -81,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]; } /** @@ -92,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]; } /** @@ -105,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; } /** @@ -116,8 +149,16 @@ public void set(int index, T element) { * @return deleted element */ @Override - public T remove(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + @SuppressWarnings("unchecked") + public T remove(int index) {// 4,5,3,6,7,7 -> remove(3) + Objects.checkIndex(index, size); + T deletedElement = (T) elementData[index]; + if (index < size - 1) { + System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); + } + elementData[size - 1] = null; + size--; + return deletedElement; } /** @@ -128,7 +169,12 @@ public T remove(int index) { */ @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); // todo: implement this method + for (int i = 0; i < size; i++) { + if (elementData[i].equals(element)) { + return true; + } + } + return false; } /** @@ -138,7 +184,7 @@ public boolean contains(T element) { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size == 0; } /** @@ -146,7 +192,7 @@ public boolean isEmpty() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -154,6 +200,7 @@ public int size() { */ @Override public void clear() { - throw new ExerciseNotCompletedException(); // todo: implement this method + elementData = new Object[DEFAULT_CAPACITY]; + size = 0; } } diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java b/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java index a4e394b0b..9b821d1da 100644 --- a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java +++ b/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java @@ -1,8 +1,8 @@ package com.bobocode.cs; -import com.bobocode.util.ExerciseNotCompletedException; - +import java.util.Objects; import java.util.function.Consumer; +import java.util.stream.Stream; /** * {@link RecursiveBinarySearchTree} is an implementation of a {@link BinarySearchTree} that is based on a linked nodes @@ -17,33 +17,123 @@ * @author Maksym Stasiuk */ public class RecursiveBinarySearchTree> implements BinarySearchTree { + private static class Node { + T element; + Node left; + Node right; + + private Node(T element) { + this.element = element; + } + + public static Node valueOf(T element) { + return new Node<>(element); + } + } + + private Node root; + private int size = 0; public static > RecursiveBinarySearchTree of(T... elements) { - throw new ExerciseNotCompletedException(); + RecursiveBinarySearchTree bst = new RecursiveBinarySearchTree<>(); + Stream.of(elements).forEach(bst::insert); + return bst; } @Override public boolean insert(T element) { - throw new ExerciseNotCompletedException(); + Objects.requireNonNull(element); + boolean isInserted = insertElement(element); + if (isInserted) { + size++; + } + return isInserted; + } + + boolean insertElement(T element) { + if (root == null) { + root = Node.valueOf(element); + return true; + } else { + return insertIntoSubTree(root, element); + } + } + + private boolean insertIntoSubTree(Node subTreeRoot, T element) { + if (subTreeRoot.element.compareTo(element) > 0) { + return insertIntoLeftSubtree(subTreeRoot, element); + } else if (subTreeRoot.element.compareTo(element) < 0) { + return insertIntoRightSubtree(subTreeRoot, element); + } else { + return false; + } + } + + private boolean insertIntoLeftSubtree(Node node, T element) { + if (node.left != null) { + return insertIntoSubTree(node.left, element); + } else { + node.left = Node.valueOf(element); + return true; + } + } + + private boolean insertIntoRightSubtree(Node node, T element) { + if (node.right != null) { + return insertIntoSubTree(node.right, element); + } else { + node.right = Node.valueOf(element); + return true; + } } + @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); + Objects.requireNonNull(element); + return findChildNodeByElement(root, element) != null; + } + + private Node findChildNodeByElement(Node node, T element) { + if (node == null) { + return null; + } else if (node.element.compareTo(element) > 0) { + return findChildNodeByElement(node.left, element); + } else if (node.element.compareTo(element) < 0) { + return findChildNodeByElement(node.right, element); + } else { + return node; + } } @Override public int size() { - throw new ExerciseNotCompletedException(); + return size; } @Override public int depth() { - throw new ExerciseNotCompletedException(); + return root != null ? depth(root) - 1 : 0; + } + + private int depth(Node node) { + if (node == null) { + return 0; + } else { + return 1 + Math.max(depth(node.left), depth(node.right)); + } } @Override public void inOrderTraversal(Consumer consumer) { - throw new ExerciseNotCompletedException(); + inOrderTraversal(root, consumer); + } + + private void inOrderTraversal(Node node, Consumer consumer) { + if (node != null) { + inOrderTraversal(node.left, consumer); + consumer.accept(node.element); + inOrderTraversal(node.right, consumer); + } } } diff --git a/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/HashTable.java b/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/HashTable.java index ade9cea4d..31a2d9dfb 100644 --- a/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/HashTable.java +++ b/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/HashTable.java @@ -1,6 +1,8 @@ package com.bobocode.cs; -import com.bobocode.util.ExerciseNotCompletedException; +import lombok.ToString; + +import static java.util.Objects.requireNonNull; /** * {@link HashTable} is a simple Hashtable-based implementation of {@link Map} interface with some additional methods. @@ -27,6 +29,20 @@ * @author Taras Boychuk */ public class HashTable implements Map { + private static final int DEFAULT_CAPACITY = 8; + private static final float RESIZE_THRESHOLD = 1.0f; + private Node[] table; + private int size; + + @SuppressWarnings("unchecked") + public HashTable(int initialCapacity) { + verifyCapacity(initialCapacity); + this.table = new Node[initialCapacity]; + } + + public HashTable() { + this(DEFAULT_CAPACITY); + } /** * This method is a critical part of the hast table. The main idea is that having a key, you can calculate its index @@ -43,7 +59,8 @@ public class HashTable implements Map { * @return array index of the given key */ public static int calculateIndex(Object key, int tableCapacity) { - throw new ExerciseNotCompletedException(); // todo: + var hash = key.hashCode() ^ (key.hashCode() >> 16); + return hash & (tableCapacity - 1); } /** @@ -59,7 +76,40 @@ public static int calculateIndex(Object key, int tableCapacity) { */ @Override public V put(K key, V value) { - throw new ExerciseNotCompletedException(); // todo: + resizeIfNeeded(); + return putOnTable(table, key, value); + } + + private void resizeIfNeeded() { + if (size / (float) table.length > RESIZE_THRESHOLD) { + resizeTable(2 * table.length); + } + } + + private V putOnTable(Node[] table, K key, V value) { + var newNode = new Node<>(requireNonNull(key), requireNonNull(value)); + var index = calculateIndex(key, table.length); + if (table[index] == null) { // add new head key + table[index] = newNode; + } else { + var current = table[index]; + while (current.next != null) { // iterate linked list to new key + if (current.key.equals(key)) { + var prevValue = current.value; + current.value = value; + return prevValue; + } + current = current.next; + } + if (current.key.equals(key)) { + var prevValue = current.value; + current.value = value; + return prevValue; + } + current.next = newNode; // attach new key to the end of the list + } + size++; + return null; } /** @@ -71,7 +121,15 @@ public V put(K key, V value) { */ @Override public V get(K key) { - throw new ExerciseNotCompletedException(); // todo: + var index = calculateIndex(requireNonNull(key), table.length); + var current = table[index]; + while (current != null) { + if (current.key.equals(key)) { + return current.value; + } + current = current.next; + } + return null; } /** @@ -82,7 +140,7 @@ public V get(K key) { */ @Override public boolean containsKey(K key) { - throw new ExerciseNotCompletedException(); // todo: + return get(key) != null; } /** @@ -93,9 +151,19 @@ public boolean containsKey(K key) { */ @Override public boolean containsValue(V value) { - throw new ExerciseNotCompletedException(); // todo: + for (var head : table) { + var current = head; + while (current != null) { + if (current.value.equals(value)) { + return true; + } + current = current.next; + } + } + return false; } + /** * Return a number of elements in the table. * @@ -103,7 +171,7 @@ public boolean containsValue(V value) { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: + return size; } /** @@ -113,7 +181,7 @@ public int size() { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: + return size == 0; } /** @@ -124,7 +192,26 @@ public boolean isEmpty() { */ @Override public V remove(K key) { - throw new ExerciseNotCompletedException(); // todo: + var index = calculateIndex(requireNonNull(key), table.length); + var current = table[index]; + if (current != null) { + if (current.key.equals(key)) { + var value = current.value; + table[index] = current.next; + size--; + return value; + } + while (current.next != null) { + if (current.next.key.equals(key)) { + var value = current.next.value; + current.next = current.next.next; + size--; + return value; + } + current = current.next; + } + } + return null; } /** @@ -150,7 +237,22 @@ public V remove(K key) { */ @Override public String toString() { - throw new ExerciseNotCompletedException(); // todo: + var stringBuilder = new StringBuilder(); + var n = table.length; + for (int i = 0; i < n; i++) { // iterate array + stringBuilder.append(i).append(": "); + var current = table[i]; + if (current != null) { + while (current.next != null) { // iterate each linked list + stringBuilder.append(current.key).append("=").append(current.value).append(" -> "); + current = current.next; + } + stringBuilder.append(current.key).append("=").append(current.value).append("\n"); + } else { + stringBuilder.append("\n"); + } + } + return stringBuilder.toString(); } /** @@ -160,13 +262,42 @@ public String toString() { * (You can imagine a hash table, with a default capacity of 8 that stores hundreds of thousands of elements. * In that case it's just 8 huge linked lists. That's why we need this method.) *

- * PLEASE NOTE that such method should not be a part of the public API, but it was made public - * for learning purposes. You can create a table, print it using toString, then resizeTable and print it again. + * PLEASE NOTE that such method should be a part of the implementation details, but it was made public for learning + * purposes. You can create a table, print it using toString, then resizeTable and print it again. * It will help you to understand how it works. * * @param newCapacity a size of the new underlying array */ public void resizeTable(int newCapacity) { - throw new ExerciseNotCompletedException(); // todo: + verifyCapacity(newCapacity); + @SuppressWarnings("unchecked") Node[] newTable = new Node[newCapacity]; + size = 0; + for (var head : table) { + var current = head; + while (current != null) { + putOnTable(newTable, current.key, current.value); + current = current.next; + } + } + table = newTable; + } + + private void verifyCapacity(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity (table array size) must be positive"); + } } + + @ToString(exclude = "next") + public static class Node { + T key; + V value; + Node next; + + public Node(T key, V value) { + this.key = key; + this.value = value; + } + } + } diff --git a/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/test/java/com/bobocode/cs/HashTableTest.java b/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/test/java/com/bobocode/cs/HashTableTest.java index b7355496c..1a3565c20 100644 --- a/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/test/java/com/bobocode/cs/HashTableTest.java +++ b/2-0-data-structures-and-algorithms/2-2-9-hash-table/src/test/java/com/bobocode/cs/HashTableTest.java @@ -503,10 +503,10 @@ class HashTableHelperMethodsTest { @Order(1) @DisplayName("resizeTable creates a new array and put there all elements") void resizeTable() { - addToTable("madmax", 833); - addToTable("altea", 553); - addToTable("AaAa", 123); - addToTable("BBBB", 456); + hashTable.put("madmax", 833); + hashTable.put("altea", 553); + hashTable.put("AaAa", 123); + hashTable.put("BBBB", 456); hashTable.resizeTable(16); @@ -518,6 +518,20 @@ void resizeTable() { } @Test + @Order(2) + @DisplayName("resizeTable does not change the size") + void resizeTableDoesNotChangeSize() { + hashTable.put("madmax", 833); + hashTable.put("altea", 553); + hashTable.put("AaAa", 123); + + hashTable.resizeTable(32); + + assertThat(hashTable.size()).isEqualTo(3); + } + + @Test + @Order(3) @DisplayName("toString returns a string that represents an underlying table") void toStringTest() { addToTable("madmax", 833); diff --git a/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaderException.java b/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaderException.java new file mode 100644 index 000000000..19ed36ef6 --- /dev/null +++ b/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaderException.java @@ -0,0 +1,7 @@ +package com.bobocode.se; + +public class FileReaderException extends RuntimeException { + public FileReaderException(String message, Exception e) { + super(message, e); + } +} diff --git a/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaders.java b/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaders.java index 6370a3638..1c2f8a54f 100644 --- a/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaders.java +++ b/3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaders.java @@ -1,6 +1,15 @@ package com.bobocode.se; -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 +23,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); + } } } diff --git a/3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStats.java b/3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStats.java index 56b4aa596..88dd6a7b6 100644 --- a/3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStats.java +++ b/3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStats.java @@ -1,12 +1,28 @@ package com.bobocode.se; -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.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. * @@ -14,7 +30,47 @@ public class FileStats { * @return new FileStats object created from text file */ public static FileStats from(String fileName) { - throw new ExerciseNotCompletedException(); //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())); } /** @@ -24,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 ExerciseNotCompletedException(); //todo + return characterCountMap.get(character).intValue(); } /** @@ -33,7 +89,15 @@ public int getCharCount(char character) { * @return the most frequently appeared character */ public char getMostPopularCharacter() { - throw new ExerciseNotCompletedException(); //todo + return mostPopularCharacter; + } + + private char findMostPopularCharacter(Map characterCountMap) { + return characterCountMap.entrySet() + .stream() + .max(Comparator.comparing(Map.Entry::getValue)) + .get() + .getKey(); } /** @@ -43,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 ExerciseNotCompletedException(); //todo + return characterCountMap.containsKey(character); } -} +} \ No newline at end of file diff --git a/3-0-java-core/3-6-3-crazy-regex/src/main/java/com/bobocode/se/CrazyRegex.java b/3-0-java-core/3-6-3-crazy-regex/src/main/java/com/bobocode/se/CrazyRegex.java index e213d3f26..b6e1793c6 100644 --- a/3-0-java-core/3-6-3-crazy-regex/src/main/java/com/bobocode/se/CrazyRegex.java +++ b/3-0-java-core/3-6-3-crazy-regex/src/main/java/com/bobocode/se/CrazyRegex.java @@ -2,6 +2,7 @@ import com.bobocode.util.ExerciseNotCompletedException; +import java.util.regex.Matcher; import java.util.regex.Pattern; /** @@ -21,7 +22,7 @@ public class CrazyRegex { * @return a pattern that looks for the word "Curiosity" */ public Pattern findSpecificWord() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("Curiosity"); } /** @@ -30,7 +31,7 @@ public Pattern findSpecificWord() { * @return a pattern that looks for the first word in text */ public Pattern findFirstWord() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("^\\w+"); } /** @@ -39,7 +40,7 @@ public Pattern findFirstWord() { * @return a pattern that looks for the last word in text */ public Pattern findLastWord() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\w+$"); } /** @@ -50,7 +51,7 @@ public Pattern findLastWord() { * @return a pattern that looks for numbers */ public Pattern findAllNumbers() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\d+"); } /** @@ -59,7 +60,7 @@ public Pattern findAllNumbers() { * @return a pattern that looks for dates */ public Pattern findDates() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); } /** @@ -69,7 +70,7 @@ public Pattern findDates() { * @return a pattern that looks for different variations of word "color" */ public Pattern findDifferentSpellingsOfColor() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("colou?rs?"); } /** @@ -80,7 +81,7 @@ public Pattern findDifferentSpellingsOfColor() { * @return a pattern that looks for zip codes */ public Pattern findZipCodes() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\s\\d{5}\\s"); } /** @@ -90,7 +91,7 @@ public Pattern findZipCodes() { * @return a pattern that looks for different variations of word "link" */ public Pattern findDifferentSpellingsOfLink() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("l[yi (]nk"); } /** @@ -100,7 +101,7 @@ public Pattern findDifferentSpellingsOfLink() { * @return a pattern that looks for phone numbers */ public Pattern findSimplePhoneNumber() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d"); } /** @@ -111,7 +112,7 @@ public Pattern findSimplePhoneNumber() { * @return a pattern that looks for numbers with length 3 and digits from 0 to 5 in the middle */ public Pattern findNumbersFromZeroToFiveWithLengthThree() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("[0-5]{3}"); } /** @@ -120,7 +121,7 @@ public Pattern findNumbersFromZeroToFiveWithLengthThree() { * @return a pattern that looks for the words that have length 5 */ public Pattern findAllWordsWithFiveLength() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\b[A-Za-z]{5}\\b"); } /** @@ -131,7 +132,7 @@ public Pattern findAllWordsWithFiveLength() { * @return a pattern that looks for words and numbers that not shorter 2 and not longer 3 */ public Pattern findAllLettersAndDigitsWithLengthThree() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\b\\w{2,3}\\b"); } /** @@ -140,7 +141,7 @@ public Pattern findAllLettersAndDigitsWithLengthThree() { * @return a pattern that looks for the words that begin with capital letter */ public Pattern findAllWordsWhichBeginWithCapitalLetter() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\b[A-Z][a-z]*\\b"); } /** @@ -150,7 +151,7 @@ public Pattern findAllWordsWhichBeginWithCapitalLetter() { * @return a pattern that looks for the abbreviations above */ public Pattern findAbbreviation() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("A[KLRZ]|C[AOT]|P[RAD]"); } /** @@ -159,7 +160,7 @@ public Pattern findAbbreviation() { * @return a pattern that looks for all open braces */ public Pattern findAllOpenBraces() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("(\\{+)"); } /** @@ -168,7 +169,7 @@ public Pattern findAllOpenBraces() { * @return a pattern that looks for everything inside [] */ public Pattern findOnlyResources() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("(?<=\\[).+?(?=\\])"); } /** @@ -177,7 +178,7 @@ public Pattern findOnlyResources() { * @return a pattern that looks for all https links in note.txt */ public Pattern findOnlyLinksInNote() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("https://((www.)?+[\\w]+(.))com"); } /** @@ -186,7 +187,7 @@ public Pattern findOnlyLinksInNote() { * @return a pattern that looks for all http links in nasa.json */ public Pattern findOnlyLinksInJson() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("http://(.*)JPG"); } /** @@ -195,7 +196,7 @@ public Pattern findOnlyLinksInJson() { * @return a pattern that looks for all .com, .net and .edu emails */ public Pattern findAllEmails() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("[\\w.]+@[\\w]+\\.(net|com|edu)"); } /** @@ -207,7 +208,7 @@ public Pattern findAllEmails() { * @return a pattern that looks for phone numbers patterns above */ public Pattern findAllPatternsForPhoneNumbers() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\(?\\d{3}[-.)]\\d{3}[-.]\\d{4}"); } /** @@ -216,7 +217,7 @@ public Pattern findAllPatternsForPhoneNumbers() { * @return a pattern that looks for duplicates */ public Pattern findOnlyDuplicates() { - throw new ExerciseNotCompletedException(); + return Pattern.compile("\\b(\\w+)\\s\\1\\b"); } /** @@ -227,7 +228,8 @@ public Pattern findOnlyDuplicates() { * @return String where all names recorded as last name first name */ public String replaceFirstAndLastNames(String names) { - throw new ExerciseNotCompletedException(); + Matcher matcher = Pattern.compile("(\\w+),\\s+(\\w+)").matcher(names); + return matcher.replaceAll("$2 $1"); } /** @@ -238,7 +240,8 @@ public String replaceFirstAndLastNames(String names) { * @return String where in all phone numbers last 7 digits replaced to X */ public String replaceLastSevenDigitsOfPhoneNumberToX(String phones) { - throw new ExerciseNotCompletedException(); + Matcher matcher = Pattern.compile("\\(?(\\d{3})[-.)]\\d{3}[-.]\\d{4}").matcher(phones); + return matcher.replaceAll("$1-XXX-XXXX"); } /** @@ -250,6 +253,7 @@ public String replaceLastSevenDigitsOfPhoneNumberToX(String phones) { * @return String where all resources embraced in href */ public String insertLinksAndResourcesIntoHref(String links) { - throw new ExerciseNotCompletedException(); + Matcher matcher = Pattern.compile("\\[(.*?)]\\((http.*?)\\)").matcher(links); + return matcher.replaceAll("$1"); } } diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java index 760989875..630b21c67 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java @@ -1,7 +1,12 @@ package com.bobocode.se; -import com.bobocode.util.ExerciseNotCompletedException; +import static java.util.Objects.requireNonNull; + +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Comparator; +import java.util.Objects; +import lombok.SneakyThrows; /** * A generic comparator that is comparing a random field of the given class. The field is either primitive or @@ -18,13 +23,17 @@ */ public class RandomFieldComparator implements Comparator { + private final Class targetType; + private final Field fieldToCompare; + public RandomFieldComparator(Class targetType) { - throw new ExerciseNotCompletedException(); // todo: implement this constructor; + this.targetType = requireNonNull(targetType); + this.fieldToCompare = chooseFieldToCompare(targetType); } /** * Compares two objects of the class T by the value of the field that was randomly chosen. It allows null values - * for the fields, and it treats null value greater than a non-null value. + * for the fields, and it treats null value grater than a non-null value. * * @param o1 * @param o2 @@ -34,14 +43,16 @@ public RandomFieldComparator(Class targetType) { */ @Override public int compare(T o1, T o2) { - throw new ExerciseNotCompletedException(); // todo: implement this method; + Objects.requireNonNull(o1); + Objects.requireNonNull(o2); + return compareFieldValues(o1, o2); } /** * Returns the name of the randomly-chosen comparing field. */ public String getComparingFieldName() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return fieldToCompare.getName(); } /** @@ -52,6 +63,23 @@ public String getComparingFieldName() { */ @Override public String toString() { - throw new ExerciseNotCompletedException(); // todo: implement this method; + return String.format("Random field comparator of class '%s' is comparing '%s'", targetType.getSimpleName(), + getComparingFieldName()); + } + + private Field chooseFieldToCompare(Class targetType) { + return Arrays.stream(targetType.getDeclaredFields()) + .filter(f -> Comparable.class.isAssignableFrom(f.getType()) || f.getType().isPrimitive()) + .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare")); + } + + @SneakyThrows + @SuppressWarnings("unchecked") + private > int compareFieldValues(T o1, T o2) { + fieldToCompare.setAccessible(true); + var value1 = (U) fieldToCompare.get(o1); + var value2 = (U) fieldToCompare.get(o2); + Comparator comparator = Comparator.nullsLast(Comparator.naturalOrder()); + return comparator.compare(value1, value2); } } diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java index 469f30cd8..1733568e5 100644 --- a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java +++ b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java @@ -82,7 +82,7 @@ void compareWhenFieldValuesOfSecondObjectIsNull() { @Order(7) @SneakyThrows @DisplayName("Method 'compare' returns positive int when the first value is greater") - void compareWhenFieldValueOfFirstObjectIsGreater() { + void compareWhenFieldValueOfFirstObjectIsGrater() { var fieldToCompareName = "firstName"; Account account1 = new Account(); Account account2 = new Account(); @@ -102,7 +102,7 @@ void compareWhenFieldValueOfFirstObjectIsGreater() { @Order(8) @SneakyThrows @DisplayName("Method 'compare' returns negative int when the first value is smaller") - void compareWhenFieldValueOfSecondObjectIsGreater() { + void compareWhenFieldValueOfSecondObjectIsGrater() { var fieldToCompareName = "firstName"; Account account1 = new Account(); Account account2 = new Account(); @@ -142,7 +142,7 @@ void compareWhenFieldValuesOfObjectsAreEqual() { @Order(10) @SneakyThrows @DisplayName("Method 'compare' returns positive int when the first primitive value is greater") - void comparePrimitivesWhenFieldValueOfFirstObjectIsGreater() { + void comparePrimitivesWhenFieldValueOfFirstObjectIsGrater() { var fieldToCompareName = "age"; Account account1 = new Account(); Account account2 = new Account(); @@ -182,7 +182,7 @@ void comparePrimitivesWhenFieldValuesOfObjectsAreEqual() { @Order(12) @SneakyThrows @DisplayName("Method 'compare' returns negative int when the first primitive value is smaller") - void comparePrimitivesWhenFieldValueOfSecondObjectIsGreater() { + void comparePrimitivesWhenFieldValueOfSecondObjectIsGrater() { var fieldToCompareName = "age"; Account account1 = new Account(); Account account2 = new Account(); diff --git a/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/data/FlightDao.java b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/data/FlightDao.java index 3e58c3b33..6865612f7 100644 --- a/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/data/FlightDao.java +++ b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/data/FlightDao.java @@ -1,6 +1,6 @@ package com.bobocode.oop.data; -import com.bobocode.util.ExerciseNotCompletedException; +import com.bobocode.oop.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/4-3-1-flight-search/src/main/java/com/bobocode/oop/factory/FlightServiceFactory.java b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/factory/FlightServiceFactory.java index 8cd2ed673..f7568b934 100644 --- a/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/factory/FlightServiceFactory.java +++ b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/factory/FlightServiceFactory.java @@ -1,7 +1,7 @@ package com.bobocode.oop.factory; +import com.bobocode.oop.data.FlightDao; import com.bobocode.oop.service.FlightService; -import com.bobocode.util.ExerciseNotCompletedException; /** * {@link FlightServiceFactory} is used to create an instance of {@link FlightService} @@ -16,6 +16,6 @@ public class FlightServiceFactory { * @return FlightService */ public FlightService creteFlightService() { - throw new ExerciseNotCompletedException(); + return new FlightService(new FlightDao()); } } diff --git a/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/FlightService.java b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/FlightService.java index b31cd8f07..9b420412a 100644 --- a/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/FlightService.java +++ b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/FlightService.java @@ -1,9 +1,9 @@ package com.bobocode.oop.service; -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 *

@@ -12,6 +12,12 @@ */ public class FlightService { + private Flights flights; + + public FlightService(Flights flights) { + this.flights = flights; + } + /** * Adds a new flight number * @@ -19,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); } /** @@ -29,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/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/Flights.java b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/Flights.java new file mode 100644 index 000000000..be6793a19 --- /dev/null +++ b/4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/Flights.java @@ -0,0 +1,9 @@ +package com.bobocode.oop.service; + +import java.util.Set; + +public interface Flights { + boolean register(String flightNumber); + + Set findAll(); +} diff --git a/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/FunctionMap.java b/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/FunctionMap.java index 446b9d27e..680a7d956 100644 --- a/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/FunctionMap.java +++ b/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/FunctionMap.java @@ -1,7 +1,6 @@ package com.bobocode.fp; import com.bobocode.fp.exception.InvalidFunctionNameException; - import java.util.HashMap; import java.util.Map; import java.util.function.Function; diff --git a/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/Functions.java b/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/Functions.java index a1eed08d4..a07723fc2 100644 --- a/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/Functions.java +++ b/5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/Functions.java @@ -1,5 +1,7 @@ package com.bobocode.fp; +import static java.lang.Math.abs; + /** * An util class that provides a factory method for creating an instance of a {@link FunctionMap} filled with a list * of functions. @@ -28,7 +30,11 @@ private Functions() { public static FunctionMap intFunctionMap() { FunctionMap intFunctionMap = new FunctionMap<>(); - // todo: according to the javadoc add functions using lambda expression + intFunctionMap.addFunction("square", n -> n * n); + intFunctionMap.addFunction("abs", Math::abs); + intFunctionMap.addFunction("increment", n -> n + 1); + intFunctionMap.addFunction("decrement", n -> n - 1); + intFunctionMap.addFunction("sgn", n -> (n != 0) ? n / abs(n) : 0); return intFunctionMap; } diff --git a/5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/SumOfSquares.java b/5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/SumOfSquares.java index b043454d1..06f8e214b 100644 --- a/5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/SumOfSquares.java +++ b/5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/SumOfSquares.java @@ -2,6 +2,8 @@ import com.bobocode.fp.exception.InvalidRangeException; +import java.util.stream.IntStream; + /** * This class allow to calculate a sum of squares of integer number in a certain range. It was implemented using * OO approach. Your job is to refactor it using functional approach. E.g. avoid using mutable variables @@ -25,11 +27,8 @@ static int calculateSumOfSquaresInRange(int startInclusive, int endInclusive) { throw new InvalidRangeException(); } - // todo: refactor using functional approach – instead of using for loop, use IntStream.rangeClose() - int sumOfSquares = 0; - for (int i = startInclusive; i <= endInclusive; i++) { - sumOfSquares += i * i; - } - return sumOfSquares; + return IntStream.rangeClosed(startInclusive, endInclusive) + .map(a -> a * a) + .sum(); } } diff --git a/5-0-functional-programming/5-1-1-crazy-lambdas/src/main/java/com/bobocode/fp/CrazyLambdas.java b/5-0-functional-programming/5-1-1-crazy-lambdas/src/main/java/com/bobocode/fp/CrazyLambdas.java index 78feebde2..4f9810273 100644 --- a/5-0-functional-programming/5-1-1-crazy-lambdas/src/main/java/com/bobocode/fp/CrazyLambdas.java +++ b/5-0-functional-programming/5-1-1-crazy-lambdas/src/main/java/com/bobocode/fp/CrazyLambdas.java @@ -5,6 +5,7 @@ import java.math.BigDecimal; import java.util.Comparator; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.*; /** @@ -27,7 +28,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"; } /** @@ -36,7 +38,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; } /** @@ -46,7 +50,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; } /** @@ -56,7 +64,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; } /** @@ -68,7 +77,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; } /** @@ -77,7 +90,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(); } @@ -87,7 +101,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); } /** @@ -96,7 +112,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; } /** @@ -105,7 +122,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; } /** @@ -114,7 +133,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; } /** @@ -125,7 +146,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; } /** @@ -134,7 +159,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); } /** @@ -145,7 +176,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; + }; } /** @@ -154,7 +194,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(); } /** @@ -164,7 +207,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; + }; } /** @@ -177,7 +226,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; } /** @@ -188,7 +243,11 @@ 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()); } /** @@ -206,7 +265,7 @@ public static BiFunction, String, IntUnaryOperator * @return a comparator instance */ public static > Comparator comparing(Function mapper) { - throw new ExerciseNotCompletedException(); + return (o1, o2) -> mapper.apply(o1).compareTo(mapper.apply(o2)); } /** @@ -226,7 +285,13 @@ public static > Comparator comparing(Funct */ public static > Comparator thenComparing( Comparator comparator, Function mapper) { - throw new ExerciseNotCompletedException(); + return (o1, o2) -> { + var initialResult = comparator.compare(o1, o2); + if (initialResult != 0) { + return initialResult; + } + return mapper.apply(o1).compareTo(mapper.apply(o2)); + }; } /** @@ -235,7 +300,8 @@ public static > Comparator thenComparing( * @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!"; } } diff --git a/5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/CrazyStreams.java b/5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/CrazyStreams.java index 40ffc170f..4b60c0c13 100644 --- a/5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/CrazyStreams.java +++ b/5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/CrazyStreams.java @@ -1,12 +1,18 @@ package com.bobocode.fp; import com.bobocode.model.Account; -import com.bobocode.util.ExerciseNotCompletedException; +import com.bobocode.model.Sex; +import com.bobocode.fp.exception.EntityNotFoundException; import lombok.AllArgsConstructor; 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 @@ -30,7 +36,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)); } /** @@ -40,7 +47,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()); } /** @@ -50,7 +59,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))); } /** @@ -60,7 +70,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])); } /** @@ -69,7 +80,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(); } /** @@ -78,7 +91,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); } /** @@ -87,7 +102,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()); } /** @@ -97,7 +115,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)); } /** @@ -108,7 +128,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))); } /** @@ -117,7 +141,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())); } /** @@ -128,17 +153,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()))); } /** @@ -148,7 +176,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(", ")))); } /** @@ -158,7 +188,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)))); } /** @@ -168,7 +201,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())); } /** @@ -179,8 +216,13 @@ 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(int nameLengthBound) { - throw new ExerciseNotCompletedException(); + return accounts.stream() + .flatMap(a -> Stream.of(a.getFirstName(), a.getLastName())) + .filter(name -> name.length() >= nameLengthBound) + .map(String::toLowerCase) + .flatMapToInt(String::chars) + .mapToObj(c -> (char) c) + .collect(groupingBy(identity(), counting())); } - } diff --git a/5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/CrazyOptionals.java b/5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/CrazyOptionals.java index 1ab1faa67..227cf3177 100644 --- a/5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/CrazyOptionals.java +++ b/5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/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 @@ -29,7 +31,6 @@ * @author Taras Boychuk */ public class CrazyOptionals { - /** * Creates an instance of {@link Optional} using a text parameter * @@ -37,7 +38,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); } /** @@ -47,7 +50,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))); } /** @@ -57,7 +67,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); } /** @@ -69,7 +81,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); } /** @@ -80,7 +95,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); } /** @@ -91,7 +110,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); } /** @@ -101,7 +128,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); } /** @@ -112,7 +142,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!")); } /** @@ -122,7 +154,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); } @@ -134,7 +169,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")); } /** @@ -147,7 +187,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(); } /** @@ -158,7 +203,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 } /** @@ -168,7 +216,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 } /** @@ -178,7 +232,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 } /** @@ -188,7 +247,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#stream`. 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/5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/PrimeNumbers.java b/5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/PrimeNumbers.java index e4a99188d..63d514e1b 100644 --- a/5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/PrimeNumbers.java +++ b/5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/PrimeNumbers.java @@ -1,12 +1,13 @@ package com.bobocode.fp; -import com.bobocode.util.ExerciseNotCompletedException; - import java.util.List; import java.util.Map; import java.util.function.IntConsumer; import java.util.stream.IntStream; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + /** * {@link PrimeNumbers} provides an API to work with prime numbers. The implementation is based on the * {@link java.util.stream.IntStream} of prime numbers. That stream is used in all public methods on this class. @@ -30,7 +31,8 @@ private PrimeNumbers() { * @return an infinite int stream of prime numbers */ public static IntStream stream() { - throw new ExerciseNotCompletedException(); // todo: create an infinite stream of ints, then filter prime numbs + return IntStream.iterate(2, i -> i + 1) + .filter(PrimeNumbers::isPrime); } /** @@ -40,7 +42,8 @@ public static IntStream stream() { * @return an int stream of prime numbers with a specified size */ public static IntStream stream(int size) { - throw new ExerciseNotCompletedException(); // todo: use the prev to generate a stream method but limit its size + return stream() + .limit(size); } /** @@ -51,8 +54,8 @@ public static IntStream stream(int size) { * @return the sum of n prime numbers */ public static int sum(int n) { - throw new ExerciseNotCompletedException(); // todo: use prev method and calculate the sum - + return stream(n) + .sum(); } /** @@ -61,7 +64,9 @@ public static int sum(int n) { * @return a list of collected prime numbers */ public static List list(int n) { - throw new ExerciseNotCompletedException(); // todo: collect prime numbers into the list + return stream(n) + .boxed() + .collect(toList()); } /** @@ -71,7 +76,10 @@ public static List list(int n) { * @param consumer a logic that should be applied to the found prime number */ public static void processByIndex(int idx, IntConsumer consumer) { - throw new ExerciseNotCompletedException(); // todo: find an element in the stream by index and process it + stream(idx + 1) + .skip(idx) + .findAny() + .ifPresent(consumer); } /** @@ -85,6 +93,13 @@ public static void processByIndex(int idx, IntConsumer consumer) { * @return a map with prime number grouped by the amount of digits */ public static Map> groupByAmountOfDigits(int n) { - throw new ExerciseNotCompletedException(); // todo: group n prime numbers by the amount of digits + return stream(n) + .boxed() + .collect(groupingBy(x -> (int) (Math.log10(x) + 1))); + } + + private static boolean isPrime(int n) { + return (n != 1) && IntStream.range(2, n) + .noneMatch(i -> n % i == 0); } } diff --git a/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/LinkedStack.java b/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/LinkedStack.java index a7bc5fc23..c4948e9fc 100644 --- a/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/LinkedStack.java +++ b/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/LinkedStack.java @@ -1,25 +1,103 @@ package com.bobocode.tdd; -import com.bobocode.util.ExerciseNotCompletedException; +import com.bobocode.tdd.exception.EmptyStackException; +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) { - throw new ExerciseNotCompletedException(); // todo + 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() { - throw new ExerciseNotCompletedException(); // todo + 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() { - throw new ExerciseNotCompletedException(); // todo + return size; } + /** + * Checks if a stack is empty + * + * @return {@code true} if a stack is empty, {@code false} otherwise + */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo + return head == null; } } diff --git a/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/exception/EmptyStackException.java b/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/exception/EmptyStackException.java new file mode 100644 index 000000000..87894f179 --- /dev/null +++ b/6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/exception/EmptyStackException.java @@ -0,0 +1,5 @@ +package com.bobocode.tdd.exception; + +public class EmptyStackException extends RuntimeException{ + +} diff --git a/6-0-test-driven-development/6-1-1-stack/src/test/java/com/bobocode/tdd/StackTest.java b/6-0-test-driven-development/6-1-1-stack/src/test/java/com/bobocode/tdd/StackTest.java index 8d01a406f..4c8bee481 100644 --- a/6-0-test-driven-development/6-1-1-stack/src/test/java/com/bobocode/tdd/StackTest.java +++ b/6-0-test-driven-development/6-1-1-stack/src/test/java/com/bobocode/tdd/StackTest.java @@ -1,4 +1,76 @@ package com.bobocode.tdd; -public class StackTest { +import com.bobocode.tdd.exception.EmptyStackException; +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 pushElementOntoEmptyStack() { + 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); + } } diff --git a/6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/LinkedList.java b/6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/LinkedList.java index 275b70a4b..2b99c1ec1 100644 --- a/6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/LinkedList.java +++ b/6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/LinkedList.java @@ -1,7 +1,8 @@ package com.bobocode.tdd; - -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(); + } } /** @@ -99,9 +159,29 @@ public T getLast() { */ @Override public T remove(int index) { - throw new ExerciseNotCompletedException(); // todo: implement this method + T deletedElement = null; + if (index == 0) { + Objects.checkIndex(index, size); + deletedElement = head.value; + removeHead(); + } else { + Node previousNode = findNodeByIndex(index - 1); + deletedElement = previousNode.next.value; + previousNode.next = previousNode.next.next; + if (index == size - 1) { + tail = previousNode; + } + } + size--; + return deletedElement; } + private void removeHead() { + head = head.next; + if (head == null) { + tail = null; + } + } /** * Checks if a specific exists in he list @@ -110,7 +190,14 @@ public T 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; } /** @@ -120,7 +207,7 @@ public boolean contains(T element) { */ @Override public boolean isEmpty() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return head == null; } /** @@ -130,7 +217,7 @@ public boolean isEmpty() { */ @Override public int size() { - throw new ExerciseNotCompletedException(); // todo: implement this method + return size; } /** @@ -138,6 +225,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); + } } } diff --git a/6-0-test-driven-development/6-1-2-linked-list/src/test/java/com/bobocode/tdd/LinkedListTest.java b/6-0-test-driven-development/6-1-2-linked-list/src/test/java/com/bobocode/tdd/LinkedListTest.java index 4477db6b4..3950f3872 100644 --- a/6-0-test-driven-development/6-1-2-linked-list/src/test/java/com/bobocode/tdd/LinkedListTest.java +++ b/6-0-test-driven-development/6-1-2-linked-list/src/test/java/com/bobocode/tdd/LinkedListTest.java @@ -1,4 +1,324 @@ package com.bobocode.tdd; -public class LinkedListTest { + +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; + +class LinkedListTest { + + private List intList = new LinkedList<>(); + + @Test + void addIntoEmptyList() { + intList.add(41); + + int element = intList.get(0); + + assertThat(element).isEqualTo(41); + assertThat(intList.size()).isEqualTo(1); + } + + @Test + void getFirstElementFromSingleElementList() { + intList.add(25); + + int element = intList.get(0); + + assertThat(element).isEqualTo(25); + } + + @Test + void addElements() { + intList = LinkedList.of(43, 233, 54); + + assertThat(intList.size()).isEqualTo(3); + assertThat(intList.get(0)).isEqualTo(43); + assertThat(intList.get(1)).isEqualTo(233); + assertThat(intList.get(2)).isEqualTo(54); + } + + @Test + void size() { + intList = LinkedList.of(4, 7, 9, 0, 7); + + int size = intList.size(); + + assertThat(size).isEqualTo(5); + } + + @Test + void getFirstElement() { + intList = LinkedList.of(31, 32); + + int firstElement = intList.getFirst(); + assertThat(firstElement).isEqualTo(31); + } + + @Test + void getLastElement() { + intList = LinkedList.of(41, 42); + + int lastElement = intList.getLast(); + + assertThat(lastElement).isEqualTo(42); + } + + @Test + void getFirstOfEmptyList() { + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> intList.getFirst()); + } + + @Test + void getLastOfEmptyList() { + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> intList.getLast()); + } + + + @Test + void getElements() { + intList = LinkedList.of(25, 87, 45); + + int firstElement = intList.get(0); + int secondElement = intList.get(1); + int thirdElement = intList.get(2); + + assertThat(firstElement).isEqualTo(25); + assertThat(secondElement).isEqualTo(87); + assertThat(thirdElement).isEqualTo(45); + + } + + @Test + void addElementByZeroIndexIntoEmptyList() { + intList.add(0, 45); + + int element = intList.get(0); + + assertThat(element).isEqualTo(45); + assertThat(intList.size()).isEqualTo(1); + } + + @Test + void addElementByIndexToTheEndOfList() { + intList = LinkedList.of(98, 64, 23, 1, 3, 4); + + int newElementIndex = intList.size(); + intList.add(newElementIndex, 44); + + assertThat(intList.get(newElementIndex)).isEqualTo(44); + assertThat(intList.size()).isEqualTo(7); + } + + @Test + void addElementToTheHeadOfNonEmptyList() { + intList = LinkedList.of(4, 6, 8, 9, 0, 2); + + intList.add(0, 53); + + assertThat(intList.get(0)).isEqualTo(53); + assertThat(intList.get(1)).isEqualTo(4); + assertThat(intList.size()).isEqualTo(7); + } + + @Test + void addElementByIndex() { + intList = LinkedList.of(43, 5, 6, 8); + + int newElementIdx = 2; + intList.add(newElementIdx, 66); + + assertThat(intList.get(newElementIdx)).isEqualTo(66); + assertThat(intList.get(0)).isEqualTo(43); + assertThat(intList.get(1)).isEqualTo(5); + assertThat(intList.get(3)).isEqualTo(6); + assertThat(intList.get(4)).isEqualTo(8); + assertThat(intList.size()).isEqualTo(5); + } + + @Test + void addElementByNegativeIndex() { + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.add(-1, 66)); + } + + @Test + void addElementByIndexLargerThanListSize() { + intList = LinkedList.of(4, 6, 11, 9); + + int newElementIdx = 5; + + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.add(newElementIdx, 88)); + } + + @Test + void addElementByIndexEqualToSize() { + intList = LinkedList.of(1, 2, 3, 4, 5); // size = 5 + + intList.add(5, 111); + int element = intList.get(5); + + assertThat(element).isEqualTo(111); + assertThat(intList.size()).isEqualTo(6); + } + + @Test + void setFirstElementOnEmptyTree() { + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.set(0, 34)); + } + + @Test + void setElementByIndexEqualToSize() { + intList = LinkedList.of(2, 3, 4); // size = 3 + + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.set(3, 222)); + } + + @Test + void setElementByIndex() { + intList = LinkedList.of(34, 78, 9, 8); + + int index = 2; //element = 78 + intList.set(index, 99); + + assertThat(intList.get(index)).isEqualTo(99); + assertThat(intList.get(0)).isEqualTo(34); + assertThat(intList.get(1)).isEqualTo(78); + assertThat(intList.get(3)).isEqualTo(8); + assertThat(intList.size()).isEqualTo(4); + } + + @Test + void getFirstElementFromEmptyList() { + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.get(0)); + } + + @Test + void getElementByNegativeIndex() { + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.get(-1)); + } + + @Test + void getElementByIndexEqualsToListSize() { + intList = LinkedList.of(33, 46, 25, 87, 45); + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.get(5)); + } + + @Test + void removeElementFromEmptyList() { + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.remove(234)); + } + + @Test + void removeFirstElement() { + intList = LinkedList.of(4, 6, 8, 9); + + int deletedElement = intList.remove(0); + + assertThat(intList.get(0)).isEqualTo(6); + assertThat(intList.size()).isEqualTo(3); + assertThat(deletedElement).isEqualTo(4); + } + + @Test + void removeLastElement() { + intList = LinkedList.of(4, 6, 8, 9); + + int deletedElement = intList.remove(intList.size() - 1); + + assertThat(intList.get(intList.size() - 1)).isEqualTo(8); + assertThat(intList.size()).isEqualTo(3); + assertThat(deletedElement).isEqualTo(9); + } + + @Test + void removeElement() { + intList = LinkedList.of(1, 2, 3, 4, 5); + + int elementIndex = 2; + int deletedElement = intList.remove(elementIndex); // element = 3 + + assertThat(intList.get(elementIndex)).isEqualTo(4); + assertThat(intList.size()).isEqualTo(4); + assertThat(deletedElement).isEqualTo(3); + } + + @Test + void containsOnEmptyList() { + boolean contains = intList.contains(34); + + assertThat(contains).isFalse(); + } + + @Test + void contains() { + intList = LinkedList.of(45, 6, 3, 6); + + boolean containsExistingElement = intList.contains(3); + boolean containsNotExistingElement = intList.contains(54); + + assertThat(containsExistingElement).isTrue(); + assertThat(containsNotExistingElement).isFalse(); + } + + @Test + void isEmptyOnEmptyList() { + boolean empty = intList.isEmpty(); + + assertThat(empty).isTrue(); + } + + @Test + void isEmpty() { + intList = LinkedList.of(34, 5, 6); + + boolean empty = intList.isEmpty(); + + assertThat(empty).isFalse(); + } + + @Test + void sizeOnEmptyList() { + int size = intList.size(); + + assertThat(size).isEqualTo(0); + } + + @Test + void clearOnEmptyList() { + intList.clear(); + + assertThat(intList.size()).isEqualTo(0); + } + + @Test + void clearChangesTheSize() { + intList = LinkedList.of(4, 5, 6); + + intList.clear(); + + assertThat(intList.size()).isEqualTo(0); + } + + @Test + void clearRemovesElements() { + intList = LinkedList.of(4, 5, 6); + + intList.clear(); + + assertThatExceptionOfType(IndexOutOfBoundsException.class) + .isThrownBy(() -> intList.get(0)); + } } diff --git a/6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/RecursiveBinarySearchTree.java b/6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/RecursiveBinarySearchTree.java index a9e368a97..aa134a7d3 100644 --- a/6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/RecursiveBinarySearchTree.java +++ b/6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/RecursiveBinarySearchTree.java @@ -1,37 +1,127 @@ package com.bobocode.tdd; -import com.bobocode.util.ExerciseNotCompletedException; - +import java.util.Objects; import java.util.function.Consumer; +import java.util.stream.Stream; public class RecursiveBinarySearchTree implements BinarySearchTree { + private static class Node { + T element; + Node left; + Node right; + + private Node(T element) { + this.element = element; + } + + public static Node valueOf(T element) { + return new Node(element); + } + } + + private Node root; + private int size = 0; public static RecursiveBinarySearchTree of(T... elements) { - throw new ExerciseNotCompletedException(); + RecursiveBinarySearchTree bst = new RecursiveBinarySearchTree<>(); + Stream.of(elements).forEach(bst::insert); + return bst; } @Override public boolean insert(T element) { - throw new ExerciseNotCompletedException(); + Objects.requireNonNull(element); + boolean isInserted = insertElement(element); + if (isInserted) { + size++; + } + return isInserted; + } + + boolean insertElement(T element) { + if (root == null) { + root = Node.valueOf(element); + return true; + } else { + return insertIntoSubTree(root, element); + } + } + + private boolean insertIntoSubTree(Node subTreeRoot, T element) { + if (subTreeRoot.element.compareTo(element) > 0) { + return insertIntoLeftSubtree(subTreeRoot, element); + } else if (subTreeRoot.element.compareTo(element) < 0) { + return insertIntoRightSubtree(subTreeRoot, element); + } else { + return false; + } + } + + private boolean insertIntoLeftSubtree(Node node, T element) { + if (node.left != null) { + return insertIntoSubTree(node.left, element); + } else { + node.left = Node.valueOf(element); + return true; + } + } + + private boolean insertIntoRightSubtree(Node node, T element) { + if (node.right != null) { + return insertIntoSubTree(node.right, element); + } else { + node.right = Node.valueOf(element); + return true; + } } + @Override public boolean contains(T element) { - throw new ExerciseNotCompletedException(); + Objects.requireNonNull(element); + return findChildNodeByElement(root, element) != null; + } + + private Node findChildNodeByElement(Node node, T element) { + if (node == null) { + return null; + } else if (node.element.compareTo(element) > 0) { + return findChildNodeByElement(node.left, element); + } else if (node.element.compareTo(element) < 0) { + return findChildNodeByElement(node.right, element); + } else { + return node; + } } @Override public int size() { - throw new ExerciseNotCompletedException(); + return size; } @Override public int depth() { - throw new ExerciseNotCompletedException(); + return root != null ? depth(root) - 1 : 0; + } + + private int depth(Node node) { + if (node == null) { + return 0; + } else { + return 1 + Math.max(depth(node.left), depth(node.right)); + } } @Override public void inOrderTraversal(Consumer consumer) { - throw new ExerciseNotCompletedException(); + inOrderTraversal(root, consumer); + } + + private void inOrderTraversal(Node node, Consumer consumer) { + if (node != null) { + inOrderTraversal(node.left, consumer); + consumer.accept(node.element); + inOrderTraversal(node.right, consumer); + } } } diff --git a/6-0-test-driven-development/6-1-3-binary-search-tree/src/test/java/com/bobocode/tdd/BinarySearchTreeTest.java b/6-0-test-driven-development/6-1-3-binary-search-tree/src/test/java/com/bobocode/tdd/BinarySearchTreeTest.java index 6c6fb5478..7513618c6 100644 --- a/6-0-test-driven-development/6-1-3-binary-search-tree/src/test/java/com/bobocode/tdd/BinarySearchTreeTest.java +++ b/6-0-test-driven-development/6-1-3-binary-search-tree/src/test/java/com/bobocode/tdd/BinarySearchTreeTest.java @@ -1,5 +1,100 @@ package com.bobocode.tdd; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + class BinarySearchTreeTest { + private static final Integer[] someElements = {10, 9, 11, 8, 12, 7}; + + @Test + void createWithElements() { + BinarySearchTree bst = RecursiveBinarySearchTree.of(someElements); + for (var e : someElements) { + assertThat(bst.contains(e)).isTrue(); + } + assertThat(bst.size()).isEqualTo(someElements.length); + } + + @Test + void insertUniqueElements() { + BinarySearchTree bst = RecursiveBinarySearchTree.of(); + for (int i = 0; i < someElements.length; i++) { + var e = someElements[i]; + assertThat(bst.contains(e)).isFalse(); //does not contain + assertThat(bst.size()).isEqualTo(i); + + assertThat(bst.insert(e)).isTrue(); //do insert + + assertThat(bst.contains(e)).isTrue(); //and contains + assertThat(bst.size()).isEqualTo(i + 1); + } + } + + @Test + void insertNonUniqueElements() { + BinarySearchTree bst = RecursiveBinarySearchTree.of(someElements); + for (var e : someElements) { + assertThat(bst.insert(e)).isFalse(); //does not insert + assertThat(bst.contains(e)).isTrue(); //but contains + } + assertThat(bst.size()).isEqualTo(someElements.length); + } + + @ParameterizedTest + @MethodSource("depthArguments") + void depth(Integer[] elements, int depth) { + BinarySearchTree bst = RecursiveBinarySearchTree.of(elements); + assertThat(bst.depth()).isEqualTo(depth); + } + + @Test + void inorderTraversal() { + BinarySearchTree bst = RecursiveBinarySearchTree.of(someElements); + Integer[] sortedElements = Arrays.copyOf(someElements, someElements.length); + Arrays.sort(sortedElements); + + List traversedElements = new ArrayList<>(bst.size()); + bst.inOrderTraversal(traversedElements::add); + + assertThat(traversedElements).isEqualTo(List.of(sortedElements)); + } + + public static Stream depthArguments() { + return Stream.of( + //empty tree + arguments(new Integer[]{}, 0), + //tree with a single element + arguments(new Integer[]{24}, 0), + /* + * .......10 + * ....../ \ + * .....5 15 + * ..../ \ + * ...1 20 + */ + arguments(new Integer[]{10, 5, 15, 1, 20}, 2), + /* + * ..1 + * ...\ + * ....2 + * .....\ + * ..... 3 + * .......\ + * ........4 + * .........\ + * ..........5 + */ + arguments(new Integer[]{1, 2, 3, 4, 5}, 4)); + } }