/*******************************************************************************
* Copyright 2014 Analog Devices, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
package com.analog.lyric.collect.tests;
import static com.analog.lyric.util.test.ExceptionTester.*;
import static java.util.Objects.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import org.junit.Test;
import com.analog.lyric.collect.AbstractHeap;
import com.analog.lyric.collect.BinaryHeap;
import com.analog.lyric.collect.IHeap;
import com.analog.lyric.collect.IHeap.IEntry;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
public class TestHeap
{
private static class Element
{
private double _expectedPriority;
Element(double expectedPriority)
{
_expectedPriority = expectedPriority;
}
}
@Test
public void test()
{
testHeap(new BinaryHeap<Element>());
IHeap<Element> crappyHeap = new CrappyHeap<Element>();
assertFalse(crappyHeap.deferOrderingForBulkAdd(42));
assertFalse(crappyHeap.deferOrderingForBulkChange(42));
testHeap(crappyHeap);
}
private void testHeap(IHeap<Element> heap)
{
Random rand = new Random(42);
assertTrue(heap.isEmpty());
assertInvariants(heap);
assertNull(heap.pollEntry());
assertNull(heap.poll());
final int nElements = 32;
final Element[] elements = new Element[nElements];
for (int i = 0; i < nElements; ++i)
{
elements[i] = new Element(rand.nextDouble());
}
expectThrow(IllegalArgumentException.class, heap, "offer", elements[0], Double.NaN);
//
// Trivial test with one element
//
heap.offer(elements[0], elements[0]._expectedPriority);
assertEquals(1, heap.size());
assertInvariants(heap);
IEntry<Element> entry = heap.peekEntry();
assertNotNull(entry);
assertSame(elements[0], entry.getElement());
assertSame(elements[0], heap.peek());
assertTrue(heap.isOrdered());
IEntry<Element> bogusEntry = new IEntry<Element>() {
@Override
public @NonNull IEntry<Element> clone()
{
return this;
}
@Override
public @NonNull Element getElement()
{
return elements[0];
}
@Override
public double getPriority()
{
return elements[0]._expectedPriority;
}
@Override
public boolean isOwned()
{
return true;
}
};
assertFalse(heap.containsEntry(bogusEntry));
assertFalse(heap.removeEntry(bogusEntry));
assertSame(entry, heap.pollEntry());
assertTrue(heap.isEmpty());
assertFalse(entry.isOwned());
assertFalse(heap.containsEntry(entry));
assertInvariants(heap);
//
// Clear
//
for (Element element : elements)
{
heap.offer(element, element._expectedPriority);
}
assertInvariants(heap);
heap.clear();
assertTrue(heap.isEmpty());
assertInvariants(heap);
//
// Simple add/remove test.
//
heap.ensureCapacity(nElements);
offerAll(heap, elements);
assertInvariants(heap);
assertEquals(nElements, heap.size());
pollAll(heap);
if (heap.deferOrdering())
{
// Try again without deferring ordering
assertTrue(heap.deferOrdering(false));
offerAll(heap, elements);
assertInvariants(heap);
assertEquals(nElements, heap.size());
pollAll(heap);
}
//
// Test deferral
//
offerRange(heap, elements, 0, nElements / 4);
heap.deferOrdering(false);
assertFalse(heap.deferOrdering());
assertInvariants(heap);
// Defer with most elements not yet added
heap.deferOrdering(true);
offerRange(heap, elements, nElements / 4, nElements);
assertInvariants(heap);
assertEquals(nElements, heap.size());
pollAll(heap);
offerRange(heap, elements, 0, 3 * (nElements / 4));
heap.deferOrdering(false);
assertFalse(heap.deferOrdering());
assertInvariants(heap);
// Defer with most elements already added
heap.deferOrdering(true);
offerRange(heap, elements, 3 * (nElements / 4), nElements);
assertInvariants(heap);
assertEquals(nElements, heap.size());
pollAll(heap);
//
// Test removal
//
offerAll(heap, elements);
assertTrue(heap.remove(elements[0]));
assertFalse(heap.remove(elements[0]));
assertFalse(heap.contains(elements[0]));
assertEquals(nElements - 1, heap.size());
assertInvariants(heap);
pollAll(heap);
offerAll(heap, elements);
entry = heap.entryForElement(elements[0]);
assertNotNull(entry);
assertTrue(entry.isOwned());
assertTrue(heap.removeEntry(entry));
assertFalse(entry.isOwned());
assertFalse(heap.removeEntry(entry));
assertEquals(nElements - 1, heap.size());
assertFalse(heap.containsEntry(entry));
assertFalse(heap.contains(entry.getElement()));
assertInvariants(heap);
for (int i = 1; i < nElements; ++i)
{
Element element = elements[i];
assertTrue(heap.remove(element));
assertFalse(heap.contains(element));
}
assertTrue(heap.isEmpty());
//
// Test priority change
//
offerAll(heap, elements);
assertInvariants(heap);
heap.deferOrdering(false);
for (Element element : elements)
{
entry = heap.entryForElement(element);
element._expectedPriority = rand.nextDouble();
heap.changePriority(Objects.requireNonNull(entry), element._expectedPriority);
assertTrue(heap.isOrdered());
}
assertInvariants(heap);
pollAll(heap);
if (heap.deferOrdering(true))
{
offerAll(heap, elements);
for (Element element : elements)
{
entry = heap.entryForElement(element);
element._expectedPriority = rand.nextDouble();
heap.changePriority(Objects.requireNonNull(entry), element._expectedPriority);
assertFalse(heap.isOrdered());
}
assertInvariants(heap);
pollAll(heap);
}
//
// All same priority
//
for (Element element : elements)
{
heap.offer(element, 42);
}
assertInvariants(heap, false);
pollAll(heap);
heap.clear();
//
// Test cloning
//
offerAll(heap, elements);
IHeap<Element> heap2 = heap.clone();
assertInvariants(heap2);
assertEquals(heap.size(), heap2.size());
Iterator<? extends IEntry<Element>> entries1 = heap.entryIterator();
Iterator<? extends IEntry<Element>> entries2 = heap2.entryIterator();
for (int i = 0; i < nElements; ++i)
{
assertTrue(entries1.hasNext());
assertTrue(entries2.hasNext());
IEntry<Element> entry1 = entries1.next();
IEntry<Element> entry2 = entries2.next();
assertSame(entry1.getElement(), entry2.getElement());
assertEquals(entry1.getPriority(), entry2.getPriority(), 0.0);
assertNotSame(entry1, entry2);
assertFalse(heap.containsEntry(entry2));
assertFalse(heap2.containsEntry(entry1));
assertFalse(heap.changePriority(entry2, 42));
assertFalse(heap.removeEntry(entry2));
}
assertFalse(entries1.hasNext());
assertFalse(entries2.hasNext());
heap2.clear();
assertFalse(heap2.containsEntry(Objects.requireNonNull(heap.peekEntry())));
//
// Test merge
//
heap.clear();
heap2.clear();
// Randomly distribute elements between two heaps.
for (Element element : elements)
{
if (rand.nextBoolean())
{
heap.offer(element, element._expectedPriority);
}
else
{
heap2.offer(element, element._expectedPriority);
}
}
assertInvariants(heap);
assertInvariants(heap2);
int size1 = heap.size();
int size2 = heap2.size();
assertEquals(nElements, size1 + size2);
heap.merge(heap2);
assertTrue(heap2.isEmpty());
assertInvariants(heap);
assertInvariants(heap2);
pollAll(heap);
}
private void offerAll(IHeap<Element> heap, Element ... elements)
{
for (Element element : elements)
{
heap.offer(element, element._expectedPriority);
if (heap.deferOrdering())
{
assertFalse(heap.isOrdered());
}
else
{
assertTrue(heap.isOrdered());
}
}
}
private void offerRange(IHeap<Element> heap, Element[] elements, int start, int end)
{
for (int i = start; i < end; ++i)
{
Element element = elements[i];
heap.offer(element, element._expectedPriority);
}
}
private void pollAll(IHeap<Element> heap)
{
IEntry<Element> prevEntry = null;
boolean usePollEntry = true;
while (!heap.isEmpty())
{
IEntry<Element> nextEntry = heap.peekEntry();
assertNotNull(nextEntry);
assertSame(nextEntry.getElement(), heap.peek());
assertTrue(heap.isOrdered());
if (usePollEntry)
{
assertSame(nextEntry, heap.pollEntry());
if (prevEntry != null)
{
assertTrue(prevEntry.getPriority() <= nextEntry.getPriority());
}
}
else
{
assertSame(nextEntry.getElement(), heap.poll());
assertNotSame(nextEntry, heap.peekEntry());
}
usePollEntry = !usePollEntry;
prevEntry = nextEntry;
}
}
private void assertInvariants(IHeap<Element> heap)
{
assertInvariants(heap, true);
}
private void assertInvariants(IHeap<Element> heap, boolean checkPriority)
{
final int size = heap.size();
assertTrue(size >= 0);
assertEquals(size == 0, heap.isEmpty());
if (!heap.deferOrdering())
{
assertTrue(heap.isOrdered());
}
if (heap.isEmpty())
{
assertNull(heap.peek());
assertNull(heap.peekEntry());
assertTrue(heap.isOrdered());
}
else if (!heap.deferOrdering())
{
// Don't peek if ordering is deferred to avoid changing state.
IEntry<Element> entry = heap.peekEntry();
requireNonNull(entry);
assertSame(heap.peek(), entry.getElement());
}
Iterator<? extends IEntry<Element>> entries = heap.entryIterator();
Iterator<Element> elements = heap.iterator();
int count = 0;
expectThrow(UnsupportedOperationException.class, elements, "remove");
while (entries.hasNext())
{
++count;
assertTrue(elements.hasNext());
IEntry<Element> entry = entries.next();
Element element = elements.next();
assertSame(element, entry.getElement());
if (checkPriority)
{
assertEquals(element._expectedPriority, entry.getPriority(), 0.0);
}
assertTrue(entry.isOwned());
assertTrue(heap.containsEntry(entry));
assertTrue(heap.contains(element));
IEntry<Element> entry2 = heap.entryForElement(element);
requireNonNull(entry2);
assertSame(element, entry2.getElement());
}
assertFalse(elements.hasNext());
assertEquals(size, count);
}
/**
* Crappy IHeap implementation for testing default methods in AbstractHeap
*/
public static class CrappyHeap<E> extends AbstractHeap<E>
{
private static class Entry<E> extends AbstractHeap.AbstractEntry<E>
{
private boolean _owned = true;
protected Entry(E element, double priority)
{
super(element, priority);
}
@Override
public boolean isOwned()
{
return _owned;
}
@Override
public Entry<E> clone()
{
return new Entry<E>(getElement(), _priority);
}
private void setPriority(double priority)
{
_priority = priority;
}
}
private List<Entry<E>> _entries = new ArrayList<Entry<E>>();
@Override
public boolean changePriority(IEntry<E> entry, double priority)
{
if (_entries.contains(entry))
{
((Entry<?>)entry).setPriority(priority);
return true;
}
return false;
}
@Override
public Iterator<? extends com.analog.lyric.collect.IHeap.IEntry<E>> entryIterator()
{
return _entries.iterator();
}
@Override
public Entry<E> offer(E element, double priority)
throws IllegalArgumentException
{
if (Double.isNaN(priority))
{
throw new IllegalArgumentException();
}
Entry<E> entry = new Entry<E>(element, priority);
_entries.add(entry);
return entry;
}
/*
*
*/
@Override
public @Nullable Entry<E> peekEntry()
{
Entry<E> first = null;
if (!_entries.isEmpty())
{
first = _entries.get(0);
for (Entry<E> entry : _entries)
{
if (entry.getPriority() < first.getPriority())
{
first = entry;
}
}
}
return first;
}
/*
*
*/
@Override
public @Nullable Entry<E> pollEntry()
{
Entry<E> entry = peekEntry();
if (entry != null)
{
removeEntry(entry);
}
return entry;
}
@Override
public boolean removeEntry(com.analog.lyric.collect.IHeap.IEntry<E> entry)
{
boolean removed = _entries.remove(entry);
if (removed)
{
((Entry<?>)entry)._owned = false;
}
return removed;
}
@Override
public void clear()
{
_entries.clear();
}
@Override
public CrappyHeap<E> clone()
{
CrappyHeap<E> clone = new CrappyHeap<E>();
for (Entry<E> entry : _entries)
{
clone.offer(entry.getElement(), entry.getPriority());
}
return clone;
}
@Override
public int size()
{
return _entries.size();
}
}
}