com.github.tell.mathematics.combinatorics.Combination.java Source code

Java tutorial

Introduction

Here is the source code for com.github.tell.mathematics.combinatorics.Combination.java

Source

/**
 * @author Tadanori TERUYA <tadanori.teruya@gmail.com> (2012)
 * @license: The MIT license <http://opensource.org/licenses/MIT>
 */
/*
 * Copyright (c) 2012 Tadanori TERUYA (tell) <tadanori.teruya@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * @license: The MIT license <http://opensource.org/licenses/MIT>
 */
package com.github.tell.mathematics.combinatorics;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.NoSuchElementException;

/**
 * @author Tadanori TERUYA &lt;tadanori.teruya@gmail.com&gt; (2012)
 */
public final class Combination<T> implements Iterable<T[]> {

    private static final Logger logger = LoggerFactory.getLogger(Combination.class);

    private T[] elements;
    private int numberOfSelection;

    private Class<T> cls;

    public Combination(final T[] elements, final int numberOfSelection) {
        assert elements.length >= numberOfSelection;

        this.elements = elements;
        this.numberOfSelection = numberOfSelection;

        //noinspection unchecked
        cls = (Class<T>) this.elements.getClass().getComponentType();
    }

    public T[][] makeCombination() {
        if (numberOfSelection == 0) {
            //noinspection unchecked
            return (T[][]) Array.newInstance(cls, 1, 0);

        } else if (numberOfSelection == 1) {
            @SuppressWarnings("unchecked")
            final T[][] result = (T[][]) Array.newInstance(cls, elements.length, 0);
            assert result.length == elements.length : String.format("result = %s, numberOfSelection = %s",
                    Arrays.deepToString(result), numberOfSelection);
            for (int i = 0; i < elements.length; i++) {
                result[i] = Arrays.copyOfRange(elements, i, i + 1);
            }
            return result;

        } else if (numberOfSelection == elements.length) {
            @SuppressWarnings("unchecked")
            final T[][] result = (T[][]) Array.newInstance(cls, 1, 0);
            result[0] = ArrayUtils.clone(elements);
            return result;

        } else if (numberOfSelection == elements.length - 1) {
            @SuppressWarnings("unchecked")
            final T[][] result = (T[][]) Array.newInstance(cls, elements.length, 0);
            for (int i = elements.length - 1; i >= 0; i--) {
                final int j = elements.length - 1 - i;
                result[j] = Arrays.copyOf(elements, i);
                result[j] = ArrayUtils.addAll(result[j], ArrayUtils.subarray(elements, i + 1, elements.length));
                logger.trace("generated array = {}", Arrays.deepToString(result[j]));
            }
            return result;

        } else {
            final T[] partialElements = Arrays.copyOf(elements, elements.length - 1);

            logger.trace("partialElements = {}, numberOfSelection = {}", Arrays.deepToString(partialElements),
                    numberOfSelection);

            final Combination<T> leftHandSideGenerator = new Combination<T>(partialElements, numberOfSelection);
            final T[][] leftCombination = leftHandSideGenerator.makeCombination();

            final Combination<T> rightHandSideGenerator = new Combination<T>(partialElements,
                    numberOfSelection - 1);
            final T[][] rightCombination = rightHandSideGenerator.makeCombination();
            for (int i = 0; i < rightCombination.length; i++) {
                rightCombination[i] = ArrayUtils.add(rightCombination[i], elements[elements.length - 1]);
            }

            //noinspection unchecked
            return ArrayUtils.addAll(leftCombination, rightCombination);
        }
    }

    public class NoSelectionIterator implements java.util.Iterator<T[]> {

        private final Class<T> cls;
        private boolean hasNext;

        private NoSelectionIterator(final Class<T> cls) {
            this.cls = cls;
            hasNext = true;
        }

        public boolean hasNext() {
            return hasNext;
        }

        public T[] next() {
            if (hasNext()) {
                hasNext = false;
                //noinspection unchecked
                return (T[]) Array.newInstance(cls, 0);
            } else {
                throw new NoSuchElementException("no more elements");
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove operation is not supported");
        }
    }

    public class TrivialSelectionIterator implements java.util.Iterator<T[]> {

        private final T[] elements;
        private boolean hasNext;

        private TrivialSelectionIterator(final T[] elements) {
            this.elements = elements;
            hasNext = true;
        }

        public boolean hasNext() {
            return hasNext;
        }

        public T[] next() {
            if (hasNext()) {
                hasNext = false;
                return ArrayUtils.clone(elements);
            } else {
                throw new NoSuchElementException("no more elements");
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove operation is not supported");
        }
    }

