using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace VocaluxeLib.Utils.Combinatorics
{
///
/// Calculates all combinations (without repetition) of a given input sequence
/// Example: {A B C}, select 2 --> {A B}, {A C}, {B C}
///
/// Type of the input elements
public class CCombinations : IEnumerable>
{
private readonly List _Values;
private readonly int _NumSelected;
private readonly int _Count;
///
/// Construct combinations for the input sequence when selecting numSelected elements
/// The values should not contain duplicates as this will result in duplicates in the output
///
/// Input sequence
/// Number of elements to select
public CCombinations(List values, int numSelected)
{
_Values = values;
_NumSelected = numSelected;
// This also acts as a parameter check
_Count = Count(_Values.Count, _NumSelected);
}
///
/// Calculates how many combinations are possible when k out of n elements are selected
///
/// Number of elements
/// Number of selected elements
///
public static int Count(int n, int k)
{
if (n < 0 || k < 0)
{
throw new ArgumentException("Parameters must not be negative");
}
if (n < k)
{
return 0;
}
// Try to optimize because (n k) = (n n-k)
if (n - k < k)
{
k = n - k;
}
if (k == 0) // or k == n but both are the same after the optimization above
{
return 1;
}
long result = n;
for (long i = 1; i < k; i++)
{
checked
{
result = result * (n - i) / (i + 1);
}
}
return (int)result;
}
///
/// Gets the number of combinations that are generated
///
///
public int Count()
{
return _Count;
}
///
/// Gets all possible combinations as a list
///
///
public List> GetAll()
{
var result = new List>(Count());
result.AddRange(this);
return result;
}
public IEnumerator> GetEnumerator()
{
return new CEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class CEnumerator : IEnumerator>
{
private readonly int _N;
private readonly int _K;
private readonly CCombinations _Parent;
private int[] _CurSet;
public CEnumerator(CCombinations parent)
{
_N = parent._Values.Count;
_K = parent._NumSelected;
_Parent = parent;
Debug.Assert(_N >= 0 && _K >= 0);
}
public void Dispose() { }
public bool MoveNext()
{
if (_N < _K)
{
return false;
}
if (_CurSet == null)
{
_CurSet = new int[_K];
for (var i = 0; i < _K; i++)
{
_CurSet[i] = i;
}
return true;
}
// Check if we reached the end
if (_K == 0 || _CurSet[0] == _N - _K)
{
return false;
}
// Search for element to increment
var idx = _K - 1;
while (idx > 0 && _CurSet[idx] == _N - _K + idx)
{
idx--;
}
_CurSet[idx]++;
// Increment all elements "right" of this to 1+their left neighbour
for (var j = idx; j < _K - 1; j++)
{
_CurSet[j + 1] = _CurSet[j] + 1;
}
return true;
}
public void Reset()
{
_CurSet = null;
}
public List Current
{
get
{
var result = new List(_K);
result.AddRange(_CurSet.Select(i => _Parent._Values[i]));
return result;
}
}
object IEnumerator.Current
{
get { return Current; }
}
}
}
}