/* * esodata - data structures and other things, of varying utility * Copyright 2022, Ben Culkin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package bjc.esodata; import java.util.*; // @FIXME Nov 15th, 2020 Ben Culkin :RecalcMinMax // Is there some sort of way to avoid having to recalculate these elements when // that element is removed? /** * A list that automatically tracks the minimum & maximum element of a list. * * @author Ben Culkin * * @param The type of element stored in the list. * */ public class MinMaxList extends AbstractList { private final List backing; private final Comparator picker; private ValueType minElement; private boolean recalcMin = false; private ValueType maxElement; private boolean recalcMax = false; // Create constructors /** * Create a new min/max list using the given comparator. * * @param picker The comparator to use to determine min/max elements. */ public MinMaxList(Comparator picker) { this(picker, new ArrayList<>()); } /** * Create a new min/max list using the given comparator. * * @param picker The comparator to use to determine min/max elements. * @param values The values to fill the list from. */ @SafeVarargs public MinMaxList(Comparator picker, ValueType... values) { this(picker); for (ValueType value : values) { add(value); } } /** * Create a new min/max list using the given comparator. * * @param picker The comparator to use to determine min/max elements. * @param backing The collection to use values from. */ public MinMaxList(Comparator picker, Collection backing) { this(picker, new ArrayList<>(backing)); } /** * Create a new min/max list using the given comparator. * * @param picker The comparator to use to determine min/max elements. * @param backing The list to use as a backing list. */ public MinMaxList(Comparator picker, List backing) { this.backing = backing; this.picker = picker; calculateBoth(); } @Override public ValueType get(int index) { return backing.get(index); } @Override public ValueType set(int index, ValueType element) { ValueType oldElement = backing.set(index, element); if (minElement == null) { minElement = element; } else if (picker.compare(element, minElement) < 0) { minElement = element; recalcMin = false; } else if (oldElement.equals(minElement)) { minElement = null; recalcMin = true; } if (maxElement == null) { maxElement = element; } else if (picker.compare(element, maxElement) > 0) { maxElement = element; recalcMax = false; } else if (oldElement.equals(maxElement)) { maxElement = null; recalcMax = true; } return oldElement; } @Override public void add(int index, ValueType element) { backing.add(index, element); if (minElement == null) { minElement = element; } else if (picker.compare(element, minElement) < 0) { minElement = element; recalcMin = false; } if (maxElement == null) { maxElement = element; } else if (picker.compare(element, maxElement) > 0) { maxElement = element; recalcMax = false; } } @Override public ValueType remove(int index) { ValueType oldElement = backing.remove(index); if (oldElement.equals(minElement)) { minElement = null; recalcMin = true; } if (oldElement.equals(maxElement)) { maxElement = null; recalcMax = true; } return oldElement; } @Override public int size() { return backing.size(); } /** * Get the minimum element currently stored in this list. * * @return The minimum element stored in the list. */ public ValueType minimum() { if (recalcMin) calculateMinimum(); return minElement; } /** * Get the maximum element currently stored in this list. * * @return The maximum element stored in the list. */ public ValueType maximum() { if (recalcMax) calculateMaximum(); return maxElement; } private void calculateMinimum() { for (ValueType element : backing) { if (minElement == null) { minElement = element; } else if (picker.compare(element, minElement) < 0) { minElement = element; } } recalcMin = false; } private void calculateMaximum() { for (ValueType element : backing) { if (maxElement == null) { maxElement = element; } else if (picker.compare(element, maxElement) > 0) { maxElement = element; } } recalcMax = false; } private void calculateBoth() { for (ValueType element : backing) { if (minElement == null) { minElement = element; } else if (picker.compare(element, minElement) < 0) { minElement = element; } if (maxElement == null) { maxElement = element; } else if (picker.compare(element, maxElement) > 0) { maxElement = element; } } recalcMin = false; recalcMax = false; } @Override public String toString() { return String.format("%s (min: %s) (max: %s)", backing, recalcMin ? "(unknown)" : minElement, recalcMax ? "(unknown)" : maxElement); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + Objects.hash(backing, picker); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; MinMaxList other = (MinMaxList) obj; return Objects.equals(backing, other.backing) && Objects.equals(picker, other.picker); } }