    public class SingleElementSelectionIterator implements java.util.Iterator<T[]> {

        private final T[][] currentBuffer;
        private final Class<T> cls;
        private int currentIndex;

        private SingleElementSelectionIterator(final T[] elements, final Class<T> cls) {
            this.cls = cls;

            //noinspection unchecked
            currentBuffer = (T[][]) Array.newInstance(this.cls, elements.length, 0);
            for (int i = 0; i < elements.length; i++) {
                currentBuffer[i] = Arrays.copyOfRange(elements, i, i + 1);
            }
            currentIndex = 0;
        }

        public boolean hasNext() {
            return currentIndex < currentBuffer.length;
        }

        public T[] next() {
            if (hasNext()) {
                return currentBuffer[currentIndex++];
            } else {
                throw new NoSuchElementException("no more elements");
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove operation is not supported");
        }
    }

    public class SingleElementRemovedSelectionIterator implements java.util.Iterator<T[]> {

        private T[][] currentBuffer;
        private Class<T> cls;
        private int currentIndex;

        private SingleElementRemovedSelectionIterator(final T[] elements, final Class<T> cls) {
            assert elements.length > 0;

            this.cls = cls;

            //noinspection unchecked
            currentBuffer = (T[][]) Array.newInstance(this.cls, elements.length, 0);
            for (int i = elements.length - 1; i >= 0; i--) {
                final int j = elements.length - 1 - i;
                currentBuffer[j] = Arrays.copyOf(elements, i);
                currentBuffer[j] = ArrayUtils.addAll(currentBuffer[j],
                        ArrayUtils.subarray(elements, i + 1, elements.length));
            }
            currentIndex = 0;
        }

        public boolean hasNext() {
            return currentIndex < currentBuffer.length;
        }

        public T[] next() {
            if (hasNext()) {
                return currentBuffer[currentIndex++];
            } else {
                throw new NoSuchElementException("no more elements");
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove operation is not supported");
        }
    }

    public class Iterator implements java.util.Iterator<T[]> {

        private T[] elements;
        private int numberOfSelection = -1;

        private Class<T> cls;

        private java.util.Iterator<T[]> recursiveIterator;
        private T pushedTailElement = null;
        private boolean hasNextCombinationWithTail = false;

        private Iterator(final T[] elements, final int numberOfSelection, final Class<T> cls) {
            assert elements.length >= numberOfSelection;

            this.elements = ArrayUtils.clone(elements);
            this.numberOfSelection = numberOfSelection;
            this.cls = cls;

            logger.trace("input elements = {}", Arrays.deepToString(this.elements));
            logger.trace("# of selection = {}", this.numberOfSelection);

            if (this.numberOfSelection == 0) {
                recursiveIterator = new NoSelectionIterator(cls);
                hasNextCombinationWithTail = false;

            } else if (this.numberOfSelection == this.elements.length) {
                recursiveIterator = new TrivialSelectionIterator(this.elements);
                hasNextCombinationWithTail = false;

            } else if (this.numberOfSelection == 1) {
                recursiveIterator = new SingleElementSelectionIterator(this.elements, this.cls);
                hasNextCombinationWithTail = false;

            } else if (this.numberOfSelection == this.elements.length - 1) {
                recursiveIterator = new SingleElementRemovedSelectionIterator(this.elements, this.cls);
                hasNextCombinationWithTail = false;

            } else {
                pushedTailElement = this.elements[this.elements.length - 1];
                this.elements = ArrayUtils.remove(this.elements, this.elements.length - 1);
                recursiveIterator = new Iterator(this.elements, this.numberOfSelection, this.cls);
                hasNextCombinationWithTail = true;
            }
        }

        public boolean hasNext() {
            return hasNextCombinationWithTail || recursiveIterator.hasNext();
        }

        public T[] next() {
            if (hasNext()) {
                if (!recursiveIterator.hasNext()) {
                    assert hasNextCombinationWithTail;
                    assert pushedTailElement != null;

                    recursiveIterator = new Iterator(elements, numberOfSelection - 1, cls);
                    hasNextCombinationWithTail = false;
                }

                final T[] result = recursiveIterator.next();
                if (hasNextCombinationWithTail || pushedTailElement == null) {
                    return result;
                } else {
                    return ArrayUtils.add(result, pushedTailElement);
                }
            } else {
                throw new NoSuchElementException("no more elements");
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove operation is not supported");
        }
    }

    public Iterator iterator() {
        return new Iterator(elements, numberOfSelection, cls);
    }
}