require'more_math/ranking_common'moduleMoreMathclassPermutationincludeEnumerableincludeComparableincludeRankingCommon# Creates a new Permutation instance of <code>size</code># (and ranked with <code>rank</code>).definitialize(size,rank=0)@size,self.rank=size,rank@last=factorial(size)-1end# Creates a new Permutation instance from the Array# <code>indices</code>, that should consist of a permutation of Fixnums# in the range of <code>0</code> and <code>indices.size - 1</code>. This is# for example the result of a call to the Permutation#value method.defself.from_value(indices)obj=new(indices.size)obj.instance_evaldoself.rank=rank_indices(indices)endobjend# Creates a new Permutation instance from the Array of Arrays# <code>cycles</code>. This is for example the result of a# call to the Permutation#cycles method .defself.from_cycles(cycles,max=0)indices=Array.new(max)cycles.eachdo|cycle|cycle.empty?andnextforiin0...cycle.sizeindices[cycle[i-1]]=cycle[i]endendindices.each_with_index{|r,i|rorindices[i]=i}from_value(indices)end# A permutation instance of size collection.size is created with# collection as the default Permutation#project data object. A# collection should respond to size, [], and []=. The Permutation# instance will default to rank 0 if none is given.defself.for(collection,rank=0)perm=new(collection.size,rank)perm.instance_variable_set(:@collection,collection)permend# Assigns <code>m</code> to the rank attribute of this Permutation# instance. That implies that the indices produced by a call to the# Permutation#value method of this instance is the permutation ranked with# this new <code>rank</code>.defrank=(m)@rank=m%factorial(size)end# Returns the indices in the range of 0 to Permutation#size - 1# of this permutation that is ranked with Permutation#rank.## <b>Example:</b># perm = Permutation.new(6, 312)# # => #<Permutation:0x6ae34 @last=719, @rank=312, @size=6># perm.value# # => [2, 4, 0, 1, 3, 5]defvalueunrank_indices(@rank)end# Returns the projection of this instance's Permutation#value# into the <code>data</code> object that should respond to# the #[] method. If this Permutation inbstance was created# with Permutation.for the collection used to create# it is used as a data object.## <b>Example:</b># perm = Permutation.new(6, 312)# # => #<Permutation:0x6ae34 @last=719, @rank=312, @size=6># perm.project("abcdef")# # => "ceabdf"defproject(data=@collection)dataorraiseArgumentError,"a collection is required to project"raiseArgumentError,"data size is != #{size}!"ifdata.size!=sizeprojection=data.clonevalue.each_with_index{|i,j|projection[j]=data[i]}projectionend# Compares to Permutation instances according to their Permutation#size# and the Permutation#rank.## The mixed in methods from the Comparable module rely on this method.def<=>(other)size<=>other.size.zero?||rank<=>other.rankend# Returns true if this Permutation instance and the other have the same# value, that is both Permutation instances have the same Permutation#size# and the same Permutation#rank.defeql?(other)self.class==other.class&&size==other.size&&rank==other.rankendalias==eql?# Computes a unique hash value for this Permutation instance.defhashsize.hash^rank.hashend# Switchtes this Permutation instance to the inverted permutation.# (See Permutation#compose for an example.)definvert!indices=unrank_indices(rank)inverted=Array.new(size)foriin0...sizeinverted[indices[i]]=iendself.rank=rank_indices(inverted)selfend# Returns the inverted Permutation of this Permutation instance.# (See Permutation#compose for an example.)definvertclone.invert!endalias-@invert# Compose this Permutation instance and the other to# a new Permutation. Note that a permutation# composed with it's inverted permutation yields# the identity permutation, the permutation with rank 0.## <b>Example:</b># p1 = Permutation.new(5, 42)# # => #<Permutation:0x75370 @last=119, @rank=42, @size=5># p2 = p1.invert# # => #<Permutation:0x653d0 @last=119, @rank=51, @size=5># p1.compose(p2)# => #<Permutation:0x639a4 @last=119, @rank=0, @size=5># Or a little nicer to look at:# p1 * -p1# # => #<Permutation:0x62004 @last=119, @rank=0, @size=5>defcompose(other)size==other.sizeorraiseArgumentError,"permutations of unequal sizes cannot be composed!"indices=self.valuecomposed=other.value.map{|i|indices[i]}klon=cloneklon.rank=rank_indices(composed)klonendalias*compose# Returns the cycles representation of this Permutation instance.# The return value of this method can be used to create a# new Permutation instance with the Permutation.from_cycles method.## <b>Example:</b># perm = Permutation.new(7, 23)# # => #<Permutation:0x58541c @last=5039, @rank=23, @size=7># perm.cycles# # => [[3, 6], [4, 5]]defcyclesperm=valueresult=[[]]seen={}current=nilloopdocurrentorcurrent=perm.find{|x|!seen[x]}breakunlesscurrentifseen[current]current=nilresult<<[]elseseen[current]=trueresult[-1]<<currentcurrent=perm[current]endendresult.popresult.select{|c|c.size>1}.mapdo|c|min_index=c.index(c.min)c[min_index..-1]+c[0...min_index]endend# Returns the signum of this Permutation instance.# It's -1 if this permutation is odd and 1 if it's# an even permutation.## A permutation is odd if it can be represented by an odd number of# transpositions (cycles of length 2), or even if it can be represented of# an even number of transpositions.defsignums=1cycles.eachdo|c|c.size%2==0ands*=-1endsendaliassgnsignum# Returns true if this permutation is even, false otherwise.defeven?signum==1end# Returns true if this permutation is odd, false otherwise.defodd?signum==-1endprivate@@fcache=[1]deffactorial(n)@@fcache.size.upto(n){|i|@@fcache[i]=i*@@fcache[i-1]}@@fcache[n]enddefrank_indices(p)result=0foriin0...sizeresult+=p[i]*factorial(size-i-1)forjin(i+1)...sizep[j]-=1ifp[j]>p[i]endendresultenddefunrank_indices(m)result=Array.new(size,0)foriin0...sizef=factorial(i)x=m%(f*(i+1))m-=xx/=fresult[size-i-1]=xx-=1forjin(size-i)...sizeresult[j]+=1ifresult[j]>xendendresultendendend