class MoreMath::Permutation

def self.for(collection, rank = 0)

instance will default to rank 0 if none is given.
collection should respond to size, [], and []=. The Permutation
collection as the default Permutation#project data object. A
A permutation instance of size collection.size is created with
def self.for(collection, rank = 0)
  perm = new(collection.size, rank)
  perm.instance_variable_set(:@collection, collection)
  perm
end

def self.from_cycles(cycles, max = 0)

call to the Permutation#cycles method .
cycles. This is for example the result of a
Creates a new Permutation instance from the Array of Arrays
def self.from_cycles(cycles, max = 0)
  indices = Array.new(max)
  cycles.each do |cycle|
    cycle.empty? and next
    for i in 0...cycle.size
      indices[ cycle[i - 1] ] = cycle[i]
    end
  end
  indices.each_with_index { |r, i| r or indices[i] = i }
  from_value(indices)
end

def self.from_value(indices)

for example the result of a call to the Permutation#value method.
in the range of 0 and indices.size - 1. This is
indices, that should consist of a permutation of Fixnums
Creates a new Permutation instance from the Array
def self.from_value(indices)
  obj = new(indices.size)
  obj.instance_eval do
    self.rank = rank_indices(indices)
  end
  obj
end

def <=>(other)

The mixed in methods from the Comparable module rely on this method.

and the Permutation#rank.
Compares to Permutation instances according to their Permutation#size
def <=>(other)
  size <=> other.size.zero? || rank <=> other.rank
end

def compose(other)

# => #
p1 * -p1
Or a little nicer to look at:
=> #
p1.compose(p2)
# => #
p2 = p1.invert
# => #
p1 = Permutation.new(5, 42)
Example:

the identity permutation, the permutation with rank 0.
composed with it's inverted permutation yields
a new Permutation. Note that a permutation
Compose this Permutation instance and the other to
def compose(other)
  size == other.size or raise ArgumentError,
    "permutations of unequal sizes cannot be composed!"
  indices = self.value
  composed = other.value.map { |i| indices[i] }
  klon = clone
  klon.rank = rank_indices(composed)
  klon
end

def cycles

# => [[3, 6], [4, 5]]
perm.cycles
# => #
perm = Permutation.new(7, 23)
Example:

new Permutation instance with the Permutation.from_cycles method.
The return value of this method can be used to create a
Returns the cycles representation of this Permutation instance.
def cycles
  perm = value
  result = [[]]
  seen = {}
  current = nil
  loop do
    current or current = perm.find { |x| !seen[x] }
    break unless current
    if seen[current]
      current = nil
      result << []
    else
      seen[current] = true
      result[-1] << current
      current = perm[current]
    end
  end
  result.pop
  result.select { |c| c.size > 1 }.map do |c|
    min_index = c.index(c.min)
    c[min_index..-1] + c[0...min_index]
  end
end

def eql?(other)

and the same Permutation#rank.
value, that is both Permutation instances have the same Permutation#size
Returns true if this Permutation instance and the other have the same
def eql?(other)
  self.class == other.class && size == other.size && rank == other.rank
end

def even?

Returns true if this permutation is even, false otherwise.
def even?
  signum == 1
end

def factorial(n)

def factorial(n)
  @@fcache.size.upto(n) { |i| @@fcache[i] =  i * @@fcache[i - 1] }
  @@fcache[n]
end

def hash

Computes a unique hash value for this Permutation instance.
def hash
  size.hash ^ rank.hash
end

def initialize(size, rank = 0)

(and ranked with rank).
Creates a new Permutation instance of size
def initialize(size, rank = 0)
  @size, self.rank = size, rank
  @last = factorial(size) - 1
end

def invert

(See Permutation#compose for an example.)
Returns the inverted Permutation of this Permutation instance.
def invert
  clone.invert!
end

def invert!

(See Permutation#compose for an example.)
Switchtes this Permutation instance to the inverted permutation.
def invert!
  indices = unrank_indices(rank)
  inverted = Array.new(size)
  for i in 0...size
    inverted[indices[i]] = i
  end
  self.rank = rank_indices(inverted)
  self
end

def odd?

Returns true if this permutation is odd, false otherwise.
def odd?
  signum == -1
end

def project(data = @collection)

# => "ceabdf"
perm.project("abcdef")
# => #
perm = Permutation.new(6, 312)
Example:

it is used as a data object.
with Permutation.for the collection used to create
the #[] method. If this Permutation inbstance was created
into the data object that should respond to
Returns the projection of this instance's Permutation#value
def project(data = @collection)
  data or raise ArgumentError, "a collection is required to project"
  raise ArgumentError, "data size is != #{size}!" if data.size != size
  projection = data.clone
  value.each_with_index { |i, j| projection[j] = data[i] }
  projection
end

def rank=(m)

this new rank.
Permutation#value method of this instance is the permutation ranked with
instance. That implies that the indices produced by a call to the
Assigns m to the rank attribute of this Permutation
def rank=(m)
  @rank = m % factorial(size)
end

def rank_indices(p)

def rank_indices(p)
  result = 0
  for i in 0...size
    result += p[i] * factorial(size - i - 1)
    for j in (i + 1)...size
      p[j] -= 1 if p[j] > p[i]
    end
  end
  result
end

def signum

an even number of transpositions.
transpositions (cycles of length 2), or even if it can be represented of
A permutation is odd if it can be represented by an odd number of

an even permutation.
It's -1 if this permutation is odd and 1 if it's
Returns the signum of this Permutation instance.
def signum
  s = 1
  cycles.each do |c|
    c.size % 2 == 0 and s *= -1
  end
  s
end

def unrank_indices(m)

def unrank_indices(m)
  result = Array.new(size, 0)
  for i in 0...size
    f = factorial(i)
    x = m % (f * (i + 1))
    m -= x
    x /= f
    result[size - i - 1] = x
    x -= 1
    for j in (size - i)...size
      result[j] += 1 if result[j] > x
    end
  end
  result
end

def value

# => [2, 4, 0, 1, 3, 5]
perm.value
# => #
perm = Permutation.new(6, 312)
Example:

of this permutation that is ranked with Permutation#rank.
Returns the indices in the range of 0 to Permutation#size - 1
def value
  unrank_indices(@rank)
end