/*
* Bag.java
*
* Copyright (C) 2008 Pei Wang
*
* This file is part of Open-NARS.
*
* Open-NARS 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 2 of the License, or
* (at your option) any later version.
*
* Open-NARS 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 Open-NARS. If not, see <http://www.gnu.org/licenses/>.
*/
package nars.storage;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import nars.config.Parameters;
import nars.entity.Item;
/**
* Original Bag implementation which distributes items into
* discrete levels (queues) according to priority
*/
public class LevelBag<E extends Item<K>,K> extends Bag<E,K> {
/**
* priority levels
*/
public final int levels;
/**
* firing threshold
*/
public final int fireCompleteLevelThreshold;
/**
* shared DISTRIBUTOR that produce the probability distribution
*/
final short[] DISTRIBUTOR;
/**
* mapping from key to item
*/
//public final Set<E> nameTable;
public final Map<K, E> nameTable;
/**
* array of lists of items, for items on different level
*/
public final Level<E>[] level;
/**
* defined in different bags
*/
final int capacity;
/**
* current sum of occupied level
*/
private float mass;
/**
* index to get next level, kept in individual objects
*/
int levelIndex;
/**
* current take out level
*/
int currentLevel;
/**
* maximum number of items to be taken out at current level
*/
int currentCounter;
final boolean[] levelEmpty;
public LevelBag(int levels, int capacity) {
this(levels, capacity, (int) (Parameters.BAG_THRESHOLD * levels));
}
/** thresholdLevel = 0 disables "fire level completely" threshold effect */
public LevelBag(int levels, int capacity, int thresholdLevel) {
this.levels = levels;
this.fireCompleteLevelThreshold = thresholdLevel;
//THRESHOLD = levels + 1; //fair/flat takeOut policy
this.capacity = capacity;
nameTable = new HashMap<>(capacity);
level = new Level[this.levels];
levelEmpty = new boolean[this.levels];
Arrays.fill(levelEmpty, true);
DISTRIBUTOR = Distributor.get(this.levels).order;
distributorLength = DISTRIBUTOR.length;
clear();
}
public class Level<E> implements Iterable<E> {
private final int thisLevel;
//Deque<E> items;
LinkedHashSet<E> items;
public Level(int level, int numElements) {
super();
items = new LinkedHashSet(numElements);
this.thisLevel = level;
}
@Override
public Iterator<E> iterator() {
return items.iterator();
}
public int size() { return items.size(); }
void levelIsEmpty(final boolean e) {
levelEmpty[thisLevel] = e;
}
public void clear() {
items.clear();
levelIsEmpty(true);
}
public boolean add(final E e) {
if (e == null)
throw new RuntimeException("Bag requires non-null items");
if (items.add(e)) {
levelIsEmpty(false);
return true;
}
return false;
}
public boolean remove(E o) {
if (items.remove(o)) {
levelIsEmpty(items.isEmpty());
return true;
}
return false;
}
public E removeFirst() {
E e = items.iterator().next();
items.remove(e);
if (e!=null) {
levelIsEmpty(items.isEmpty());
}
return e;
}
public E peekFirst() {
return items.iterator().next();
}
public Iterator<E> descendingIterator() {
return items.iterator();
//return items.descendingIterator();
}
}
private Level<E> newLevel(int l) {
return new Level(l, 1 + capacity / levels);
}
@Override
public final void clear() {
for (int i = 0; i < levels; i++) {
if (level[i] != null) {
level[i].clear();
}
}
nameTable.clear();
currentLevel = levels - 1;
levelIndex = capacity % levels; // so that different bags start at different point
mass = 0;
currentCounter = 0;
}
/**
* The number of items in the bag
*
* @return The number of items
*/
@Override
public int size() {
int in = nameTable.size();
if (Parameters.DEBUG_BAG && (Parameters.DEBUG)) {
int is = sizeItems();
if (Math.abs(is-in) > 1 ) {
throw new RuntimeException(this.getClass() + " inconsistent index: items=" + is + " names=" + in + ", capacity=" + getCapacity());
}
}
return in;
}
/** this should always equal size(), but it's here for testing purposes */
protected int sizeItems() {
int t = 0;
for (Level<E> l : level) {
if (l!=null)
t += l.size();
}
return t;
}
@Override
public Set<K> keySet() {
return nameTable.keySet();
}
/**
* Get the average priority of Items
*
* @return The average priority of Items in the bag
*/
@Override
public float getAveragePriority() {
if (size() == 0) {
return 0.01f;
}
float f = (float) mass / (size());
if (f > 1) {
return 1.0f;
}
return f;
}
/**
* Get an Item by key
*
* @param key The key of the Item
* @return The Item with the given key
*/
@Override
public E get(final K key) {
return nameTable.get(key);
}
final int distributorLength;
/** look for a non-empty level */
protected void nextNonEmptyLevel() {
int cl = currentLevel;
do {
} while (levelEmpty[cl = DISTRIBUTOR[(levelIndex++) % distributorLength]]);
currentLevel = cl;
if (currentLevel < fireCompleteLevelThreshold) { // for dormant levels, take one item
currentCounter = 1;
} else { // for active levels, take all current items
currentCounter = getNonEmptyLevelSize(currentLevel);
}
}
@Override
public E peekNext() {
if (size() == 0)
return null; // empty bag
E e = takeNext();
putIn(e);
return e;
}
public E peekNextWithoutAffectingBagOrder() {
if (size() == 0) return null; // empty bag
if (levelEmpty[currentLevel] || (currentCounter == 0)) { // done with the current level
nextNonEmptyLevel();
}
return level[currentLevel].peekFirst();
}
@Override
public E takeNext() {
if (size() == 0) {
return null; // empty bag
}
if (levelEmpty[currentLevel] || (currentCounter == 0)) { // done with the current level
nextNonEmptyLevel();
}
if (levelEmpty[currentLevel]) {
throw new RuntimeException("Empty level selected for takeNext");
}
final E selected = takeOutFirst(currentLevel); // take out the first item in the level
currentCounter--;
return selected;
}
public int getNonEmptyLevelSize(final int level) {
return this.level[level].size();
}
public int getLevelSize(final int level) {
return (levelEmpty[level]) ? 0 : this.level[level].size();
}
@Override public E take(final K name) {
E oldItem = nameTable.remove(name);
if (oldItem == null) {
return null;
}
final int expectedLevel = getLevel(oldItem);
//TODO scan up/down iteratively, it is likely to be near where it was
if (!levelEmpty[expectedLevel]) {
if (level[expectedLevel].remove(oldItem)) {
removeMass(oldItem);
return oldItem;
}
}
for (int l = 0; l < levels; l++) {
if ((!levelEmpty[l]) && (l!=expectedLevel)) {
if (level[l].remove(oldItem)) {
removeMass(oldItem);
return oldItem;
}
}
}
//If it wasn't found, it probably was removed already. So this check is probably not necessary
//search other levels for this item because it's not where we thought it was according to getLevel()
if (Parameters.DEBUG) {
int ns = nameTable.size();
int is = sizeItems();
if (ns == is)
return null;
throw new RuntimeException("LevelBag inconsistency: " + nameTable.size() + "|" + sizeItems() + " Can not remove missing element: size inconsistency" + oldItem + " from " + this.getClass().getSimpleName());
}
return oldItem;
}
/**
* Decide the put-in level according to priority
*
* @param item The Item to put in
* @return The put-in level
*/
private int getLevel(final E item) {
final float fl = item.getPriority() * levels;
final int level = (int) Math.ceil(fl) - 1;
if (level < 0) return 0;
if (level >= levels) return levels-1;
return level;
}
/**
* Insert an item into the itemTable, and return the overflow
*
* @param newItem The Item to put in
* @return null if nothing overflowed, non-null if an overflow Item, which
* may be the attempted input item (in which case it was not inserted)
*/
@Override public E addItem(final E newItem) {
E oldItem = null;
int inLevel = getLevel(newItem);
if (size() >= capacity) { // the bag will be full after the next
int outLevel = 0;
while (levelEmpty[outLevel]) {
outLevel++;
}
if (outLevel > inLevel) { // ignore the item and exit
return newItem;
} else { // remove an old item in the lowest non-empty level
oldItem = takeOutFirst(outLevel);
}
}
ensureLevelExists(inLevel);
level[inLevel].add(newItem); // FIFO
nameTable.put(newItem.name(), newItem);
addMass(newItem);
return oldItem;
}
protected final void ensureLevelExists(final int level) {
if (this.level[level] == null) {
this.level[level] = newLevel(level);
}
}
/**
* Take out the first or last E in a level from the itemTable
*
* @param level The current level
* @return The first Item
*/
private E takeOutFirst(final int level) {
final E selected = this.level[level].removeFirst();
if (selected!=null) {
nameTable.remove(selected.name());
removeMass(selected);
}
else {
throw new RuntimeException("Attempt to remove item from empty level: " + level);
}
return selected;
}
protected void removeMass(E item) {
mass -= item.getPriority();
}
protected void addMass(E item) {
mass += item.getPriority();
}
/**
* TODO refactor : paste from preceding method
*/
public String toStringLong(int minLevel) {
StringBuilder buf = new StringBuilder(32)
.append(" BAG ").append(getClass().getSimpleName())
.append(" ").append(showSizes());
for (int i = levels; i >= minLevel; i--) {
if (!levelEmpty[i - 1]) {
buf = buf.append("\n --- LEVEL ").append(i).append(":\n ");
for (final E e : level[i - 1]) {
buf = buf.append(e.toStringLong()).append('\n');
}
}
}
buf.append(">>>> end of Bag").append(getClass().getSimpleName());
return buf.toString();
}
/**
* show item Table Sizes
*/
public String showSizes() {
StringBuilder buf = new StringBuilder(" ");
int l = 0;
for (Level<E> items : level) {
int s = items.size();
if ((items != null) && (s > 0)) {
l++;
buf.append(s).append(' ');
}
}
return "Levels: " + Integer.toString(l) + ", sizes: " + buf;
}
@Override
public float getMass() {
return mass;
}
public float getAverageItemsPerLevel() {
return capacity / levels;
}
public float getMaxItemsPerLevel() {
int max = getLevelSize(0);
for (int i = 1; i < levels; i++) {
int s = getLevelSize(i);
if (s > max) {
max = s;
}
}
return max;
}
public float getMinItemsPerLevel() {
int min = getLevelSize(0);
for (int i = 1; i < levels; i++) {
int s = getLevelSize(i);
if (s < min) {
min = s;
}
}
return min;
}
@Override
public int getCapacity() {
return capacity;
}
public Iterable<E> getLevel(final int i) {
if (level[i] == null) {
return Collections.EMPTY_LIST;
}
return level[i];
}
@Override
public Collection<E> values() {
return nameTable.values();
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int l = level.length - 1;
private Iterator<E> levelIterator;
private E next;
final int size = size();
int count = 0;
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
if (l >= 0 && levelIterator == null) {
while (levelEmpty[l]) {
if (--l == -1)
return false; //end of the levels
}
levelIterator = level[l].descendingIterator();
}
if (levelIterator == null) {
return false;
}
next = levelIterator.next();
count++;
if (levelIterator.hasNext()) {
return true;
} else {
levelIterator = null;
l--;
return count <= size;
}
}
@Override
public E next() {
E e = next;
next = null;
return e;
}
};
}
public int numEmptyLevels() {
int empty = 0;
for (int i = 0; i < level.length; i++) {
if (levelEmpty[i]) {
empty++;
}
}
return empty;
}
}