All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.semanticweb.elk.util.collections.Operations Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * ELK Reasoner
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 Department of Computer Science, University of Oxford
 * %%
 * 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.
 * #L%
 */
package org.semanticweb.elk.util.collections;

import java.io.IOException;
import java.io.Writer;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Some useful static methods for collections
 * 
 * @author "Yevgeny Kazakov"
 * @author Peter Skocovsky
 */
public class Operations {

	public static final Multimap EMPTY_MULTIMAP = new Multimap() {

		@Override
		public boolean contains(Object key, Object value) {
			return false;
		}

		@Override
		public boolean add(Object key, Object value) {
			throw new UnsupportedOperationException(
					"The Empty multimap cannot be modified!");
		}

		@Override
		public Collection get(Object key) {
			return Collections.emptySet();
		}

		@Override
		public boolean remove(Object key, Object value) {
			return false;
		}

		@Override
		public boolean isEmpty() {
			return true;
		}

		@Override
		public Set keySet() {
			return Collections.emptySet();
		}

		@Override
		public void clear() {
		}

		@Override
		public Collection remove(Object key) {
			return Collections.emptySet();
		}

	};

	@SuppressWarnings("unchecked")
	public static  Multimap emptyMultimap() {
		return (Multimap) EMPTY_MULTIMAP;
	}

	@SafeVarargs
	public static  Iterable concat(
			final Iterable... inputs) {
		return concat(Arrays.asList(inputs));
	}

