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

com.helger.commons.math.CombinationGenerator Maven / Gradle / Ivy

There is a newer version: 11.1.10
Show newest version
/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.commons.math;

import java.math.BigInteger;
import java.util.Collection;
import java.util.NoSuchElementException;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;

import com.helger.commons.CGlobal;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.Nonempty;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.collection.iterate.IIterableIterator;
import com.helger.commons.lang.GenericReflection;

/**
 * Utility class for generating all possible combinations of elements for a
 * specified number of available slots. Duplicates in the passed elements will
 * be treated as different individuals and hence deliver duplicate result
 * solutions. This generator will only return complete result sets filling all
 * slots.
 *
 * @author Boris Gregorcic
 * @author Philip Helger
 * @param 
 *        Element type to be combined
 */
public class CombinationGenerator  implements IIterableIterator >
{
  private final DATATYPE [] m_aElements;
  private final int [] m_aIndexResult;
  private final BigInteger m_aTotalCombinations;
  private BigInteger m_aCombinationsLeft;
  private final boolean m_bUseLong;
  private final long m_nTotalCombinations;
  private long m_nCombinationsLeft;

  /**
   * Ctor
   *
   * @param aElements
   *        the elements to fill into the slots for creating all combinations
   *        (must not be empty!)
   * @param nSlotCount
   *        the number of slots to use (must not be greater than the element
   *        count!)
   */
  public CombinationGenerator (@Nonnull @Nonempty final ICommonsList  aElements, @Nonnegative final int nSlotCount)
  {
    ValueEnforcer.notEmpty (aElements, "Elements");
    ValueEnforcer.isBetweenInclusive (nSlotCount, "SlotCount", 0, aElements.size ());

    m_aElements = GenericReflection.uncheckedCast (aElements.toArray ());
    m_aIndexResult = new int [nSlotCount];
    final BigInteger aElementFactorial = FactorialHelper.getAnyFactorialLinear (m_aElements.length);
    final BigInteger aSlotFactorial = FactorialHelper.getAnyFactorialLinear (nSlotCount);
    final BigInteger aOverflowFactorial = FactorialHelper.getAnyFactorialLinear (m_aElements.length - nSlotCount);
    m_aTotalCombinations = aElementFactorial.divide (aSlotFactorial.multiply (aOverflowFactorial));
    // Can we use the fallback to long? Is much faster than using BigInteger
    m_bUseLong = m_aTotalCombinations.compareTo (CGlobal.BIGINT_MAX_LONG) < 0;
    m_nTotalCombinations = m_bUseLong ? m_aTotalCombinations.longValue () : CGlobal.ILLEGAL_ULONG;
    reset ();
  }

  /**
   * Reset the generator
   */
  public final void reset ()
  {
    for (int i = 0; i < m_aIndexResult.length; i++)
      m_aIndexResult[i] = i;
    m_aCombinationsLeft = m_aTotalCombinations;
    m_nCombinationsLeft = m_nTotalCombinations;
  }

  /**
   * @return number of combinations not yet generated
   */
  @Nonnull
  public BigInteger getCombinationsLeft ()
  {
    return m_bUseLong ? BigInteger.valueOf (m_nCombinationsLeft) : m_aCombinationsLeft;
  }

  /**
   * @return whether or not there are more combinations left
   */
  public boolean hasNext ()
  {
    return m_bUseLong ? m_nCombinationsLeft > 0 : m_aCombinationsLeft.compareTo (BigInteger.ZERO) > 0;
  }

  /**
   * @return total number of combinations
   */
  @Nonnull
  public BigInteger getTotalCombinations ()
  {
    return m_aTotalCombinations;
  }

  /**
   * Generate next combination (algorithm from Rosen p. 286)
   *
   * @return the next combination as list of the size specified for slots filled
   *         with elements from the original list
   */
  @Nonnull
  @ReturnsMutableCopy
  public ICommonsList  next ()
  {
    if (!hasNext ())
      throw new NoSuchElementException ();

    // Not for the very first item, as the first item is the original order
    final boolean bFirstItem = m_bUseLong ? m_nCombinationsLeft == m_nTotalCombinations : m_aCombinationsLeft.equals (m_aTotalCombinations);
    if (!bFirstItem)
    {
      final int nElementCount = m_aElements.length;
      final int nSlotCount = m_aIndexResult.length;

      int i = nSlotCount - 1;
      while (m_aIndexResult[i] == (nElementCount - nSlotCount + i))
      {
        i--;
      }
      m_aIndexResult[i]++;
      final int nIndexResultI = m_aIndexResult[i];
      for (int j = i + 1; j < nSlotCount; j++)
      {
        m_aIndexResult[j] = nIndexResultI + j - i;
      }
    }

    // One combination less
    if (m_bUseLong)
      m_nCombinationsLeft--;
    else
      m_aCombinationsLeft = m_aCombinationsLeft.subtract (BigInteger.ONE);

    // Build result list
    final ICommonsList  aResult = new CommonsArrayList <> (m_aIndexResult.length);
    for (final int nIndex : m_aIndexResult)
      aResult.add (m_aElements[nIndex]);
    return aResult;
  }

  /**
   * Get a list of all permutations of the input elements.
   *
   * @param 
   *        Element type to be combined
   * @param aInput
   *        Input list.
   * @param nSlotCount
   *        Slot count.
   * @return The list of all permutations. Beware: the resulting list may be
   *         quite large and may contain duplicates if the input list contains
   *         duplicate elements!
   */
  @Nonnull
  public static  ICommonsList > getAllPermutations (@Nonnull @Nonempty final ICommonsList  aInput,
                                                                                      @Nonnegative final int nSlotCount)
  {
    final ICommonsList > aResultList = new CommonsArrayList <> ();
    addAllPermutations (aInput, nSlotCount, aResultList);
    return aResultList;
  }

  /**
   * Fill a list with all permutations of the input elements.
   *
   * @param 
   *        Element type to be combined
   * @param aInput
   *        Input list.
   * @param nSlotCount
   *        Slot count.
   * @param aResultList
   *        The list to be filled with all permutations. Beware: this list may
   *        be quite large and may contain duplicates if the input list contains
   *        duplicate elements! Note: this list is not cleared before filling
   */
  public static  void addAllPermutations (@Nonnull @Nonempty final ICommonsList  aInput,
                                                    @Nonnegative final int nSlotCount,
                                                    @Nonnull final Collection > aResultList)
  {
    for (final ICommonsList  aPermutation : new CombinationGenerator <> (aInput, nSlotCount))
      aResultList.add (aPermutation);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy