blob: da598ae4b813c3c17561539eb880d9a604df26df [file] [log] [blame]
using Lucene.Net.Attributes;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using Assert = Lucene.Net.TestFramework.Assert;
using Console = Lucene.Net.Util.SystemConsole;
namespace Lucene.Net.Util
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
[TestFixture]
public class TestPriorityQueue : LuceneTestCase
{
private static readonly int MAX_PQ_SIZE = ArrayUtil.MAX_ARRAY_LENGTH - 1;
private class IntegerQueue : PriorityQueue<int?>
{
public IntegerQueue(int count)
: base(count)
{
}
public IntegerQueue(int count, bool prepopulate)
: base(count, prepopulate)
{
}
protected internal override bool LessThan(int? a, int? b)
{
return (a <= b);
}
}
public void TestPQ()
{
TestPQ(AtLeast(10000), Random);
}
public static void TestPQ(int count, Random gen)
{
PriorityQueue<int?> pq = new IntegerQueue(count);
int sum = 0, sum2 = 0;
for (int i = 0; i < count; i++)
{
int next = gen.Next();
sum += next;
pq.Add(next);
}
// Date end = new Date();
// System.out.print(((float)(end.getTime()-start.getTime()) / count) * 1000);
// System.out.println(" microseconds/put");
// start = new Date();
int last = int.MinValue;
for (int i = 0; i < count; i++)
{
var next = pq.Pop();
assertTrue(next.Value >= last);
last = next.Value;
sum2 += last;
}
assertEquals(sum, sum2);
// end = new Date();
// System.out.print(((float)(end.getTime()-start.getTime()) / count) * 1000);
// System.out.println(" microseconds/pop");
}
[Test]
public virtual void TestClear()
{
PriorityQueue<int?> pq = new IntegerQueue(3);
pq.Add(2);
pq.Add(3);
pq.Add(1);
Assert.AreEqual(3, pq.Count);
pq.Clear();
Assert.AreEqual(0, pq.Count);
}
[Test]
public void TestFixedSize()
{
PriorityQueue<int?> pq = new IntegerQueue(3);
pq.InsertWithOverflow(2);
pq.InsertWithOverflow(3);
pq.InsertWithOverflow(1);
pq.InsertWithOverflow(5);
pq.InsertWithOverflow(7);
pq.InsertWithOverflow(1);
assertEquals(3, pq.Count);
assertEquals((int?)3, pq.Top);
}
[Test]
public virtual void TestInsertWithOverflow()
{
// Tests that InsertWithOverflow discards the correct value,
// and the resulting PQ preserves its structure
int size = 4;
PriorityQueue<int?> pq = new IntegerQueue(size);
int? i1 = 2;
int? i2 = 3;
int? i3 = 1;
int? i4 = 5;
int? i5 = 7;
int? i6 = 1;
Assert.IsNull(pq.InsertWithOverflow(i1));
Assert.IsNull(pq.InsertWithOverflow(i2));
Assert.IsNull(pq.InsertWithOverflow(i3));
Assert.IsNull(pq.InsertWithOverflow(i4));
Assert.IsTrue(pq.InsertWithOverflow(i5) == i3); // i3 should have been dropped
Assert.IsTrue(pq.InsertWithOverflow(i6) == i6); // i6 should not have been inserted
Assert.AreEqual(size, pq.Count);
Assert.AreEqual((int?)2, pq.Top);
// LUCENENET SPECIFIC
pq.Pop();
Assert.AreEqual((int?)3, pq.Top);
pq.Pop();
Assert.AreEqual((int?)5, pq.Top);
pq.Pop();
Assert.AreEqual((int?)7, pq.Top);
}
#region LUCENENET SPECIFIC TESTS
private class IntegerQueueWithSentinel : IntegerQueue
{
public IntegerQueueWithSentinel(int count, bool prepopulate)
: base(count, prepopulate)
{
}
protected override int? GetSentinelObject()
{
return int.MaxValue;
}
}
private class MyType
{
public MyType(int field)
{
Field = field;
}
public int Field { get; set; }
}
private class MyQueue : PriorityQueue<MyType>
{
public MyQueue(int count)
: base(count)
{
}
protected internal override bool LessThan(MyType a, MyType b)
{
return (a.Field < b.Field);
}
}
private class Less : IComparer<int?>
{
public int Compare(int? a, int? b)
{
Assert.IsNotNull(a);
Assert.IsNotNull(b);
return (int) (a - b);
}
}
private class Greater : IComparer<int?>
{
public int Compare(int? a, int? b)
{
Assert.IsNotNull(a);
Assert.IsNotNull(b);
return (int) (a - b);
}
}
[Ignore("Increase heap size to run this test")]
[Test, LuceneNetSpecific]
public static void TestMaxSizeBounds()
{
// Minimum size is 0
int maxSize = 0;
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
// Maximum size is ArrayUtil.MAX_ARRAY_LENGTH - 1
maxSize = MAX_PQ_SIZE;
pq = new IntegerQueue(maxSize);
// A valid maximum size
maxSize = 12345;
pq = new IntegerQueue(maxSize);
// Cannot construct a negative size heap
maxSize = -3;
try
{
pq = new IntegerQueue(maxSize);
// Should had thrown an exception
Assert.Fail();
}
catch (ArgumentException)
{
}
maxSize = MAX_PQ_SIZE;
try
{
pq = new IntegerQueue(maxSize);
}
catch (ArgumentException)
{
}
}
[Test, LuceneNetSpecific]
public static void TestPrepopulation()
{
int maxSize = 10;
// Populates the internal array
PriorityQueue<int?> pq = new IntegerQueueWithSentinel(maxSize, true);
Assert.AreEqual(pq.Top, int.MaxValue);
Assert.AreEqual(pq.Count, 10);
// Does not populate it
pq = new IntegerQueue(maxSize, false);
Assert.AreEqual(pq.Top, default);
Assert.AreEqual(pq.Count, 0);
}
private static void AddAndTest<T>(PriorityQueue<T> pq, T element, T expectedTop, int expectedSize)
{
pq.Add(element);
Assert.AreEqual(pq.Top, expectedTop);
Assert.AreEqual(pq.Count, expectedSize);
}
[Test, LuceneNetSpecific]
public static void TestAdd()
{
int maxSize = 10;
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
// Add mixed elements
AddAndTest(pq, 5, 5, 1);
AddAndTest(pq, 1, 1, 2);
AddAndTest(pq, 3, 1, 3);
AddAndTest(pq, -1, -1, 4);
AddAndTest(pq, -111111, -111111, 5);
// Add a sorted list of elements
pq = new IntegerQueue(maxSize);
AddAndTest(pq, -111111, -111111, 1);
AddAndTest(pq, -1, -111111, 2);
AddAndTest(pq, 1, -111111, 3);
AddAndTest(pq, 3, -111111, 4);
AddAndTest(pq, 5, -111111, 5);
// Add a reversed sorted list of elements
pq = new IntegerQueue(maxSize);
AddAndTest(pq, 5, 5, 1);
AddAndTest(pq, 3, 3, 2);
AddAndTest(pq, 1, 1, 3);
AddAndTest(pq, -1, -1, 4);
AddAndTest(pq, -111111, -111111, 5);
}
[Test, LuceneNetSpecific]
public static void TestDuplicates()
{
// Tests that the queue doesn't absorb elements with duplicate keys
int maxSize = 10;
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
pq.Add(3);
pq.Add(3);
Assert.AreEqual(pq.Count, 2);
pq.Add(3);
Assert.AreEqual(pq.Count, 3);
pq.Add(17);
pq.Add(17);
pq.Add(17);
pq.Add(17);
Assert.AreEqual(pq.Count, 7);
}
[Test, LuceneNetSpecific]
public static void TestPop()
{
int maxSize = 10;
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
// Add one element and pop it
pq.Add(7);
pq.Pop();
Assert.AreEqual(pq.Count, 0);
// Add a bunch of elements, pop them all
pq.Add(1);
pq.Add(20);
pq.Add(1);
pq.Add(15);
pq.Add(4);
pq.Add(12);
pq.Add(1000);
pq.Add(-3);
pq.Pop();
Assert.AreEqual(pq.Count, 7);
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
Assert.AreEqual(pq.Count, 0);
// Interleaved adds and pops
pq.Add(1);
pq.Add(20);
pq.Pop();
Assert.AreEqual(pq.Count, 1);
pq.Add(1);
pq.Add(15);
pq.Add(4);
pq.Pop();
pq.Pop();
Assert.AreEqual(pq.Count, 2);
pq.Add(12);
pq.Add(1000);
pq.Add(-3);
pq.Pop();
pq.Pop();
Assert.AreEqual(pq.Count, 3);
// Pop an empty PQ
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
pq.Pop();
}
[Test, LuceneNetSpecific]
public static void TestUpdateTop()
{
// Mostly to reflect the usage of UpdateTop
int maxSize = 10;
PriorityQueue<MyType> pq = new MyQueue(maxSize);
pq.Add(new MyType(1));
pq.Add(new MyType(20));
pq.Add(new MyType(1));
pq.Add(new MyType(15));
pq.Add(new MyType(4));
pq.Add(new MyType(12));
pq.Add(new MyType(1000));
pq.Add(new MyType(-300));
Assert.AreEqual(pq.Top.Field, -300);
MyType topElement = pq.Top;
topElement.Field = 25; // Now this should no longer be at the top of the queue
pq.UpdateTop(); // Hence we need to update the top queue
Assert.AreEqual(pq.Top.Field, 1);
// The less eficient way to do this is the following
topElement = pq.Pop();
topElement.Field = 678;
pq.Add(topElement);
Assert.AreEqual(pq.Top.Field, 1);
}
[Test, LuceneNetSpecific]
public static void TestOverflow()
{
// Tests adding elements to full queues
// Add's documentation claims throwing an IndexOutOfRangeException in this situation
// Add an element to a prepopulated queue
int maxSize = 10;
PriorityQueue<int?> pq = new IntegerQueueWithSentinel(maxSize, true);
try
{
pq.Add(3);
Assert.Fail();
}
catch (IndexOutOfRangeException)
{
}
// Populate manually
maxSize = 5;
pq = new IntegerQueue(maxSize);
pq.Add(1);
pq.Add(4);
pq.Add(-1);
pq.Add(0);
pq.Add(10);
try
{
pq.Add(666);
}
catch (IndexOutOfRangeException)
{
}
}
[Test, LuceneNetSpecific]
public virtual void TestInsertWithOverflowDoesNotOverflow()
{
// Tests that InsertWithOverflow does not cause overflow
PriorityQueue<int?> pq = new IntegerQueue(3);
pq.InsertWithOverflow(2);
pq.InsertWithOverflow(3);
pq.InsertWithOverflow(1);
pq.InsertWithOverflow(5);
pq.InsertWithOverflow(7);
pq.InsertWithOverflow(1);
Assert.AreEqual(3, pq.Count);
Assert.AreEqual((int?)3, pq.Top);
}
private static void AddElements<T>(PriorityQueue<T> pq, T[] elements)
{
int size = (int)elements.size();
for (int i = 0; i < size; i++)
{
pq.Add(elements[i]);
}
}
private static void PopElements<T>(PriorityQueue<T> pq)
{
int size = pq.Count;
for (int i = 0; i < size; i++)
{
pq.Pop();
}
}
private static void PopAndTestElements<T>(PriorityQueue<T> pq, T[] elements)
{
int size = pq.Count;
for (int i = 0; i < size; i++)
{
Assert.AreEqual(pq.Pop(), elements[i]);
}
}
private static void PopAndTestElements<T>(PriorityQueue<T> pq)
{
int size = pq.Count;
T last = pq.Pop();
for (int i = 1; i < size; i++)
{
T next = pq.Pop();
Assert.IsTrue(pq.LessThan(last, next));
last = next;
}
}
private static void TimedAddAndPop<T>(PriorityQueue<T> pq, T[] elements)
{
int size = (int)elements.size();
DateTime start, end;
TimeSpan total;
start = DateTime.Now;
AddElements(pq, elements);
end = DateTime.Now;
total = end - start;
Console.WriteLine("Total adding time: {0} ticks or {1}ms", total.Ticks, total.Milliseconds);
Console.WriteLine("Average time per add: {0} ticks", total.Ticks / size);
start = DateTime.Now;
PopElements(pq);
end = DateTime.Now;
total = end - start;
Console.WriteLine("Total popping time: {0} ticks or {1}ms", total.Ticks, total.Milliseconds);
Console.WriteLine("Average time per pop: {0} ticks", total.Ticks / size);
}
[Test, LuceneNetSpecific]
public static void TestPersistence()
{
// Tests that a big number of elements are added and popped (in the correct order)
// without losing any information
int maxSize = AtLeast(100000);
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
int?[] elements = new int?[maxSize];
for (int i = 0; i < maxSize; i++)
{
elements[i] = Random.Next();
}
AddElements(pq, elements);
ArrayUtil.IntroSort(elements, new Less());
PopAndTestElements(pq, elements);
}
[Test, LuceneNetSpecific]
public static void TestStress()
{
int atLeast = 1000000;
int maxSize = AtLeast(atLeast);
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
// Add a lot of elements
for (int i = 0; i < maxSize; i++)
{
pq.Add(Random.Next());
}
// Pop some of them
while (pq.Count > atLeast/2)
{
pq.Pop();
}
// Add some more
while (pq.Count < (atLeast*3)/4)
{
pq.Add(Random.Next());
}
PopAndTestElements(pq);
Assert.AreEqual(pq.Count, 0);
// We fill it again
for (int i = 0; 2 * i < maxSize; i++)
{
pq.Add(Random.Next());
}
Assert.AreEqual(pq.Count, (maxSize + 1) / 2);
pq.Clear();
Assert.AreEqual(pq.Count, 0);
// One last time
for (int i = 0; 2 * i < maxSize; i++)
{
pq.Add(Random.Next());
}
PopAndTestElements(pq);
Assert.AreEqual(pq.Count, 0);
}
[Test, Explicit, LuceneNetSpecific]
public static void Benchmarks()
{
AssumeTrue("Turn VERBOSE on or otherwise you won't see the results.", Verbose);
int maxSize = AtLeast(100000);
PriorityQueue<int?> pq = new IntegerQueue(maxSize);
int?[] elements = new int?[maxSize];
for (int i = 0; i < maxSize; i++)
{
elements[i] = Random.Next();
}
Console.WriteLine("Random list of elements...");
TimedAddAndPop<int?>(pq, elements);
pq.Clear();
Console.WriteLine("\nSorted list of elements...");
pq = new IntegerQueue(maxSize);
ArrayUtil.IntroSort(elements, new Less());
TimedAddAndPop<int?>(pq, elements);
pq.Clear();
Console.WriteLine("\nReverse sorted list of elements...");
pq = new IntegerQueue(maxSize);
ArrayUtil.IntroSort(elements, new Greater());
TimedAddAndPop<int?>(pq, elements);
pq.Clear();
}
#endregion
}
}