	public static  Iterator singletonIterator(final T element) {
		return new Iterator() {
			boolean hasNext = true;

			@Override
			public boolean hasNext() {
				return hasNext;
			}

			@Override
			public T next() {
				hasNext = false;
				return element;
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	public static  Iterable singleton(final T element) {
		return new Iterable() {
			@Override
			public Iterator iterator() {
				return singletonIterator(element);
			}
		};
	}

	/**
	 * Concatenates several {@link Iterable}s into one
	 * 
	 * @param inputs
	 *            the {@link Iterable} of {@link Iterable}s to be concatenated
	 * @param 
	 *            the type of elements of {@link Iterable}s
	 * @return {@link Iterable} consisting of all elements found in input
	 *         {@link Iterable}s
	 */
	public static  Iterable concat(
			final Iterable> inputs) {
		assert inputs != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator> outer = inputs
							.iterator();

					Iterator inner;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public T next() {
						if (hasNext) {
							T result = inner.next();
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException();
					}

					boolean advance() {
						while (true) {
							if (inner != null && inner.hasNext())
								return true;

							if (outer.hasNext())
								inner = outer.next().iterator();
							else
								return false;
						}
					}
				};
			}
		};
	}

	/**
	 * @param 
	 *            the type of elements of iterators
	 * @param first
	 *            the elements that should come first
	 * @param second
	 *            the elements that should come next
	 * @return an iterator that iterates that advances the two given iterators
	 *         at the same time and returns the values of the second iterator
	 */
	public static  Iterator synchronize(Iterator first,
			Iterator second) {

		return new Iterator() {

			@Override
			public boolean hasNext() {
				return first.hasNext() && second.hasNext();
			}

			@Override
			public T next() {
				first.next();
				return second.next();
			}

		};

	}
	
	/**
	 * Splits the input {@link Iterable} on batches with at most given number of
	 * elements.
	 *  
	 * @param elements
	 *            the {@link Iterable} to be split
	 * @param 
	 *            the type of elements in the {@link Iterable}
	 * @param batchSize
	 *            the maximal number of elements in batches
	 * @return a {@link Iterable} of batches containing elements from the input
	 *         collection
	 * @see #concat(Iterable)
	 */
	public static  Iterable> split(
			final Iterable elements, final int batchSize) {
		return new Iterable>() {

			@Override
			public Iterator> iterator() {
				return new Iterator>() {

					final Iterator elementsIterator = elements
							.iterator();

					@Override
					public boolean hasNext() {
						return elementsIterator.hasNext();
					}

					@Override
					public ArrayList next() {
						final ArrayList nextBatch = new ArrayList(
								batchSize);
						int count = 0;
						while (count++ < batchSize
								&& elementsIterator.hasNext()) {
							nextBatch.add(elementsIterator.next());
						}
						return nextBatch;
					}

					@Override
					public void remove() {
						throw new UnsupportedOperationException(
								"Deletion is not supported");
					}
				};
			}
		};
	}

	/**
	 * Splits the input {@link Collection} on batches with at most given number
	 * of elements.
	 * 
	 * @param elements
	 *            the {@link Collection} to be split
	 * @param 
	 *            the type of elements in the {@link Collection}           
	 * @param batchSize
	 *            the maximal number of elements in batches
	 * @return a {@link Collection} of batches containing elements from the
	 *         input collection
	 */
	public static  Collection> split(
			final Collection elements, final int batchSize) {
		return new AbstractCollection>() {

			@Override
			public Iterator> iterator() {
				Iterable> iterable = split(
						(Iterable) elements, batchSize);
				return iterable.iterator();
			}

			@Override
			public int size() {
				// rounding up
				return (elements.size() + batchSize - 1) / batchSize;
			}
		};
	}

	public static  Collection getCollection(final Iterable iterable,
			final int size) {
		return new AbstractCollection() {

			@Override
			public Iterator iterator() {
				return iterable.iterator();
			}

			@Override
			public int size() {
				return size;
			}
		};
	}

	/**
	 * @param 
	 *            the type of elements
	 * @param input
	 *            the input iterator over these elements
	 * @param condition
	 *            the condition used for filtering
	 * @return the filtered iterator
	 * 
	 */	
	public static  Iterable filter(final Iterable input,
			final Condition condition) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator i = input.iterator();
					T next;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public T next() {
						if (hasNext) {
							T result = next;
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						i.remove();
					}

					boolean advance() {
						while (i.hasNext()) {
							next = i.next();
							if (condition.holds(next))
								return true;
						}
						return false;
					}
				};
			}
		};
	}

	@SuppressWarnings("unchecked")
	public static  Iterable filter(final Iterable input,
			final Class type) {

		return (Iterable) filter(input, new Condition() {
			@Override
			public boolean holds(S element) {
				return type.isInstance(element);
			}
		});
	}

	/**
	 * Returns read-only view of the given set consisting of the elements
	 * satisfying a given condition, if the number of such elements is known
	 * 
	 * @param 
	 *            the type of elements
	 * @param input
	 *            the given set to be filtered
	 * @param condition
	 *            the condition used for filtering the set. Must be consistent
	 *            with equals() for T, that is: a.equals(b) must imply that
	 *            holds(a) == holds(b)
	 * @param size
	 *            the number of elements in the filtered set
	 * @return the set consisting of the elements of the input set satisfying
	 *         the given condition
	 */
	public static  Set filter(final Set input,
			final Condition condition, final int size) {
		return new Set() {

			@Override
			public int size() {
				return size;
			}

			@Override
			public boolean isEmpty() {
				return size == 0;
			}

			@Override
			@SuppressWarnings("unchecked")
			public boolean contains(Object o) {

				if (!input.contains(o))
					return false;

				T elem = null;

				try {
					elem = (T) o;
				} catch (ClassCastException cce) {
					return false;
				}
				/*
				 * here's why the condition must be consistent with equals(): we
				 * check it on the passed element while we really need to check
				 * it on the element which is in the underlying set (and is
				 * equal to o according to equals()). However, as long as the
				 * condition is consistent, the result will be the same.
				 */
				return condition.holds(elem);
			}

			@Override
			public Iterator iterator() {
				Iterable iterable = filter(input, condition);
				return iterable.iterator();
			}

			@Override
			public Object[] toArray() {
				Object[] result = new Object[size];
				int i = 0;
				for (Object o : filter(input, condition)) {
					result[i++] = o;
				}
				return result;
			}

			@Override
			public  S[] toArray(S[] a) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean add(T e) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean remove(Object o) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean containsAll(Collection c) {
				for (Object o : c) {
					if (contains(o))
						return false;
				}
				return true;
			}

			@Override
			public boolean addAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean retainAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public boolean removeAll(Collection c) {
				throw new UnsupportedOperationException();
			}

			@Override
			public void clear() {
				throw new UnsupportedOperationException();
			}

		};

	}

	/**
	 * Transformations of input values to output values
	 * 
	 * @param 
	 *            the type of the input of the transformation
	 * @param 
	 *            the type of the output of the transformation
	 */
	public interface Transformation {
		/**
		 * Transforms the input element
		 * 
		 * @param element
		 *            the element to be transformed
		 * @return the result of the transformation
		 */
		public O transform(I element);
	}

	// TODO: get rid of Conditions in favour of transformations

	/**
	 * Transforms elements using a given {@link Transformation} the output
	 * elements consist of the result of the transformation in the same order;
	 * if the transformation returns {@code null}, it is not included in the
	 * output
	 * 
	 * @param 
	 *            the type of input of elements
	 * @param 
	 *            the type of output elements
	 * @param input
	 *            the input elements
	 * @param transformation
	 *            the transformation for elements
	 * @return the transformed output elements
	 * 
	 */
	public static  Iterable map(final Iterable input,
			final Transformation transformation) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {

				return new Iterator() {
					Iterator i = input.iterator();
					O next;
					boolean hasNext = advance();

					@Override
					public boolean hasNext() {
						return hasNext;
					}

					@Override
					public O next() {
						if (hasNext) {
							O result = next;
							hasNext = advance();
							return result;
						}
						throw new NoSuchElementException();
					}

					@Override
					public void remove() {
						i.remove();
					}

					boolean advance() {
						while (i.hasNext()) {
							next = transformation.transform(i.next());
							if (next != null)
								return true;
						}
						return false;
					}
				};
			}
		};
	}

	public static  Iterable mapConcat(final Iterable input,
			final Transformation> transformation) {
		assert input != null;

		return new Iterable() {

			@Override
			public Iterator iterator() {
				return mapConcat(input.iterator(), transformation);
			}
		};
	}

	public static  Collection map(final Collection input,
			final Transformation transformation) {
		assert input != null;

		return new AbstractCollection() {

			@Override
			public Iterator iterator() {
				return map(input.iterator(), transformation);
			}

			@Override
			public int size() {
				// this is an upper bound
				return input.size();
			}

		};
	}

	public static  Iterator map(final Iterator input,
			final Transformation transformation) {
		return new Iterator() {
			O next;
			boolean hasNext = advance();

			@Override
			public boolean hasNext() {
				return hasNext;
			}

			@Override
			public O next() {
				if (hasNext) {
					O result = next;
					hasNext = advance();
					return result;
				}
				throw new NoSuchElementException();
			}

			@Override
			public void remove() {
				input.remove();
			}

			boolean advance() {
				while (input.hasNext()) {
					next = transformation.transform(input.next());
					if (next != null)
						return true;
				}
				return false;
			}
		};
	}

	public static  Iterator mapConcat(final Iterator input,
			final Transformation> transformation) {
		return new Iterator() {
			Iterator nextIter = Collections. emptyList().iterator();

			O next;

			@Override
			public boolean hasNext() {
				for (;;) {
					if (next != null) {
						return true;
					}

					if (nextIter.hasNext()) {
						next = nextIter.next();
						return true;
					}

					if (input.hasNext()) {
						nextIter = transformation.transform(input.next())
								.iterator();
					} else {
						return false;
					}
				}
			}

			@Override
			public O next() {
				if (!hasNext()) {
					throw new NoSuchElementException();
				}

				O result = next;

				next = null;

				return result;
			}

			@Override
			public void remove() {
				input.remove();
			}

		};
	}

	/**
	 * Prints key-value entries present in the first {@link Multimap} but not in
	 * the second {@link Multimap} using the given {@link Writer} and prefixing
	 * all messages with a given prefix.
	 * 
	 * @param 
	 *            the type of the keys of the {@link Multimap}
	 * @param 
	 *            the type of the values of the {@link Multimap}
	 * @param first
	 *            the entries that should be printed
	 * @param second
	 *            the entries that should be skipped
	 * @param writer
	 *            the writer using which the entries should be printed
	 * @param prefix
	 *            the string to be appended to every line
	 * @throws IOException
	 *             if any I/O error occurs
	 */
	public static  void dumpDiff(Multimap first,
			Multimap second, Writer writer, String prefix)
			throws IOException {
		for (K key : first.keySet()) {
			Collection firstValues = first.get(key);
			Collection secondValues = second.get(key);
			dumpDiff(firstValues, secondValues, writer, prefix + key + "->");
		}

	}

	/**
	 * Prints the elements present in the first {@link Collection} but not in
	 * the second {@link Collection} using the given {@link Writer} and
	 * prefixing all messages with a given prefix.
	 * 
	 * @param 
	 *            the type of elements
	 * @param first
	 *            the elements to be printed
	 * @param second
	 *            the elements to be skipped
	 * @param writer
	 *            the writer using which the elements should be printed
	 * @param prefix
	 *            the string to be appended to every line
	 * @throws IOException
	 *             if any I/O error occurs
	 */
	public static  void dumpDiff(Collection first, Collection second,
			Writer writer, String prefix) throws IOException {
		for (T element : first)
			if (!second.contains(element))
				writer.append(prefix + element + "\n");
	}

	/**
	 * A simple object that transforms objects of type I to objects of type O
	 * 
	 * @author Pavel Klinov
	 * 
	 *         [email protected]
	 * 
	 * @param 
	 *            the type of input objects
	 * @param 
	 *            the type of the output objects
	 */
	public interface Functor {

		public O apply(I element);
	}

	/**
	 * An extension of {@link Functor} which can do the reverse transformation
	 * 
	 * @author Pavel Klinov
	 *
	 *         [email protected]
	 * @param 
	 *            the type of input objects
	 * @param 
	 *            the type of output objects
	 */
	public interface FunctorEx extends Functor {

		/**
		 * The reason this method takes Objects rather than instances of O is
		 * because it's primarily used for an efficient implementation of
		 * {@link Set#contains(Object)}, which takes an Object
		 * 
		 * @param element
		 *            the output element to be converted back to input
		 * 
		 * @return Can return null if the transformation is not possible
		 */
		public I deapply(Object element);
	}

	/**
	 * An {@link Iterator} whose elements are obtained from another
	 * {@link Iterator} by applying a {@link Functor}
	 * 
	 * @author Pavel Klinov
	 *
	 *         [email protected]
	 * 
	 * @param 
	 *            the type of elements of the input {@link Iterator}
	 * @param 
	 *            the type of elements of this {@link Iterator}
	 */
	private static class MapIterator implements Iterator {

		private final Iterator iter_;
		private final Functor functor_;

		MapIterator(Iterator iter, Functor functor) {
			iter_ = iter;
			functor_ = functor;
		}

		@Override
		public boolean hasNext() {
			return iter_.hasNext();
		}

		@Override
		public O next() {
			return functor_.apply(iter_.next());
		}

		@Override
		public void remove() {
			iter_.remove();
		}

	}

	/**
	 * A simple second-order map function for {@link Set}s
	 * 
	 * @param 
	 *            the type of elements of the input {@link Set}
	 * @param 
	 *            the type of elements of this {@link Set}
	 * @param input
	 *            the input {@link Set} to be converted
	 * @param functor
	 *            the {@link Functor} that applies to the input elements
	 * @return a set which contains the results of applying the {@link Functor} to the
	 *         elements in the input {@link Set}
	 */
	public static  Set map(final Set input,
			final FunctorEx functor) {
		return new AbstractSet() {

			@Override
			public Iterator iterator() {
				return new MapIterator(input.iterator(), functor);
			}

			@Override
			public boolean contains(Object o) {
				I element = functor.deapply(o);

				return element == null ? false : input.contains(element);
			}

			@Override
			public int size() {
				return input.size();
			}

		};
	}

	public static  String toString(Iterable iterable) {
		StringBuilder builder = new StringBuilder();
		boolean first = true;

		for (T elem : iterable) {
			String elemStr = elem.toString();

			if (!first && !elemStr.isEmpty()) {
				builder.append(' ');
			}

			if (!elemStr.isEmpty()) {
				builder.append(elemStr);
			}

			first = false;
		}

		return builder.toString();
	}

	/**
	 * @param 
	 *            the type of elements
	 * @param elementComparator
	 *            a {@link Comparator} on these elements
	 * @return {@link Comparator} that compares {@link Iterable} according to
	 *         lexical order w.r.t. element ordering defined by the provided
	 *         element {@link Comparator}.
	 */
	public static final  Comparator> lexicalOrder(
			final Comparator elementComparator) {
		return new Comparator>() {
			@Override
			public int compare(final Iterable o1, final Iterable o2) {

				final Iterator list1 = o1 == null
						? Collections. emptyList().iterator()
						: o1.iterator();
				final Iterator list2 = o2 == null
						? Collections. emptyList().iterator()
						: o2.iterator();

				while (list1.hasNext() || list2.hasNext()) {

					// index did not exceed the size of at least one list.
					if (!list1.hasNext()) {
						// list1 is shorter and coincides on its elements.
						return -1;
					}
					if (!list2.hasNext()) {
						// list2 is shorter and coincides on its elements.
						return 1;
					}
					// index exceeded the size of none of the lists.

					final int cmp = elementComparator.compare(list1.next(),
							list2.next());
					if (cmp != 0) {
						return cmp;
					}

				}
				/*
				 * index exceeded size of both lists and no difference was
				 * found, so the lists are the same.
				 */
				return 0;
			}
		};
	}

}