/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.collections; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.Rand; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; /** * An array list of weighted items. * * <p>The weight of an item affects the results when an item is randomly chosen * from the list using the {@link #getRandom} method.</p> * * <p>Adding items using the {@link java.util.List} implementation gives items * the default weight.</p> * * @param <T> The item type. */ public class WeightedArrayList<T> implements List<T> { private int _sumOfWeight = 0; private List<Weighted<T>> _weightedItems; /** * Constructor. */ public WeightedArrayList() { _weightedItems = new ArrayList<>(20); } /** * Constructor. * * @param size The initial capacity. */ public WeightedArrayList(int size) { PreCon.positiveNumber(size); _weightedItems = new ArrayList<>(size); } /** * Get the sum of weight of items in the list. */ public int getSumOfWeight() { return _sumOfWeight; } /** * Get a random item from the list. */ public T getRandom() { int sumOfWeights = getSumOfWeight(); int randomInt = Rand.getInt(sumOfWeights) + 1; for (Weighted<T> weighted : _weightedItems) { randomInt -= weighted.getWeight(); if (randomInt <= 0) { return weighted.getItem(); } } Weighted<T> result = Rand.get(_weightedItems); return result.getItem(); } /** * Add an item to the list with the specified weight. * * @param item The item to add. * @param weight The item weight. */ public boolean add(T item, int weight) { PreCon.notNull(item); PreCon.greaterThanZero(weight); Weighted<T> weighted = new Weighted<T>(item, weight); _sumOfWeight += weight; return _weightedItems.add(weighted); } /** * Get an iterator that can return the current items * weighted value. */ public WeightedIterator<T> weightedIterator() { return new WeightedIterator<T>() { Iterator<Weighted<T>> iterator = _weightedItems.iterator(); Weighted<T> current; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { current = iterator.next(); return current.getItem(); } @Override public int weight() { return current.getWeight(); } }; } @Override public int indexOf(Object obj) { PreCon.notNull(obj); return _weightedItems.indexOf(obj); } @Override public int lastIndexOf(Object obj) { PreCon.notNull(obj); return _weightedItems.lastIndexOf(obj); } @Override public ListIterator<T> listIterator() { return new WeightedListIterator(0); } @Override public ListIterator<T> listIterator(int index) { PreCon.positiveNumber(index); PreCon.lessThanEqual(index, _weightedItems.size()); return new WeightedListIterator(index); } @Override public List<T> subList(int fromIndex, int toIndex) { PreCon.positiveNumber(fromIndex); PreCon.lessThanEqual(toIndex, _weightedItems.size()); List<Weighted<T>> list = _weightedItems.subList(fromIndex, toIndex); List<T> result = new ArrayList<>(list.size()); for (Weighted<T> weighted : list) { result.add(weighted.getItem()); } return result; } @Override public int size() { return _weightedItems.size(); } @Override public boolean isEmpty() { return _weightedItems.isEmpty(); } @Override public boolean contains(Object obj) { PreCon.notNull(obj); for (Weighted<T> weighted : _weightedItems) { if (weighted.getItem().equals(obj)) return true; } return false; } @Override public Iterator<T> iterator() { return new Iterator<T>() { Iterator<Weighted<T>> iterator = _weightedItems.iterator(); Weighted<T> current; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { current = iterator.next(); return current.getItem(); } @Override public void remove() { iterator.remove(); if (current != null) { _sumOfWeight -= current.getWeight(); } } }; } @Override public Object[] toArray() { Object[] array = new Object[_weightedItems.size()]; for (int i=0; i < array.length; i++) array[i] = _weightedItems.get(i).getItem(); return array; } @Override public <T1> T1[] toArray(T1[] array) { PreCon.notNull(array); for (int i=0; i < array.length; i++) { @SuppressWarnings("unchecked") T1 item = (T1) _weightedItems.get(i).getItem(); array[i] = item; } return array; } @Override public boolean add(T item) { PreCon.notNull(item); if (_weightedItems.add(new Weighted<T>(item, 1))) { _sumOfWeight += 1; return true; } return false; } @Override public boolean remove(Object obj) { PreCon.notNull(obj); ListIterator<Weighted<T>> iterator = _weightedItems.listIterator(); while (iterator.hasNext()) { Weighted<T> weighted = iterator.next(); if (weighted.getItem().equals(obj)) { _sumOfWeight -= weighted.getWeight(); iterator.remove(); return true; } } return false; } @Override public boolean containsAll(Collection<?> collection) { PreCon.notNull(collection); for (Object obj : collection) { ListIterator<Weighted<T>> iterator = _weightedItems.listIterator(); boolean contains = false; while (iterator.hasNext()) { Weighted<T> weighted = iterator.next(); if (weighted.getItem().equals(obj)) { contains = true; break; } } if (!contains) return false; } return true; } @Override public boolean addAll(Collection<? extends T> collection) { PreCon.notNull(collection); for (T item : collection) { add(item); } return true; } @Override public boolean addAll(int index, Collection<? extends T> collection) { PreCon.positiveNumber(index); PreCon.lessThanEqual(index, _weightedItems.size()); List<Weighted<T>> weightedList = new ArrayList<>(collection.size()); for (T item : collection) { weightedList.add(new Weighted<T>(item, 1)); _sumOfWeight += 1; } return _weightedItems.addAll(index, weightedList); } @Override public boolean removeAll(Collection<?> collection) { PreCon.notNull(collection); Iterator<Weighted<T>> iterator = _weightedItems.iterator(); int startSize = _weightedItems.size(); while(iterator.hasNext()) { Weighted<T> weighted = iterator.next(); for (Object obj : collection) { if (weighted.getItem().equals(obj)) { iterator.remove(); _sumOfWeight -= weighted.getWeight(); break; } } } return startSize != _weightedItems.size(); } @Override public boolean retainAll(Collection<?> collection) { PreCon.notNull(collection); Iterator<Weighted<T>> iterator = _weightedItems.iterator(); int startSize = _weightedItems.size(); while(iterator.hasNext()) { Weighted<T> weighted = iterator.next(); boolean contains = false; for (Object obj : collection) { if (weighted.getItem().equals(obj)) { contains = true; break; } } if (!contains) { iterator.remove(); _sumOfWeight -= weighted.getWeight(); } } return startSize != _weightedItems.size(); } @Override public void clear() { _sumOfWeight = 0; _weightedItems.clear(); } @Override public T get(int index) { PreCon.positiveNumber(index); PreCon.lessThan(index, _weightedItems.size()); Weighted<T> weighted = _weightedItems.get(index); return weighted.getItem(); } @Override public T set(int index, T element) { PreCon.positiveNumber(index); PreCon.lessThan(index, _weightedItems.size()); PreCon.notNull(element); Weighted<T> weighted = _weightedItems.get(index); _sumOfWeight -= weighted.getWeight(); weighted = new Weighted<>(element, 1); _sumOfWeight += 1; _weightedItems.set(index, weighted); return element; } @Override public void add(int index, T element) { PreCon.positiveNumber(index); PreCon.lessThan(index, _weightedItems.size()); PreCon.notNull(element); Weighted<T> weighted = new Weighted<>(element, 1); _weightedItems.add(index, weighted); _sumOfWeight += 1; } @Override public T remove(int index) { PreCon.positiveNumber(index); PreCon.lessThan(index, _weightedItems.size()); Weighted<T> weighted = _weightedItems.remove(index); _sumOfWeight -= weighted.getWeight(); return weighted.getItem(); } private static class Weighted<T> { private T _item; private int _weight; public Weighted(T item, int weight) { _item = item; _weight = weight; } public T getItem() { return _item; } public int getWeight() { return _weight; } } /** * WeightedList iterator. * * @param <T> Item type. */ public interface WeightedIterator<T> { /** * Determine if there is a next item. */ boolean hasNext(); /** * Get the next item. */ T next(); /** * Get the weight of the current item. */ int weight(); } private class WeightedListIterator implements ListIterator<T> { private ListIterator<Weighted<T>> iterator; private Weighted<T> current; public WeightedListIterator(int index) { iterator = _weightedItems.listIterator(index); } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { current = iterator.next(); return current.getItem(); } @Override public boolean hasPrevious() { return iterator.hasPrevious(); } @Override public T previous() { current = iterator.previous(); return current.getItem(); } @Override public int nextIndex() { return iterator.nextIndex(); } @Override public int previousIndex() { return iterator.previousIndex(); } @Override public void remove() { iterator.remove(); if (current != null) { _sumOfWeight -= current.getWeight(); } } @Override public void set(T t) { PreCon.notNull(t); if (current != null) { _sumOfWeight -= current.getWeight(); } Weighted<T> weighted = new Weighted<>(t, 1); iterator.set(weighted); _sumOfWeight += 1; } @Override public void add(T t) { Weighted<T> weighted = new Weighted<>(t, 1); iterator.set(weighted); _sumOfWeight += 1; } } }