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

org.ddogleg.struct.DogArray Maven / Gradle / Ivy

Go to download

DDogleg Numerics is a high performance Java library for non-linear optimization, robust model fitting, polynomial root finding, sorting, and more.

The newest version!
/*
 * Copyright (c) 2012-2024, Peter Abeles. All Rights Reserved.
 *
 * This file is part of DDogleg (http://ddogleg.org).
 *
 * 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 org.ddogleg.struct;

import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * Growable array which automatically creates, recycles, and resets its elements. Access to internal variables
 * is provided for high performant code. If a reset function is provided then when an object is created or recycled
 * the reset function is called with the objective of giving the object a repeatable initial state.
 *
 * @author Peter Abeles
 */
@SuppressWarnings({"unchecked", "NullAway.Init", "ForLoopReplaceableByForEach", "ManualArrayToCollectionCopy"})
public class DogArray extends FastAccess {
	/** new instances are created using this. If null then no new instances are created automatically. */
	private @Getter Factory factory;
	/** function that's called to reset a returned instance */
	private @Getter @Setter DProcess reset;
	/** function that's called to initialize a new instance */
	private @Getter @Setter DProcess initialize = new DProcess.DoNothing<>();

	// Wrapper around this class for lists
	private final DogArrayList list = new DogArrayList<>(this);

	/**
	 * Constructor which allows new instances to be created using a lambda
	 */
	public DogArray( Class type, Factory factory ) {
		super(type);
		init(10, factory);
	}

	/**
	 * Constructor which allows new instances to be created using a lambda and determines the class by
	 * creating a new instance.
	 */
	public DogArray( Factory factory ) {
		super((Class)factory.newInstance().getClass());
		init(10, factory);
	}

	/**
	 * User provided factory function and reset function.
	 *
	 * @param factory Creates new instances
	 * @param reset Called whenever an element is recycled and needs to be reset
	 */
	public DogArray( Factory factory, DProcess reset ) {
		super((Class)factory.newInstance().getClass());
		this.reset = reset;
		init(10, factory);
	}

	/**
	 * User provided factory function and reset function.
	 *
	 * @param factory Creates new instances
	 * @param reset Called whenever an element is recycled and needs to be reset
	 * @param initialize Called after a new instance is created
	 */
	public DogArray( Factory factory, DProcess reset, DProcess initialize ) {
		super((Class)factory.newInstance().getClass());
		this.reset = reset;
		this.initialize = initialize;
		init(10, factory);
	}

	/**
	 * Constructor which allows new instances to be created using a lambda
	 */
	public DogArray( int initialMaxSize, Factory factory ) {
		super((Class)factory.newInstance().getClass());
		init(initialMaxSize, factory);
	}

	/**
	 * Data structure initialization is done here so that child classes can declay initialization until they are ready
	 */
	protected void init( int initialMaxSize, Factory factory ) {
		this.size = 0;
		this.factory = factory;
		if (this.reset == null)
			this.reset = new DProcess.DoNothing<>();

		data = (T[])Array.newInstance(type, initialMaxSize);

		if (factory != null) {
			for (int i = 0; i < initialMaxSize; i++) {
				try {
					data[i] = createInstance();
				} catch (RuntimeException e) {
					throw new RuntimeException("declareInstances is true, but createInstance() can't create a new instance.  Maybe override createInstance()?");
				}
			}
		}
	}

	/**
	 * Returns a wrapper around FastQueue that allows it to act as a read only list.
	 * There is little overhead in using this interface.
	 *
	 * NOTE: The same instead of a list is returned each time.  Be careful when writing
	 * concurrent code and create a copy.
	 *
	 * @return List wrapper.
	 */
	@Override
	public List toList() {
		return list;
	}

