diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/collection/QueuedLinkedList.java b/app/src/main/java/ru/dragonestia/picker/service/impl/collection/QueuedLinkedList.java new file mode 100644 index 0000000..eac544e --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/collection/QueuedLinkedList.java @@ -0,0 +1,109 @@ +package ru.dragonestia.picker.service.impl.collection; + +import java.util.HashMap; +import java.util.Map; + +public class QueuedLinkedList { + + private Node first; + private Node last; + private Node cursor; + private final Map> itemMap = new HashMap<>(); + + public void add(ITEM item) { + if (itemMap.containsKey(item.getId())) return; + + Node node = new Node<>(item); + if (first == null) { + first = node; + last = first; + } else { + node.prev = last; + last.next = node; + last = node; + } + + itemMap.put(item.getId(), node); + } + + public void remove(ITEM item) { + if (!itemMap.containsKey(item.getId())) return; + + var node = itemMap.get(item.getId()); + itemMap.remove(item.getId()); + node.removed = true; + + if (node.prev == null) { // first element + first = node.next; + if (first != null) { + first.prev = null; + } + } + + if (node.next == null) { // last element + last = node.prev; + if (last != null) { + last.next = null; + } + } + + if (node.next != null && node.prev != null) { // middle element + var prev = node.prev; + var next = node.next; + + prev.next = next; + next.prev = prev; + } + } + + public int size() { + return itemMap.size(); + } + + public void resetCursor() { + cursor = null; + } + + public ITEM pick() { + if (first == null) { + throw new RuntimeException("List is empty"); + } + + if (cursor == null) cursor = first; + + Node item = cursor; + while (item != null && item.removed) { + item = item.next; + } + if (item == null) item = first; + + cursor = item.next; + return item.object; + } + + private static class Node { + + private final ITEM object; + private Node prev, next; + private boolean removed = false; + + public Node(ITEM object) { + this.object = object; + } + + @Override + public String toString() { + return "{Node id=%s, prev=%s, next=%s, removed=%s }".formatted( + object.getId(), + prev == null? null : prev.object.getId(), + next == null? null : next.object.getId(), + removed + ); + } + } + + public interface Item { + + String getId(); + } +} diff --git a/app/src/test/java/ru/dragonestia/picker/collection/QueuedLinkedListTests.java b/app/src/test/java/ru/dragonestia/picker/collection/QueuedLinkedListTests.java new file mode 100644 index 0000000..dc322eb --- /dev/null +++ b/app/src/test/java/ru/dragonestia/picker/collection/QueuedLinkedListTests.java @@ -0,0 +1,63 @@ +package ru.dragonestia.picker.collection; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import ru.dragonestia.picker.service.impl.collection.QueuedLinkedList; + +import java.util.HashSet; + +public class QueuedLinkedListTests { + + @Test + void testQueuedLinkedList() { + var list = new QueuedLinkedList(); + for (int i = 0; i < 10; i++) { + list.add(new Item(Integer.toString(i))); + } + + printList(list); + + var removed = new HashSet(); + for (int i = 5; i < 8; i++) { + var id = Integer.toString(i); + + list.remove(new Item(id)); + System.out.println("Removed: " + id); + removed.add(id); + } + + printList(list); + + for (int i = 0; i < 20; i++) { + var item = list.pick(); + + System.out.println("Picked: " + item.getId()); + Assertions.assertFalse(removed.contains(item.getId())); + } + } + + private void printList(QueuedLinkedList list) { + list.resetCursor(); + + var sb = new StringBuilder("List(" + list.size() + "): "); + for (int i = 0, n = list.size(); i < n; i++) { + sb.append(i); + if (i + 1 != n) sb.append(", "); + } + System.out.println(sb); + } + + public static class Item implements QueuedLinkedList.Item { + + private final String id; + + public Item(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + } +}