Fully implemented LeastPickedPicker
This commit is contained in:
parent
0bbc831513
commit
c22a8f82f0
@ -0,0 +1,64 @@
|
||||
package ru.dragonestia.picker.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||
import ru.dragonestia.picker.repository.NodeRepository;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Profile("test_pickers")
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class TestPickersConfig {
|
||||
|
||||
private final NodeRepository nodeRepository;
|
||||
private final RoomRepository roomRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Bean
|
||||
void createSequentialFillingNode() {
|
||||
var node = new Node("seq", PickingMode.SEQUENTIAL_FILLING);
|
||||
nodeRepository.create(node);
|
||||
|
||||
fillNode(node);
|
||||
}
|
||||
|
||||
@Bean
|
||||
void createRoundRobinNode() {
|
||||
var node = new Node("round", PickingMode.ROUND_ROBIN);
|
||||
nodeRepository.create(node);
|
||||
|
||||
fillNode(node);
|
||||
}
|
||||
|
||||
@Bean
|
||||
void createLeastPickerNode() {
|
||||
var node = new Node("least", PickingMode.LEAST_PICKED);
|
||||
nodeRepository.create(node);
|
||||
|
||||
fillNode(node);
|
||||
}
|
||||
|
||||
private void fillNode(Node node) {
|
||||
for (int i = 0, n = 5; i < n; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
var room = Room.create("room-" + i + "-" + j, node, SlotLimit.of(n), "");
|
||||
roomRepository.create(room);
|
||||
|
||||
for (int k = n - i - 1; k >= 0; k--) {
|
||||
var user = new User("user-" + k);
|
||||
userRepository.linkWithRoom(room, List.of(user), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Node;
|
||||
import ru.dragonestia.picker.repository.RoomRepository;
|
||||
import ru.dragonestia.picker.repository.NodeRepository;
|
||||
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -17,6 +18,7 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
|
||||
private final RoomRepository roomRepository;
|
||||
private final PickerRepository pickerRepository;
|
||||
private final NodeId2PickerModeCache nodeId2PickerModeCache;
|
||||
private final Map<String, Node> nodeMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
@ -27,7 +29,8 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
}
|
||||
|
||||
nodeMap.put(node.id(), node);
|
||||
pickerRepository.create(node.id(), node.mode());
|
||||
var picker = pickerRepository.create(node.id(), node.mode());
|
||||
nodeId2PickerModeCache.put(node.id(), picker);
|
||||
}
|
||||
|
||||
roomRepository.onCreateNode(node);
|
||||
@ -38,6 +41,7 @@ public class NodeRepositoryImpl implements NodeRepository {
|
||||
synchronized (nodeMap) {
|
||||
nodeMap.remove(node.id());
|
||||
pickerRepository.remove(node.id());
|
||||
nodeId2PickerModeCache.remove(node.id());
|
||||
}
|
||||
|
||||
roomRepository.onRemoveNode(node);
|
||||
|
||||
@ -6,10 +6,7 @@ import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
|
||||
import ru.dragonestia.picker.repository.impl.picker.Picker;
|
||||
import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker;
|
||||
import ru.dragonestia.picker.repository.impl.picker.SequentialFillingPicker;
|
||||
import ru.dragonestia.picker.repository.impl.picker.*;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Collection;
|
||||
@ -21,17 +18,19 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
public class PickerRepository {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final Map<String, Picker<Room, User>> pickers = new ConcurrentHashMap<>();
|
||||
private final Map<String, RoomPicker> pickers = new ConcurrentHashMap<>();
|
||||
|
||||
public void create(String nodeId, PickingMode mode) {
|
||||
pickers.put(nodeId, of(mode));
|
||||
public RoomPicker create(String nodeId, PickingMode mode) {
|
||||
var picker = of(mode);
|
||||
pickers.put(nodeId, picker);
|
||||
return picker;
|
||||
}
|
||||
|
||||
public void remove(String nodeId) {
|
||||
pickers.remove(nodeId);
|
||||
}
|
||||
|
||||
public Picker<Room, User> find(String nodeId) {
|
||||
public RoomPicker find(String nodeId) {
|
||||
return pickers.get(nodeId);
|
||||
}
|
||||
|
||||
@ -39,7 +38,7 @@ public class PickerRepository {
|
||||
return pickers.get(nodeId).pick(users);
|
||||
}
|
||||
|
||||
private Picker<Room, User> of(PickingMode mode) {
|
||||
private RoomPicker of(PickingMode mode) {
|
||||
switch (mode) {
|
||||
case SEQUENTIAL_FILLING -> {
|
||||
return new SequentialFillingPicker(userRepository);
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
package ru.dragonestia.picker.repository.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.repository.NodeRepository;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
|
||||
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class UserRepositoryImpl implements UserRepository {
|
||||
|
||||
private final NodeId2PickerModeCache nodeId2PickerModeCache;
|
||||
private final Map<User, Set<Room>> usersMap = new ConcurrentHashMap<>();
|
||||
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
|
||||
|
||||
@ -44,6 +51,11 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
|
||||
usersSet.addAll(users);
|
||||
roomUsers.put(path, usersSet);
|
||||
|
||||
var picker = nodeId2PickerModeCache.get(room.getNodeId());
|
||||
if (picker instanceof LeastPickedPicker leastPickedPicker) {
|
||||
leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -72,6 +84,11 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
} else {
|
||||
roomUsers.put(path, set);
|
||||
}
|
||||
|
||||
var picker = nodeId2PickerModeCache.get(room.getNodeId());
|
||||
if (picker instanceof LeastPickedPicker leastPickedPicker) {
|
||||
leastPickedPicker.updateUsersAmount(room, roomUsers.get(path).size());
|
||||
}
|
||||
}
|
||||
return counter.get();
|
||||
}
|
||||
|
||||
25
app/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java
vendored
Normal file
25
app/src/main/java/ru/dragonestia/picker/repository/impl/cache/NodeId2PickerModeCache.java
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package ru.dragonestia.picker.repository.impl.cache;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.dragonestia.picker.repository.impl.picker.RoomPicker;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
public class NodeId2PickerModeCache {
|
||||
|
||||
private final Map<String, RoomPicker> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public void put(String nodeId, RoomPicker picker) {
|
||||
cache.put(nodeId, picker);
|
||||
}
|
||||
|
||||
public void remove(String nodeId) {
|
||||
cache.remove(nodeId);
|
||||
}
|
||||
|
||||
public RoomPicker get(String nodeId) {
|
||||
return cache.get(nodeId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
package ru.dragonestia.picker.repository.impl.collection;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DynamicSortedMap<ITEM extends DynamicSortedMap.Item> {
|
||||
|
||||
private final TreeMap<Integer, LinkedList<Node<ITEM>>> tree = new TreeMap<>();
|
||||
private final HashMap<String, Node<ITEM>> indexes = new HashMap<>();
|
||||
|
||||
public void put(ITEM item) {
|
||||
var node = new Node<>(item, room -> {
|
||||
// TODO: update map state
|
||||
});
|
||||
|
||||
indexes.put(node.object.getId(), node);
|
||||
var list = tree.getOrDefault(node.cachedScore, new LinkedList<>());
|
||||
if (list.isEmpty()) tree.put(node.cachedScore, list);
|
||||
list.add(node);
|
||||
}
|
||||
|
||||
public void removeById(String id) {
|
||||
if (!indexes.containsKey(id)) return;
|
||||
|
||||
remove(indexes.get(id).object);
|
||||
}
|
||||
|
||||
public void remove(ITEM item) {
|
||||
if (!indexes.containsKey(item.getId())) return;
|
||||
|
||||
var node = indexes.get(item.getId());
|
||||
node.removed = true;
|
||||
indexes.remove(item.getId());
|
||||
|
||||
var list = tree.get(node.cachedScore);
|
||||
list.remove(node);
|
||||
if (list.isEmpty()) tree.remove(node.cachedScore);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return indexes.size();
|
||||
}
|
||||
|
||||
public ITEM getMinimum() {
|
||||
return getMinimum(0);
|
||||
}
|
||||
|
||||
public ITEM getMinimum(int needAppendScore) {
|
||||
if (size() == 0) {
|
||||
throw new RuntimeException("Map is empty");
|
||||
}
|
||||
|
||||
ITEM result = null;
|
||||
rootLoop:
|
||||
for (var index: tree.navigableKeySet()) {
|
||||
var list = tree.get(index);
|
||||
for (var node: list) {
|
||||
if (!node.object.canBeUsed(needAppendScore)) continue;
|
||||
|
||||
result = node.object;
|
||||
break rootLoop;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("Cant get available item");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ITEM getMaximum() {
|
||||
if (size() == 0) {
|
||||
throw new RuntimeException("Map is empty");
|
||||
}
|
||||
|
||||
ITEM result = null;
|
||||
rootLoop:
|
||||
for (var index: tree.descendingKeySet()) {
|
||||
var list = tree.get(index);
|
||||
for (var node: list) {
|
||||
if (!node.object.canBeUsed(0)) continue;
|
||||
|
||||
result = node.object;
|
||||
break rootLoop;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("Cant get available item");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Set<Node<ITEM>> getItems() {
|
||||
var set = new LinkedHashSet<Node<ITEM>>();
|
||||
for (var index: tree.navigableKeySet()) {
|
||||
set.addAll(tree.get(index));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public Node<ITEM> getNode(String id) {
|
||||
return indexes.get(id);
|
||||
}
|
||||
|
||||
public void updateItem(String id, Function<Integer, Integer> setter) {
|
||||
var node = indexes.get(id);
|
||||
|
||||
var prevScore = node.cachedScore;
|
||||
node.object.updateScore(setter.apply(node.cachedScore));
|
||||
var newScore = node.cachedScore;
|
||||
|
||||
var prevList = tree.get(prevScore);
|
||||
prevList.remove(node);
|
||||
if (prevList.isEmpty()) {
|
||||
tree.remove(prevScore);
|
||||
}
|
||||
|
||||
var newList = tree.getOrDefault(newScore, new LinkedList<>());
|
||||
if (newList.isEmpty()) tree.put(newScore, newList);
|
||||
newList.add(node);
|
||||
}
|
||||
|
||||
public static class Node<ITEM extends Item> {
|
||||
|
||||
private final ITEM object;
|
||||
private int cachedScore;
|
||||
private boolean removed = false;
|
||||
|
||||
private Node(ITEM object, Consumer<ITEM> onUpdate) {
|
||||
this.object = object;
|
||||
cachedScore = object.getScore();
|
||||
|
||||
object.setOnUpdateScore(newScore -> {
|
||||
cachedScore = newScore;
|
||||
onUpdate.accept(object);
|
||||
});
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return cachedScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{Node id=%s, score=%s, removed=%s }".formatted(
|
||||
object.getId(),
|
||||
getScore(),
|
||||
removed
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj == this) return true;
|
||||
if (obj instanceof DynamicSortedMap.Node<?> node) {
|
||||
return object.getId().equals(node.object.getId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Item {
|
||||
|
||||
String getId();
|
||||
|
||||
int getScore();
|
||||
|
||||
void updateScore(int value);
|
||||
|
||||
void setOnUpdateScore(Consumer<Integer> setter);
|
||||
|
||||
default boolean canBeUsed(int units) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,13 +2,16 @@ package ru.dragonestia.picker.repository.impl.picker;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class LeastPickedPicker implements Picker<Room, User> {
|
||||
public class LeastPickedPicker implements RoomPicker {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final DynamicSortedMap<RoomWrapper> map = new DynamicSortedMap<>();
|
||||
|
||||
public LeastPickedPicker(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
@ -16,16 +19,43 @@ public class LeastPickedPicker implements Picker<Room, User> {
|
||||
|
||||
@Override
|
||||
public void add(Room room) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
synchronized (map) {
|
||||
map.put(new RoomWrapper(room, () -> userRepository.usersOf(room).size()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Room room) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
synchronized (map) {
|
||||
map.removeById(room.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Room pick(Collection<User> users) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
RoomWrapper wrapper;
|
||||
|
||||
synchronized (map) {
|
||||
try {
|
||||
wrapper = map.getMinimum();
|
||||
|
||||
if (wrapper.isFull()) throw new RuntimeException();
|
||||
} catch (RuntimeException ex) {
|
||||
throw new RuntimeException("There are no rooms available");
|
||||
}
|
||||
}
|
||||
|
||||
return wrapper.getItem();
|
||||
}
|
||||
|
||||
public void updateUsersAmount(Room room, int users) {
|
||||
synchronized (map) {
|
||||
map.updateItem(room.getId(), prevValue -> users);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickingMode getPickingMode() {
|
||||
return PickingMode.LEAST_PICKED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package ru.dragonestia.picker.repository.impl.picker;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
|
||||
public interface RoomPicker extends Picker<Room, User> {
|
||||
|
||||
PickingMode getPickingMode();
|
||||
}
|
||||
@ -2,13 +2,16 @@ package ru.dragonestia.picker.repository.impl.picker;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
|
||||
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item {
|
||||
public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item, DynamicSortedMap.Item {
|
||||
|
||||
private final Room room;
|
||||
private final Supplier<Integer> userCountSupplier;
|
||||
private Consumer<Integer> setter;
|
||||
|
||||
public RoomWrapper(Room room, Supplier<Integer> userCountSupplier) {
|
||||
this.room = room;
|
||||
@ -39,4 +42,26 @@ public class RoomWrapper implements ItemWrapper<Room>, QueuedLinkedList.Item {
|
||||
public boolean canAddUnits(int amount) {
|
||||
return ItemWrapper.super.canAddUnits(amount) && !room.isLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore() {
|
||||
return countUnits();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScore(int value) {
|
||||
if (setter == null) return;
|
||||
|
||||
setter.accept(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnUpdateScore(Consumer<Integer> setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeUsed(int units) {
|
||||
return canAddUnits(units);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,12 +2,13 @@ package ru.dragonestia.picker.repository.impl.picker;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class RoundRobinPicker implements Picker<Room, User> {
|
||||
public class RoundRobinPicker implements RoomPicker {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final QueuedLinkedList<RoomWrapper> list = new QueuedLinkedList<>();
|
||||
@ -45,4 +46,9 @@ public class RoundRobinPicker implements Picker<Room, User> {
|
||||
|
||||
return wrapper.getItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickingMode getPickingMode() {
|
||||
return PickingMode.ROUND_ROBIN;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,14 @@ package ru.dragonestia.picker.repository.impl.picker;
|
||||
|
||||
import ru.dragonestia.picker.model.Room;
|
||||
import ru.dragonestia.picker.model.User;
|
||||
import ru.dragonestia.picker.model.type.PickingMode;
|
||||
import ru.dragonestia.picker.repository.UserRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SequentialFillingPicker implements Picker<Room, User> {
|
||||
public class SequentialFillingPicker implements RoomPicker {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final Map<String, RoomWrapper> wrappers = new LinkedHashMap<>();
|
||||
@ -45,4 +46,9 @@ public class SequentialFillingPicker implements Picker<Room, User> {
|
||||
|
||||
throw new RuntimeException("There are no rooms available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PickingMode getPickingMode() {
|
||||
return PickingMode.SEQUENTIAL_FILLING;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
package ru.dragonestia.picker.collection;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class DynamicSortedMapTests {
|
||||
|
||||
@Test
|
||||
void testMap() {
|
||||
var map = new DynamicSortedMap<Item>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int value = i;
|
||||
if ((i & 1) == 1) value = 10 - value;
|
||||
|
||||
var item = new Item(Integer.toString(i), value);
|
||||
map.put(item);
|
||||
}
|
||||
|
||||
printMap(map);
|
||||
|
||||
Assertions.assertEquals(new Item("0", 0), map.getMinimum());
|
||||
Assertions.assertEquals(new Item("1", 9), map.getMaximum());
|
||||
|
||||
var minId = map.getMinimum().id;
|
||||
var maxId = map.getMaximum().id;
|
||||
setFor(map, minId, 100);
|
||||
setFor(map, maxId, -100);
|
||||
|
||||
printMap(map);
|
||||
|
||||
Assertions.assertEquals(new Item("1", -100), map.getMinimum());
|
||||
Assertions.assertEquals(new Item("0", 100), map.getMaximum());
|
||||
}
|
||||
|
||||
private void setFor(DynamicSortedMap<Item> map, String id, int score) {
|
||||
var before = map.getNode(id).toString();
|
||||
map.updateItem(id, (current) -> score);
|
||||
var after = map.getNode(id).toString();
|
||||
|
||||
System.out.printf("Updated '%s' from %s to %s\n", id, before, after);
|
||||
}
|
||||
|
||||
private void printMap(DynamicSortedMap<Item> map) {
|
||||
var list = map.getItems().stream().toList();
|
||||
var sb = new StringBuilder("Map(" + map.size() + "): ");
|
||||
for (int i = 0, n = map.size(); i < n; i++) {
|
||||
sb.append(list.get(i));
|
||||
if (i + 1 == n) sb.append(", ");
|
||||
}
|
||||
System.out.println(sb);
|
||||
}
|
||||
|
||||
public static class Item implements DynamicSortedMap.Item {
|
||||
|
||||
private final String id;
|
||||
private Consumer<Integer> setter = val -> {};
|
||||
private int score;
|
||||
|
||||
public Item(String id, int score) {
|
||||
this.id = id;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScore(int value) {
|
||||
setter.accept(value);
|
||||
score = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnUpdateScore(Consumer<Integer> setter) {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj == this) return true;
|
||||
if (obj instanceof Item other) {
|
||||
return id.equals(other.id) && score == other.score;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{Item id='%s' score=%s}".formatted(id, score);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -80,5 +80,20 @@ public class QueuedLinkedListTests {
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj == this) return true;
|
||||
if (obj instanceof Item other) {
|
||||
return id.equals(other.id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{Item id='%s' }".formatted(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user