	/**
	 * Removes the indexes from the array. This is done by swapping removed elements with the last element. O(N) copies.
	 *
	 * @param indexes Index of elements which are to be removed. This will be modified
	 * @param fromIndex the index of the first element, inclusive, to be sorted
	 * @param toIndex the index of the last element, exclusive, to be sorted
	 * @param workSpace Optional internal workspace. Can be set to null.
	 */
	public void remove( int[] indexes, int fromIndex, int toIndex, @Nullable List workSpace ) {
		if (toIndex <= fromIndex)
			return;
		if (workSpace == null) {
			workSpace = new ArrayList<>();
		} else {
			workSpace.clear();
		}
		// sort indexes from lowest to highest
		Arrays.sort(indexes, fromIndex, toIndex);

		// the next index wihch should be skipped
		int target = indexes[fromIndex];
		// how many indexes have been removed so far
		int count = 0;
		for (int i = indexes[fromIndex]; i < size; i++) {
			if (i == target) {
				workSpace.add(data[i]);
				count++;
				if (count < toIndex - fromIndex) {
					target = indexes[fromIndex + count];
				} else {
					target = -1;
				}
			} else {
				data[i - count] = data[i];
			}
		}

		// push removed objects to the end
		for (int i = 0; i < workSpace.size(); i++) {
			data[size - i - 1] = workSpace.get(i);
		}
		size -= workSpace.size();
	}

	/**
	 * Shrinks the size of the array by one and returns the element stored at the former last element.
	 *
	 * @return The last element in the list that was removed.
	 */
	public T removeTail() {
		if (size > 0) {
			size--;
			return data[size];
		} else
			throw new IllegalArgumentException("Size is already zero");
	}

	public DogArray reset() {
		size = 0;
		return this;
	}

	/**
	 * Returns a new element of data.  If there are new data elements available then array will
	 * automatically grow.
	 *
	 * @return A new instance.
	 */
	public T grow() {
		if (size < data.length) {
			T ret = data[size++];
			reset.process(ret);
			return ret;
		} else {
			reserve((data.length + 1)*2);
			return data[size++];
		}
	}

	/**
	 * Grows the array and adds all the items in list. Values are copied using the provided function
	 */
	public  void copyAll( List list, Set setter ) {
		reserve(size() + list.size());
		for (int i = 0; i < list.size(); i++) {
			T dst = grow();
			setter.set(list.get(i), dst);
		}
	}

	/**
	 * Removes an element from the array and preserves the order of all elements. This is done by shifting elements
	 * in the array down one and placing the removed element at the old end of the list. O(N) runtime.
	 *
	 * @param index Index of the element being removed
	 * @return The object removed.
	 */
	@Override
	public T remove( int index ) {
		T removed = data[index];
		for (int i = index + 1; i < size; i++) {
			data[i - 1] = data[i];
		}
		data[size - 1] = removed;
		size--;
		return removed;
	}

	/**
	 * Searches for and removes the 'target' from the list. Returns true if the target was found. If false
	 * then the target was never found and no change has been made. This is an O(N) operation.
	 *
	 * @param target Object to remove from the list
	 * @return true if the target was found and removed
	 */
	public boolean remove( T target ) {
		int index = indexOf(target);
		if (index < 0)
			return false;
		remove(index);
		return true;
	}

	/**
	 * Removes the specified index from the array by swapping it with last element. Does not preserve order
	 * but has a runtime of O(1).
	 *
	 * @param index The index to be removed.
	 * @return The removed object
	 */
	@Override
	public T removeSwap( int index ) {
		T removed = data[index];
		data[index] = data[size - 1];
		data[size - 1] = removed;
		size--;
		return removed;
	}

	/**
	 * Ensures that the internal array has at least `length` elements. If it does not then a new internal array
	 * is created with the specified length and elements from the old are copied into the new. The `size` does
	 * not change.
	 *
	 * @param length Requested minimum internal array length
	 */
	public DogArray reserve( int length ) {
		// now need to grow since it is already larger
		if (this.data.length >= length)
			return this;

		T[] data = (T[])Array.newInstance(type, length);
		System.arraycopy(this.data, 0, data, 0, this.data.length);

		if (factory != null) {
			for (int i = this.data.length; i < length; i++) {
				data[i] = createInstance();
			}
		}
		this.data = data;
		return this;
	}

