/*
* Copyright 2015, 2016 Tagir Valeev
*
* 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 one.util.streamex;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
/**
* Extracts least limit elements from the input sorting them according to the
* given comparator. Works for 2 <= limit < Integer.MAX_VALUE/2. Uses
* O(min(limit, inputSize)) additional memory.
*
* @param <T> type of input elements
*
* @author Tagir Valeev
*/
/* package */class Limiter<T> extends AbstractCollection<T> {
private T[] data;
private final int limit;
private final Comparator<? super T> comparator;
private int size;
private boolean initial = true;
@SuppressWarnings("unchecked")
public Limiter(int limit, Comparator<? super T> comparator) {
this.limit = limit;
this.comparator = comparator;
this.data = (T[]) new Object[Math.min(1000, limit) * 2];
}
/**
* Accumulate new element
*
* @param t element to accumulate
*
* @return false if the element is definitely not included into result, so
* any bigger element could be skipped as well, or true if element
* will probably be included into result.
*/
public boolean put(T t) {
if (initial) {
if (size == data.length) {
if (size < limit * 2) {
@SuppressWarnings("unchecked")
T[] newData = (T[]) new Object[Math.min(limit, size) * 2];
System.arraycopy(data, 0, newData, 0, size);
data = newData;
} else {
Arrays.sort(data, comparator);
initial = false;
size = limit;
}
put(t);
} else {
data[size++] = t;
}
return true;
}
if (size == data.length) {
sortTail();
}
if (comparator.compare(t, data[limit - 1]) < 0) {
data[size++] = t;
return true;
}
return false;
}
/**
* Merge other {@code Limiter} object into this (other object becomes unusable after that).
*
* @param ls other object to merge
* @return this object
*/
public Limiter<T> putAll(Limiter<T> ls) {
int i = 0;
if (!ls.initial) {
// sorted part
for (; i < limit; i++) {
if (!put(ls.data[i]))
break;
}
i = limit;
}
for (; i < ls.size; i++) {
put(ls.data[i]);
}
return this;
}
private void sortTail() {
// size > limit here
T[] d = data;
int l = limit, s = size;
Comparator<? super T> cmp = comparator;
Arrays.sort(d, l, s, cmp);
if (cmp.compare(d[s - 1], d[0]) < 0) {
// Common case: descending sequence
// Assume size - limit <= limit here
System.arraycopy(d, 0, d, s - l, 2 * l - s);
System.arraycopy(d, l, d, 0, s - l);
} else {
// Merge presorted 0..limit-1 and limit..size-1
@SuppressWarnings("unchecked")
T[] buf = (T[]) new Object[l];
int i = 0, j = l, k = 0;
// d[l-1] is guaranteed to be the worst element, thus no need to
// check it
while (i < l - 1 && k < l && j < s) {
if (cmp.compare(d[i], d[j]) <= 0) {
buf[k++] = d[i++];
} else {
buf[k++] = d[j++];
}
}
if (k < l) {
System.arraycopy(d, i < l - 1 ? i : j, d, k, l - k);
}
System.arraycopy(buf, 0, d, 0, k);
}
size = l;
}
/**
* Must be called after accumulation is finished. After calling
* {@code sort()} this Limiter represents the resulting collection.
*/
public void sort() {
if (initial)
Arrays.sort(data, 0, size, comparator);
else if (size > limit)
sortTail();
}
@Override
public Object[] toArray() {
return Arrays.copyOfRange(data, 0, size());
}
@Override
public Iterator<T> iterator() {
return Arrays.asList(data).subList(0, size()).iterator();
}
@Override
public int size() {
return initial && size < limit ? size : limit;
}
}