diff --git a/README.md b/README.md
new file mode 100644
index 00000000..c5ba8967
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+# Java Course HSE AMI '28
diff --git a/commander/dependency-reduced-pom.xml b/commander/dependency-reduced-pom.xml
new file mode 100644
index 00000000..5405c3c1
--- /dev/null
+++ b/commander/dependency-reduced-pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+ hse.java
+ commander
+ commander
+ 1.0-SNAPSHOT
+
+
+
+ maven-compiler-plugin
+ 3.13.0
+
+ 25
+ 25
+
+
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.8
+
+
+ default-cli
+
+ hse.java.commander/hse.java.commander.CommanderApplication
+ app
+ app
+ app
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+ org.openjfx
+ javafx
+ 21.0.6
+ pom
+ compile
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.12.1
+ test
+
+
+ opentest4j
+ org.opentest4j
+
+
+ junit-platform-commons
+ org.junit.platform
+
+
+ apiguardian-api
+ org.apiguardian
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.12.1
+ test
+
+
+ junit-platform-engine
+ org.junit.platform
+
+
+ apiguardian-api
+ org.apiguardian
+
+
+
+
+
+ UTF-8
+ 5.12.1
+
+
diff --git a/commander/src/main/java/hse/java/commander/MainController.java b/commander/src/main/java/hse/java/commander/MainController.java
index 5de3b66d..ab4d7070 100644
--- a/commander/src/main/java/hse/java/commander/MainController.java
+++ b/commander/src/main/java/hse/java/commander/MainController.java
@@ -2,18 +2,125 @@
import javafx.fxml.FXML;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
import javafx.scene.control.ListView;
+<<<<<<< HEAD
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+=======
import java.nio.file.Path;
public class MainController {
+>>>>>>> 1c195628027c24a0d2a677be0bd75b5381ae77e5
+public class MainController {
@FXML
+<<<<<<< HEAD
+ public Button move;
+ @FXML
+=======
+>>>>>>> 1c195628027c24a0d2a677be0bd75b5381ae77e5
public ListView left;
-
@FXML
public ListView right;
+ @FXML
+ public Label leftPathLabel;
+ @FXML
+ public Label rightPathLabel;
+
+<<<<<<< HEAD
+ private File leftDirectory = new File(System.getProperty("user.home"));
+ private File rightDirectory = new File(System.getProperty("user.home"));
+
+ public void initialize() {
+ refresh(left, leftDirectory);
+ refresh(right, rightDirectory);
+
+ left.setOnMouseClicked(event -> {
+ if (event.getClickCount() == 2) {
+ handleNavigation(left, true);
+ }
+ });
+
+ right.setOnMouseClicked(event -> {
+ if (event.getClickCount() == 2) {
+ handleNavigation(right, false);
+ }
+ });
+
+ move.setOnAction(event -> moveFile());
+ }
+
+ private void refresh(ListView list, File directory) {
+ list.getItems().clear();
+ list.getItems().add("..");
+ File[] files = directory.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ list.getItems().add(file.isDirectory() ? "[" + file.getName() + "]" : file.getName());
+ }
+ }
+ if (list == left) {
+ leftPathLabel.setText(directory.getAbsolutePath());
+ } else {
+ rightPathLabel.setText(directory.getAbsolutePath());
+ }
+ }
+
+ private void handleNavigation(ListView list, boolean isLeft) {
+ String selected = list.getSelectionModel().getSelectedItem();
+ if (selected == null) return;
+
+ File currentDir = isLeft ? leftDirectory : rightDirectory;
+ File nextDirectory;
+
+ if (selected.equals("..")) {
+ nextDirectory = currentDir.getParentFile();
+ } else {
+ String name = selected.replace("[", "").replace("]", "");
+ nextDirectory = new File(currentDir, name);
+ }
+ if (nextDirectory != null && nextDirectory.isDirectory()) {
+ if (isLeft) {
+ leftDirectory = nextDirectory;
+ } else {
+ rightDirectory = nextDirectory;
+ }
+ refresh(list, nextDirectory);
+ }
+ }
+
+ private void moveFile() {
+ boolean moveFromLeft = left.getSelectionModel().getSelectedItem() != null;
+ ListView fromList = moveFromLeft ? left : right;
+ File fromDirectory = moveFromLeft ? leftDirectory : rightDirectory;
+ File toDirectory = moveFromLeft ? rightDirectory : leftDirectory;
+
+ String selected = fromList.getSelectionModel().getSelectedItem();
+
+ if (selected == null || selected.equals("..")) {
+ return;
+ }
+
+ String name = selected.replace("[", "").replace("]", "");
+ Path source = new File(fromDirectory, name).toPath();
+ Path destination = new File(toDirectory, name).toPath();
+
+ try {
+ Files.move(source, destination, StandardCopyOption.REPLACE_EXISTING);
+ refresh(left, leftDirectory);
+ refresh(right, rightDirectory);
+ } catch (IOException e) {
+ System.err.println("Анлак, не удалось перенести: " + e.getMessage());
+ }
+ }
+}
+=======
@FXML
public Button move;
@@ -43,3 +150,4 @@ public void initialize() {
});
}
}
+>>>>>>> 1c195628027c24a0d2a677be0bd75b5381ae77e5
diff --git a/commander/src/main/resources/hse/java/commander/commander-ui.fxml b/commander/src/main/resources/hse/java/commander/commander-ui.fxml
index 724bbc8a..3eaf10f9 100644
--- a/commander/src/main/resources/hse/java/commander/commander-ui.fxml
+++ b/commander/src/main/resources/hse/java/commander/commander-ui.fxml
@@ -2,7 +2,31 @@
+
+<<<<<<< HEAD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+=======
@@ -14,3 +38,4 @@
+>>>>>>> 1c195628027c24a0d2a677be0bd75b5381ae77e5
diff --git a/pom.xml b/pom.xml
index bb6d9b8a..54ba403b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,8 @@
UTF-8
5.10.0
3.1.2
+
+ 1.18.38
@@ -61,6 +63,11 @@
${lombok.version}
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+ -parameters
+
diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Node.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Node.java
new file mode 100644
index 00000000..d613e287
--- /dev/null
+++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Node.java
@@ -0,0 +1,32 @@
+package hse.java.lectures.lecture3.practice.randomSet;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+public class Node {
+ @Setter
+ private T value;
+ @Setter
+ private Node left;
+ @Setter
+ private Node right;
+ private int size;
+
+ public Node(T value) {
+ size = 1;
+ this.value = value;
+ this.left = null;
+ this.right = null;
+ }
+
+ public void recalculateSize() {
+ size = 1;
+ if (left != null) {
+ size += left.getSize();
+ }
+ if (right != null) {
+ size += right.getSize();
+ }
+ }
+}
diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Pair.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Pair.java
new file mode 100644
index 00000000..5e8b9193
--- /dev/null
+++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Pair.java
@@ -0,0 +1,4 @@
+package hse.java.lectures.lecture3.practice.randomSet;
+
+public record Pair(L left, R right) {
+}
diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java
index 8af477b5..51e61ac1 100644
--- a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java
+++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java
@@ -1,21 +1,32 @@
package hse.java.lectures.lecture3.practice.randomSet;
-public class RandomSet {
+import java.util.Random;
+
+public class RandomSet> {
+ private final Treap treap;
+ private final Random random = new Random();
+
+ public RandomSet() {
+ treap = new Treap<>();
+ }
public boolean insert(T value) {
- throw new UnsupportedOperationException("Not implemented");
+ return treap.tryInsert(value);
}
public boolean remove(T value) {
- throw new UnsupportedOperationException("Not implemented");
+ return treap.tryRemove(value);
}
public boolean contains(T value) {
- throw new UnsupportedOperationException("Not implemented");
+ return treap.contains(value);
}
public T getRandom() {
- throw new UnsupportedOperationException("Not implemented");
+ if (treap.getSize() == 0) {
+ throw new EmptySetException("Set is empty");
+ }
+ int randomIndex = random.nextInt(treap.getSize());
+ return treap.get(randomIndex);
}
-
}
diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Treap.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Treap.java
new file mode 100644
index 00000000..3e133b13
--- /dev/null
+++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/Treap.java
@@ -0,0 +1,110 @@
+package hse.java.lectures.lecture3.practice.randomSet;
+
+import java.util.Optional;
+import java.util.Random;
+
+public class Treap> {
+ private Node root;
+ private final Random random;
+
+ public Treap() {
+ root = null;
+ random = new Random();
+ }
+
+ private boolean findByValue(Node node, T value) {
+ if (node == null) {
+ return false;
+ }
+ int compareResult = node.getValue().compareTo(value);
+ if (compareResult == 0) {
+ return true;
+ }
+ if (compareResult > 0) {
+ return findByValue(node.getLeft(), value);
+ }
+ return findByValue(node.getRight(), value);
+ }
+
+ private Optional findByIndex(Node currentNode, int index) {
+ if (currentNode == null) {
+ return Optional.empty();
+ }
+ int leftSize = currentNode.getLeft() != null ? currentNode.getLeft().getSize() : 0;
+ if (index < leftSize) {
+ return findByIndex(currentNode.getLeft(), index);
+ } else if (index == leftSize) {
+ return Optional.of(currentNode.getValue());
+ } else {
+ return findByIndex(currentNode.getRight(), index - leftSize - 1);
+ }
+ }
+
+ private Pair, Node> split(Node currentNode, T value) {
+ if (currentNode == null) {
+ return new Pair<>(null, null);
+ }
+ int compareResult = currentNode.getValue().compareTo(value);
+ if (compareResult == 0) {
+ return new Pair<>(currentNode.getLeft(), currentNode.getRight());
+ } else if (compareResult < 0) {
+ var splitResult = split(currentNode.getRight(), value);
+ currentNode.setRight(splitResult.left());
+ currentNode.recalculateSize();
+ return new Pair<>(currentNode, splitResult.right());
+ } else {
+ var splitResult = split(currentNode.getLeft(), value);
+ currentNode.setLeft(splitResult.right());
+ currentNode.recalculateSize();
+ return new Pair<>(splitResult.left(), currentNode);
+ }
+ }
+
+ private Node merge(Node leftNode, Node rightNode) {
+ if (leftNode == null) {
+ return rightNode;
+ }
+ if (rightNode == null) {
+ return leftNode;
+ }
+ if (random.nextInt(leftNode.getSize() + rightNode.getSize()) < leftNode.getSize()) {
+ leftNode.setRight(merge(leftNode.getRight(), rightNode));
+ leftNode.recalculateSize();
+ return leftNode;
+ } else {
+ rightNode.setLeft(merge(leftNode, rightNode.getLeft()));
+ rightNode.recalculateSize();
+ return rightNode;
+ }
+ }
+
+ public int getSize() {
+ return root != null ? root.getSize() : 0;
+ }
+
+ public boolean contains(T value) {
+ return findByValue(root, value);
+ }
+
+ public boolean tryInsert(T value) {
+ if (contains(value)) {
+ return false;
+ }
+ var splitResult = split(root, value);
+ root = merge(merge(splitResult.left(), new Node<>(value)), splitResult.right());
+ return true;
+ }
+
+ public boolean tryRemove(T value) {
+ if (!contains(value)) {
+ return false;
+ }
+ var splitResult = split(root, value);
+ root = merge(splitResult.left(), splitResult.right());
+ return true;
+ }
+
+ public T get(int index) {
+ return findByIndex(root, index).orElseThrow(() -> new IllegalArgumentException("Index out of bounds"));
+ }
+}
diff --git a/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java b/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java
index 08f551e4..af541449 100644
--- a/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java
+++ b/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java
@@ -1,6 +1,11 @@
package hse.java.lectures.lecture3.tasks.atm;
-import java.util.*;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
public class Atm {
public enum Denomination {
@@ -19,27 +24,87 @@ public enum Denomination {
int value() {
return value;
}
+ }
+
+ private Map banknotes = new EnumMap<>(Denomination.class);
- public static Denomination fromInt(int value) {
- return Arrays.stream(values()).filter(v -> v.value == value)
- .findFirst()
- .orElse(null);
+ private Denomination getDenominationByValue(int value) {
+ for (Denomination denomination : Denomination.values()) {
+ if (denomination.value() == value) {
+ return denomination;
+ }
}
+ throw new IllegalArgumentException("Invalid denomination value: " + value);
}
- private final Map banknotes = new EnumMap<>(Denomination.class);
-
public Atm() {
}
- public void deposit(Map banknotes){}
+ public void deposit(Map bills) {
+ if (bills == null) {
+ throw new InvalidDepositException("Bills map cannot be null");
+ }
+
+ for (Map.Entry entry : bills.entrySet()) {
+ int denominationValue = entry.getKey();
+ int count = entry.getValue();
+
+ if (count <= 0) {
+ throw new InvalidDepositException("Count must be positive for denomination: " + denominationValue);
+ }
- public Map withdraw(int amount) {
- return Map.of();
+ Denomination denomination;
+ try {
+ denomination = getDenominationByValue(denominationValue);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidDepositException("Invalid denomination: " + denominationValue);
+ }
+
+ banknotes.put(denomination, banknotes.getOrDefault(denomination, 0) + count);
+ }
}
- public int getBalance() {
- return 0;
+ public Map withdraw(int amount) {
+ if (amount <= 0) {
+ throw new InvalidAmountException("Amount must be positive");
+ }
+
+ if (amount > getBalance()) {
+ throw new InsufficientFundsException("Not enough funds in the ATM");
+ }
+
+ Map result = new HashMap<>();
+ var banknoteCopy = new EnumMap<>(banknotes);
+
+
+ for (int i = Denomination.values().length - 1; i >= 0; i--) {
+ Denomination denomination = Denomination.values()[i];
+ int availableCount = banknotes.getOrDefault(denomination, 0);
+ int neededCount = amount / denomination.value();
+ int countToDispense = Math.min(availableCount, neededCount);
+
+ if (countToDispense > 0) {
+ result.put(denomination.value(), countToDispense);
+ amount -= countToDispense * denomination.value();
+ banknotes.put(denomination, availableCount - countToDispense);
+ }
+ }
+
+ if (amount != 0) {
+ banknotes = banknoteCopy;
+ throw new CannotDispenseException("Not enough funds in the ATM");
+ }
+
+ return result;
}
-}
+ public int getBalance() {
+ int result = 0;
+ for (Map.Entry entry : banknotes.entrySet()) {
+ Denomination denomination = entry.getKey();
+ int count = entry.getValue();
+ result += count * denomination.value();
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/hse/java/lectures/lecture3/tasks/html/HtmlDocument.java b/src/main/java/hse/java/lectures/lecture3/tasks/html/HtmlDocument.java
index 1ddf27bf..b82e54fc 100644
--- a/src/main/java/hse/java/lectures/lecture3/tasks/html/HtmlDocument.java
+++ b/src/main/java/hse/java/lectures/lecture3/tasks/html/HtmlDocument.java
@@ -4,12 +4,18 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.Set;
+import java.util.*;
public class HtmlDocument {
+ private static final Map allowedTags = Map.of(
+ "", "",
+ "", "",
+ "", "",
+ "", "
",
+ "", "
"
+ );
+
public HtmlDocument(String filePath) {
this(Path.of(filePath));
}
@@ -27,6 +33,135 @@ private String readFile(Path filePath) {
}
}
- private void validate(String content){}
+ private String ignoreAttributes(String tag) {
+ String lowerTag = tag.toLowerCase();
+ for (String allowed : allowedTags.keySet()) {
+ var expected = allowed.substring(1, allowed.length() - 1);
+ if (lowerTag.startsWith("<" + expected)) {
+ return "<" + expected + ">";
+ }
+ if (lowerTag.startsWith("" + expected)) {
+ return "" + expected + ">";
+ }
+ }
+ return null;
+ }
+
+ private List extractRootTags(String content) {
+ List rootTags = new ArrayList<>();
+ int pos = 0;
+ int depth = 0;
+
+ while (pos < content.length()) {
+ int open = content.indexOf('<', pos);
+ if (open == -1) break;
+
+ int close = content.indexOf('>', open);
+ if (close == -1) break;
+
+ String tag = content.substring(open, close + 1).toLowerCase();
+ pos = close + 1;
+
+ if (tag.startsWith("")) {
+ depth--;
+ } else if (tag.startsWith("<") && !tag.startsWith("")) {
+ String normalized = ignoreAttributes(tag);
+ if (normalized == null || !allowedTags.containsKey(normalized)) {
+ depth++;
+ continue;
+ }
+
+ if (depth == 0) {
+ rootTags.add(normalized);
+ }
+ depth++;
+ }
+ }
+
+ return rootTags;
+ }
+
+ private void validateStructure(String content) {
+ int htmlOpenPos = content.indexOf("");
+ int htmlClosePos = content.indexOf("");
+
+ if (htmlOpenPos == -1 || htmlClosePos == -1 || htmlOpenPos >= htmlClosePos) {
+ throw new InvalidStructureException("Html tag is needed and must be properly opened before closed.");
+ }
+
+ for (String openTag : allowedTags.keySet()) {
+ checkTagPosition(content, openTag, htmlOpenPos, htmlClosePos);
+ }
+ for (String closeTag : allowedTags.values()) {
+ checkTagPosition(content, closeTag, htmlOpenPos, htmlClosePos);
+ }
+
+ String innerHtml = content.substring(htmlOpenPos + "".length(), htmlClosePos);
+ List rootTags = extractRootTags(innerHtml);
+
+ if (rootTags.size() != 2) {
+ throw new InvalidStructureException("Inside there must be exactly followed by .");
+ }
+
+ if (!"".equals(rootTags.get(0))) {
+ throw new InvalidStructureException(" must be the first child of .");
+ }
+
+ if (!"".equals(rootTags.get(1))) {
+ throw new InvalidStructureException(" must follow as second child of .");
+ }
+ }
+
+ private void checkTagPosition(String content, String tag, int htmlOpenPos, int htmlClosePos) {
+ int index = -1;
+ while ((index = content.indexOf(tag, index + 1)) != -1) {
+ if (index < htmlOpenPos || index > htmlClosePos) {
+ throw new InvalidStructureException(
+ "Tag '" + tag + "' must be inside ..., but found at position " + index);
+ }
+ }
+ }
+
+ private void validate(String content) {
+ Stack stack = new Stack<>();
+ int pos = 0;
+
+ while (pos < content.length()) {
+ int tagStart = content.indexOf('<', pos);
+ int tagEnd = content.indexOf('>', tagStart);
+ if (tagStart == -1 || tagEnd == -1) break;
+
+ String tag = content.substring(tagStart, tagEnd + 1);
+
+ var normalizedTag = ignoreAttributes(tag);
+
+ if (normalizedTag == null) {
+ throw new UnsupportedTagException(tag);
+ }
+
+ if (allowedTags.containsKey(normalizedTag)) {
+ stack.push(normalizedTag.toLowerCase());
+ } else if (normalizedTag.startsWith("")) {
+ if (stack.isEmpty()) {
+ throw new UnexpectedClosingTagException(tag);
+ }
+ String expectedTag = allowedTags.get(stack.peek());
+ if (!normalizedTag.equalsIgnoreCase(expectedTag)) {
+ throw new MismatchedClosingTagException(stack.peek());
+ }
+ stack.pop();
+ } else {
+ throw new UnsupportedTagException(normalizedTag);
+ }
+
+ pos = tagEnd + 1;
+ }
+
+ if (!stack.isEmpty()) {
+ throw new UnclosedTagException("Unclosed tag: " + stack.peek());
+ }
+
+ validateStructure(content.toLowerCase());
+ }
}
diff --git a/src/main/java/hse/java/lectures/lecture6/tasks/queue/BoundedBlockingQueue.java b/src/main/java/hse/java/lectures/lecture6/tasks/queue/BoundedBlockingQueue.java
index 816f3ee6..518bc0e0 100644
--- a/src/main/java/hse/java/lectures/lecture6/tasks/queue/BoundedBlockingQueue.java
+++ b/src/main/java/hse/java/lectures/lecture6/tasks/queue/BoundedBlockingQueue.java
@@ -1,25 +1,52 @@
package hse.java.lectures.lecture6.tasks.queue;
-public class BoundedBlockingQueue {
+import java.util.ArrayDeque;
+import java.util.Deque;
+public class BoundedBlockingQueue {
+ private final Deque queue;
+ private final int capacity;
public BoundedBlockingQueue(int capacity) {
-
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Capacity must be > 0");
+ }
+ this.queue = new ArrayDeque<>();
+ this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
-
+ if (item == null) {
+ throw new IllegalArgumentException("Item must be non null");
+ }
+
+ synchronized (queue) {
+ while (queue.size() == capacity) {
+ queue.wait();
+ }
+ queue.addLast(item);
+ queue.notifyAll();
+ }
}
public T take() throws InterruptedException {
- return null;
+ synchronized (queue) {
+ while (queue.isEmpty()) {
+ queue.wait();
+ }
+ T item = queue.removeFirst();
+ queue.notifyAll();
+ return item;
+ }
}
public int size() {
- return 0;
+ synchronized (queue) {
+ return queue.size();
+ }
}
public int capacity() {
- return 0;
+ return this.capacity;
}
}
diff --git a/src/main/java/hse/java/practice/task1/EdgePosition.java b/src/main/java/hse/java/practice/task1/EdgePosition.java
index f96cdf3c..f7ff700d 100644
--- a/src/main/java/hse/java/practice/task1/EdgePosition.java
+++ b/src/main/java/hse/java/practice/task1/EdgePosition.java
@@ -14,5 +14,5 @@ public enum EdgePosition {
LEFT,
RIGHT,
FRONT,
- BACK
+ BACK;
}
diff --git a/src/main/java/hse/java/practice/task1/EdgeRotation.java b/src/main/java/hse/java/practice/task1/EdgeRotation.java
new file mode 100644
index 00000000..f98209c5
--- /dev/null
+++ b/src/main/java/hse/java/practice/task1/EdgeRotation.java
@@ -0,0 +1,117 @@
+package hse.java.practice.task1;
+
+/**
+ * Рассматриваем кубик Рубика как подгруппу симметрической группы S48,
+ * которая действует на 48 стикерах (по 8 стикеров на каждой из 6 граней, центры неподвижны).
+ * Поворот грани = это перестановка, которая циклически меняет 4 стикера на грани и 12 стикеров на соседних гранях.
+ * НА ВИКИ ПОДРОБНЕЕ.
+ */
+public class EdgeRotation {
+ private static final int STICKERS_ON_EDGE = 8;
+
+ public static final int[][] LEFT_CLOCKWISE = {
+ {16, 18, 23, 21},
+ {17, 20, 22, 19},
+ {0, 32, 8, 47},
+ {3, 35, 11, 44},
+ {5, 37, 13, 42}
+ };
+ public static final int[][] LEFT_COUNTERCLOCKWISE = reverseDirection(LEFT_CLOCKWISE);
+
+ public static final int[][] FRONT_CLOCKWISE = {
+ {32, 34, 39, 37},
+ {33, 36, 38, 35},
+ {5, 24, 10, 23},
+ {6, 27, 9, 20},
+ {7, 29, 8, 18}
+ };
+ public static final int[][] FRONT_COUNTERCLOCKWISE = reverseDirection(FRONT_CLOCKWISE);
+
+ public static final int[][] RIGHT_CLOCKWISE = {
+ {24, 26, 31, 29},
+ {25, 28, 30, 27},
+ {2, 45, 10, 34},
+ {4, 43, 12, 36},
+ {7, 40, 15, 39}
+ };
+ public static final int[][] RIGHT_COUNTERCLOCKWISE = reverseDirection(RIGHT_CLOCKWISE);
+
+ public static final int[][] BACK_CLOCKWISE = {
+ {40, 42, 47, 45},
+ {41, 44, 46, 43},
+ {2, 16, 15, 31},
+ {1, 19, 14, 28},
+ {0, 21, 13, 26}
+ };
+ public static final int[][] BACK_COUNTERCLOCKWISE = reverseDirection(BACK_CLOCKWISE);
+
+ public static final int[][] UP_CLOCKWISE = {
+ {0, 2, 7, 5},
+ {1, 4, 6, 3},
+ {40, 24, 32, 16},
+ {41, 25, 33, 17},
+ {42, 26, 34, 18}
+ };
+ public static final int[][] UP_COUNTERCLOCKWISE = reverseDirection(UP_CLOCKWISE);
+
+ public static final int[][] DOWN_CLOCKWISE = {
+ {8, 10, 15, 13},
+ {9, 12, 14, 11},
+ {37, 29, 45, 21},
+ {38, 30, 46, 22},
+ {39, 31, 47, 23}
+ };
+ public static final int[][] DOWN_COUNTERCLOCKWISE = reverseDirection(DOWN_CLOCKWISE);
+
+ private EdgeRotation() {
+ }
+
+ private static int[][] reverseDirection(int[][] rotation) {
+ var result = new int[rotation.length][];
+
+ for (int i = 0; i < rotation.length; i++) {
+ var row = rotation[i];
+ var reversedRow = new int[row.length];
+ reversedRow[0] = row[0];
+ for (int j = 1; j < row.length; j++) {
+ reversedRow[j] = row[row.length - j];
+ }
+ result[i] = reversedRow;
+ }
+
+ return result;
+ }
+
+ /**
+ * По индексу стикера возвращает индекс грани, на которой он находится.
+ *
+ * @param stickerIndex индекс стикера от 0 до 47 включительно
+ * @return индекс грани от 0 до 5 включительно
+ */
+ public static int getEdgeIndexByStickerIndex(int stickerIndex) {
+ return stickerIndex / STICKERS_ON_EDGE;
+ }
+
+ /**
+ * По индексу стикера возвращает индекс части грани, на которой он находится.
+ *
+ * @param stickerIndex индекс стикера от 0 до 47 включительно
+ * @return пара индексов, где первый индекс - это строка, а второй - это столбец
+ */
+ public static PartsIndex stickerIndexToPartsIndex(int stickerIndex) {
+ int stickerIndexOnEdge = stickerIndex % STICKERS_ON_EDGE;
+ int row;
+ int column;
+ if (stickerIndexOnEdge < 3) {
+ row = 0;
+ column = stickerIndexOnEdge;
+ } else if (stickerIndexOnEdge < 5) {
+ row = 1;
+ column = (stickerIndexOnEdge == 3) ? 0 : 2;
+ } else {
+ row = 2;
+ column = stickerIndexOnEdge - 5;
+ }
+ return new PartsIndex(row, column);
+ }
+}
diff --git a/src/main/java/hse/java/practice/task1/PartsIndex.java b/src/main/java/hse/java/practice/task1/PartsIndex.java
new file mode 100644
index 00000000..a7457a52
--- /dev/null
+++ b/src/main/java/hse/java/practice/task1/PartsIndex.java
@@ -0,0 +1,7 @@
+package hse.java.practice.task1;
+
+/**
+ * Позиция внутри грани куба.
+ */
+public record PartsIndex(int row, int column) {
+}
diff --git a/src/main/java/hse/java/practice/task1/RubiksCube.java b/src/main/java/hse/java/practice/task1/RubiksCube.java
index d986f9f0..6f4927f5 100644
--- a/src/main/java/hse/java/practice/task1/RubiksCube.java
+++ b/src/main/java/hse/java/practice/task1/RubiksCube.java
@@ -2,6 +2,8 @@
import java.util.Arrays;
+import static hse.java.practice.task1.EdgeRotation.*;
+
/**
* Необходимо реализовать интерфейс Cube
* При повороте передней грани, меняются верх низ право и лево
@@ -26,34 +28,59 @@ public RubiksCube() {
}
}
+ private void applyCycle(int[] cycle) {
+ CubeColor temp = getSticker(cycle[cycle.length - 1]);
+ for (int i = cycle.length - 1; i > 0; i--) {
+ setSticker(cycle[i], getSticker(cycle[i - 1]));
+ }
+ setSticker(cycle[0], temp);
+ }
+
+ private CubeColor getSticker(int stickerIndex) {
+ var index = stickerIndexToPartsIndex(stickerIndex);
+ return edges[getEdgeIndexByStickerIndex(stickerIndex)].getParts()[index.row()][index.column()];
+ }
+
+ private void setSticker(int stickerIndex, CubeColor color) {
+ var index = stickerIndexToPartsIndex(stickerIndex);
+ edges[getEdgeIndexByStickerIndex(stickerIndex)].getParts()[index.row()][index.column()] = color;
+ }
+
+ private void rotate(RotateDirection direction, int[][] clockwiseRotation, int[][] counterClockwiseRotation) {
+ int[][] rotation = (direction == RotateDirection.CLOCKWISE) ? clockwiseRotation : counterClockwiseRotation;
+ for (int[] cycle : rotation) {
+ applyCycle(cycle);
+ }
+ }
+
@Override
public void up(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.UP_CLOCKWISE, EdgeRotation.UP_COUNTERCLOCKWISE);
}
@Override
public void down(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.DOWN_CLOCKWISE, EdgeRotation.DOWN_COUNTERCLOCKWISE);
}
@Override
public void left(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.LEFT_CLOCKWISE, EdgeRotation.LEFT_COUNTERCLOCKWISE);
}
@Override
public void right(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.RIGHT_CLOCKWISE, EdgeRotation.RIGHT_COUNTERCLOCKWISE);
}
@Override
public void front(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.FRONT_CLOCKWISE, EdgeRotation.FRONT_COUNTERCLOCKWISE);
}
@Override
public void back(RotateDirection direction) {
-
+ rotate(direction, EdgeRotation.BACK_CLOCKWISE, EdgeRotation.BACK_COUNTERCLOCKWISE);
}
public Edge[] getEdges() {
diff --git a/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java b/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java
index 5e83fcae..c74a91c0 100644
--- a/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java
+++ b/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java
@@ -31,7 +31,7 @@ void initialBalanceIsZero() {
@Test
void depositIncreasesBalance() {
Atm atm = new Atm();
- atm.deposit(Map.of(D100, 10, D500, 5));
+ atm.deposit(Map.of(100, 10, 500, 5));
assertEquals(3500, atm.getBalance());
}
@@ -45,8 +45,8 @@ void depositRejectsNullMap() {
@Test
void depositRejectsNonPositiveCountAndKeepsState() {
Atm atm = new Atm();
- atm.deposit(Map.of(D100, 1));
- assertThrows(InvalidDepositException.class, () -> atm.deposit(Map.of(D100, 0)));
+ atm.deposit(Map.of(100, 1));
+ assertThrows(InvalidDepositException.class, () -> atm.deposit(Map.of(100, 0)));
assertEquals(100, atm.getBalance());
}
@@ -54,11 +54,11 @@ void depositRejectsNonPositiveCountAndKeepsState() {
void withdrawGreedyAndUpdatesBalance() {
Atm atm = new Atm();
// 2000
- atm.deposit(Map.of(D1000, 1, D500, 1, D100, 5));
+ atm.deposit(Map.of(1000, 1, 500, 1, 100, 5));
- Map result = atm.withdraw(1700);
+ Map result = atm.withdraw(1700);
- assertEquals(Map.of(D1000, 1, D500, 1, D100, 2), result);
+ assertEquals(Map.of(1000, 1, 500, 1, 100, 2), result);
assertEquals(300, atm.getBalance());
}
@@ -72,14 +72,14 @@ void withdrawRejectsInvalidAmount() {
@Test
void withdrawRejectsInsufficientFunds() {
Atm atm = new Atm();
- atm.deposit(Map.of(D100, 2));
+ atm.deposit(Map.of(100, 2));
assertThrows(InsufficientFundsException.class, () -> atm.withdraw(300));
}
@Test
void withdrawRejectsUnmakeableAmountAndKeepsState() {
Atm atm = new Atm();
- atm.deposit(Map.of(D500, 1, D100, 1));
+ atm.deposit(Map.of(500, 1, 100, 1));
assertThrows(CannotDispenseException.class, () -> atm.withdraw(150));
assertEquals(600, atm.getBalance());
}
@@ -99,7 +99,7 @@ void additionalTests(AtmCase atmCase) {
"Case: " + atmCase.name);
assertEquals(atmCase.expect.balance, atm.getBalance(), "Case: " + atmCase.name);
} else {
- Map result = atm.withdraw(atmCase.withdraw);
+ var result = atm.withdraw(atmCase.withdraw);
assertEquals(toMap(atmCase.expect.dispense), result, "Case: " + atmCase.name);
assertEquals(atmCase.expect.balance, atm.getBalance(), "Case: " + atmCase.name);
}
@@ -120,10 +120,10 @@ private static List loadCases() throws IOException {
}
}
- private Map toMap(Map source) {
- Map result = new HashMap<>();
+ private Map toMap(Map source) {
+ Map result = new HashMap<>();
for (Map.Entry entry : source.entrySet()) {
- result.put(Atm.Denomination.fromInt(Integer.parseInt(entry.getKey())), entry.getValue());
+ result.put(Integer.parseInt(entry.getKey()), entry.getValue());
}
return result;
}