	/**
	 * Ensures that the reserve is at lease the current {@link #size} plus the specified amount. This is
	 * exactly the same as doing `reserve(size + amount)`
	 *
	 * @param amount How much you wish the ensure the size is increased by
	 */
	public void reserveIncrease( int amount ) {
		reserve(size + amount);
	}

	/**
	 * 

* Resize with a configuration operator. Equivalent to calling {@link #reserve} and this.size = N, then * applying the 'configure' operator to each new element. *

* * NOTE: The 'reset' operator is applied before the 'configure' operator. * * @param length The new size of the array * @param configure Operator that the "new" element is passed in to. */ public DogArray resize( int length, DProcess configure ) { reserve(length); for (int i = size; i < length; i++) { reset.process(data[i]); configure.process(data[i]); } this.size = length; return this; } /** * Resize with a configuration operator. Equivalent to calling {@link #reserve} and this.size = N, then * applying the 'configure' operator to each new element. * * NOTE: The 'reset' operator is applied before the 'configure' operator. * * @param length The new size of the array * @param configure Operator that the "new" element is passed in to along with the index of the element. */ public DogArray resize( int length, DProcessIdx configure ) { reserve(length); for (int i = size; i < length; i++) { reset.process(data[i]); configure.process(i, data[i]); } this.size = length; return this; } /** * Changes the size to the specified length. Equivalent to calling {@link #reserve} and this.size = N. * * All new elements will be passed in to {@link #reset}. * * @param newSize New array size */ public DogArray resize( int newSize ) { reserve(newSize); for (int i = size; i < newSize; i++) { reset.process(data[i]); } this.size = newSize; return this; } /** * Convenience functions that calls {@link #reset} first before {@link #resize}. * * @param newSize New array size */ @Deprecated public void resetResize( int newSize ) { reset(); resize(newSize); } /** * Convenience functions that calls {@link #reset} first before {@link #resize}, then applies the * configure function for each element.. * * @param newSize New array size * @param configure Operator that the "new" element is passed in to along with the index of the element. */ @Deprecated public void resetResize( int newSize, DProcessIdx configure ) { reset(); resize(newSize, configure); } /** * Randomly shuffles elements in the list. O(N) complexity. * * @param rand random seed. */ public void shuffle( Random rand ) { shuffle(rand, size); } /** * Shuffle where it will only shuffle up to the specified number of elements. This is useful * when you want to randomly select up to N elements in the list. When shuffling, The first * i < N elements is randomly selected out from an element from i+1 to N-1. * * @param numShuffle The maximum number of elements that will be shuffled * @param rand random seed. */ public void shuffle( Random rand, int numShuffle ) { int N = Math.min(numShuffle, size); for (int i = 0; i < N; i++) { int selected = rand.nextInt(size - i); T tmp = data[selected]; data[selected] = data[size - i - 1]; data[size - i - 1] = tmp; } } /** * Creates a new instance of elements stored in this array */ protected T createInstance() { T instance = factory.newInstance(); initialize.process(instance); reset.process(instance); return instance; } public List copyIntoList( List ret ) { if (ret == null) ret = new ArrayList<>(size); for (int i = 0; i < size; i++) { ret.add(data[i]); } return ret; } /** * Checks to see if the object is in the unused list. */ public boolean isUnused( T object ) { final T[] data = this.data; for (int i = size; i < data.length; i++) { if (data[i] == object) return true; } return false; } // -------- These are only around so that it can be a java bean public T[] getData() { return data; } public void setData( T[] data ) { this.data = data; } public int getSize() { return size; } public void setSize( int size ) { this.size = size; } public final boolean isDeclare() { return factory != null; } public Class getType() { return type; } public interface Set { void set( S src, D dst ); } }