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

com.numdata.oss.HashList Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Numdata nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NUMDATA BV BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.numdata.oss;

import java.util.*;

import org.jetbrains.annotations.*;

/**
 * Adds index hashing to a {@link ArrayList} to provide fast {@link #contains}
 * and {@link #indexOf} lookups. Modifications are rather costly, especially
 * adding/removing any element other than the element at the end of the list.
 *
 * @param  Element type.
 *
 * @author Peter S. Heijnen
 */
public class HashList
extends ArrayList
{
	/**
	 * Serialize data version.
	 */
	private static final long serialVersionUID = -2142544575701848415L;

	/**
	 * Maps {@link Object#hashCode()} to {@link TreeSet}s with element indices.
	 */
	private final Map> _indexHashmap = new HashMap>();

	/**
	 * Construct empty list.
	 */
	public HashList()
	{
	}

	/**
	 * Construct list will all contents from the specified collection.
	 *
	 * @param collection Collection with initial contents.
	 */
	@SuppressWarnings( "OverridableMethodCallDuringObjectConstruction" )
	public HashList( final Collection collection )
	{
		addAll( collection );
	}

	@Override
	public boolean add( final E element )
	{
		add( size(), element );
		return true;
	}

	/**
	 * Convenience method to implement common application of {@link HashList}:
	 * get index of {@code element} in list if it already exists or add it to
	 * the end of the list, and return the index of the {@code element} in the
	 * list.
	 *
	 * @param element Element to get index of or add to end of the list.
	 *
	 * @return Index of object in list.
	 */
	public int indexOfOrAdd( @NotNull final E element )
	{
		int result = -1;

		final Map> indexHashmap = _indexHashmap;
		final Integer hashCode = element.hashCode();

		TreeSet indices = indexHashmap.get( hashCode );
		if ( indices != null )
		{
			for ( final int index : indices )
			{
				if ( element.equals( get( index ) ) )
				{
					result = index;
					break;
				}
			}
		}

		if ( result < 0 )
		{
			/*
			 * Add index to map.
			 */
			if ( indices == null )
			{
				indices = new TreeSet();
				indexHashmap.put( hashCode, indices );
			}

			final int size = size();
			indices.add( size );
			super.add( size, element );
			result = size;
		}

		return result;
	}

	@Override
	public void add( final int index, final E element )
	{
		final Map> indexHashmap = _indexHashmap;

		/*
		 * Increment index of all trailing elements.
		 */
		if ( index < size() )
		{
			for ( final Map.Entry> entry : indexHashmap.entrySet() )
			{
				final TreeSet newIndices = new TreeSet();

				for ( final Integer i : entry.getValue() )
				{
					if ( i >= index )
					{
						newIndices.add( i + 1 );
					}
					else
					{
						newIndices.add( i );
					}
				}

				entry.setValue( newIndices );
			}
		}

		/*
		 * Add index to map.
		 */
		final Integer hashCode = element.hashCode();

		TreeSet indices = indexHashmap.get( hashCode );
		if ( indices == null )
		{
			indices = new TreeSet();
			indexHashmap.put( hashCode, indices );
		}

		indices.add( index );
		super.add( index, element );
	}

	@Override
	public boolean addAll( final Collection collection )
	{
		for ( final E element : collection )
		{
			add( element );
		}

		return !collection.isEmpty();
	}

	@Override
	public boolean addAll( final int index, final Collection collection )
	{
		int i = index;

		for ( final E element : collection )
		{
			add( i++, element );
		}

		return !collection.isEmpty();
	}

	@Override
	public void clear()
	{
		super.clear();
		_indexHashmap.clear();
	}

	@Override
	public boolean contains( final Object object )
	{
		return ( indexOf( object ) >= 0 );
	}

	@Override
	public boolean containsAll( @NotNull final Collection collection )
	{
		boolean result = true;

		for ( final Object element : collection )
		{
			if ( !contains( element ) )
			{
				result = false;
				break;
			}
		}

		return result;
	}

	@Override
	public int indexOf( final Object object )
	{
		int result = -1;

		final Iterable indices = _indexHashmap.get( object.hashCode() );
		if ( indices != null )
		{
			for ( final int index : indices )
			{
				if ( object.equals( get( index ) ) )
				{
					result = index;
					break;
				}
			}
		}

		return result;
	}

	@Override
	public int lastIndexOf( final Object object )
	{
		int result = -1;

		final NavigableSet indices = _indexHashmap.get( object.hashCode() );
		if ( indices != null )
		{
			for ( final int index : indices.descendingSet() )
			{
				if ( object.equals( get( index ) ) )
				{
					result = index;
					break;
				}
			}
		}

		return result;
	}

	@NotNull
	@Override
	public ListIterator listIterator()
	{
		return new HashListIterator();
	}

	@Override
	public E remove( final int index )
	{
		final E element = super.remove( index );

		final Map> indexHashmap = _indexHashmap;

		/*
		 * Remove index from map.
		 */
		final Integer hashCode = element.hashCode();

		final Set indices = indexHashmap.get( hashCode );
		if ( indices.size() > 1 )
		{
			indices.remove( index );
		}
		else
		{
			indexHashmap.remove( hashCode );
		}

		/*
		 * Decrement index of all trailing elements.
		 */
		if ( index < size() - 1 )
		{
			for ( final Map.Entry> entry : indexHashmap.entrySet() )
			{
				final TreeSet newIndices = new TreeSet();

				for ( final Integer i : entry.getValue() )
				{
					if ( i > index )
					{
						newIndices.add( i - 1 );
					}
					else
					{
						newIndices.add( i );
					}
				}

				entry.setValue( newIndices );
			}
		}

		return element;
	}

	@Override
	public boolean remove( final Object object )
	{
		final boolean result;

		final int index = indexOf( object );
		if ( index >= 0 )
		{
			remove( index );
			result = true;
		}
		else
		{
			result = false;
		}

		return result;
	}

	@Override
	public boolean removeAll( @NotNull final Collection collection )
	{
		boolean result = false;

		for ( final Object element : collection )
		{
			if ( remove( element ) )
			{
				result = true;
			}
		}

		return result;
	}

	@Override
	public boolean retainAll( @NotNull final Collection collection )
	{
		boolean result = false;

		int index = 0;
		while ( index < size() )
		{
			final E element = get( index );
			if ( !collection.contains( element ) )
			{
				remove( index );
				result = true;
			}
			else
			{
				index++;
			}
		}

		return result;
	}

	@Override
	protected void removeRange( final int fromIndex, final int toIndex )
	{
		for ( int toRemove = toIndex - fromIndex; toRemove > 0; toRemove-- )
		{
			remove( fromIndex );
		}
	}

	@Override
	public E set( final int index, final E element )
	{
		final E oldElement = get( index );

		//noinspection ObjectEquality
		if ( element != oldElement )
		{
			final Map> indexHashmap = _indexHashmap;

			final Integer indexValue = index;

			Integer hashCode = oldElement.hashCode();

			TreeSet indices = indexHashmap.get( hashCode );
			if ( indices.size() > 1 )
			{
				indices.remove( indexValue );
			}
			else
			{
				indexHashmap.remove( hashCode );
			}

			hashCode = element.hashCode();

			indices = indexHashmap.get( hashCode );
			if ( indices == null )
			{
				indices = new TreeSet();
				indexHashmap.put( hashCode, indices );
			}

			indices.add( indexValue );
		}

		return super.set( index, element );
	}

	/**
	 * Iterator for {@link HashList}. This extends {@link ListIterator} to make
	 * sure {@link HashList#_indexHashmap} is updated when changes are made
	 * using the iterator's {@link #add}, {@link #set} or {@link #remove}
	 * methods.
	 */
	private class HashListIterator
	implements ListIterator
	{
		/**
		 * Index of current element.
		 */
		int _index = -1;

		/**
		 * Flag to indicate that the current element can be removed.
		 */
		boolean _removable = false;

		/**
		 * Flag to indicate that the current element was removed.
		 */
		boolean _removed = false;

		@Override
		public void add( final E element )
		{
			HashList.this.add( _index++, element );
			_removable = false;
		}

		@Override
		public boolean hasNext()
		{
			return ( _index < size() - 1 );
		}

		@Override
		public boolean hasPrevious()
		{
			return ( _index > 0 );
		}

		@Override
		public E next()
		{
			if ( !hasNext() )
			{
				throw new NoSuchElementException( "finished" );
			}

			int index = _index;
			if ( !_removed )
			{
				_index = ++index;
			}

			_removable = true;
			_removed = false;

			return get( index );
		}

		@Override
		public int nextIndex()
		{
			return ( _removed ? _index : ( _index + 1 ) );
		}

		@Override
		public E previous()
		{
			if ( !hasPrevious() )
			{
				throw new NoSuchElementException( "finished" );
			}

			_removable = true;
			_removed = false;

			return get( --_index );
		}

		@Override
		public int previousIndex()
		{
			return hasPrevious() ? ( _index - 1 ) : -1;
		}

		@Override
		public void remove()
		{
			if ( !_removable )
			{
				throw new IllegalStateException( "can't remove twice" );
			}

			_removable = false;
			_removed = true;

			HashList.this.remove( _index );
		}

		@Override
		public void set( final E value )
		{
			HashList.this.set( _index, value );
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy