I'm trying to write some code in Fortran which requires the re-ordering of an n-dimensional array. I thought the reshape intrinsic combined with the order argument should allow this, however I'm running into difficulties.
Consider the following minimal example
program test
implicit none
real, dimension(:,:,:,:,:), allocatable :: matA, matB
integer, parameter :: n1=3, n2=5, n3=7, n4=11, n5=13
integer :: i1, i2, i3, i4, i5
allocate(matA(n1,n2,n3,n4,n5)) !Source array
allocate(matB(n3,n2,n4,n1,n5)) !Reshaped array
!Populate matA
do i5=1, n5
do i4=1, n4
do i3=1, n3
do i2=1, n2
do i1=1, n1
matA(i1,i2,i3,i4,i5) = i1+i2*10+i3*100+i4*10000+i5*1000000
enddo
enddo
enddo
enddo
enddo
print*,"Ad1 : ",matA(:,1,1,1,1),shape(matA)
matB = reshape(matA, shape(matB), order = [3,2,4,1,5])
print*,"Bd4 : ",matB(1,1,1,:,1),shape(matB) !Leading dimension of A is the fourth dimension of B
end program test
I would expect this to result in
Ad1 : 1010111.00 1010112.00 1010113.00 3 5 7 11 13
Bd4 : 1010111.00 1010112.00 1010113.00 7 5 11 3 13
But instead I find:
Ad1 : 1010111.00 1010112.00 1010113.00 3 5 7 11 13
Bd4 : 1010111.00 1010442.00 1020123.00 7 5 11 3 13
I've tried this with gfortran (4.8.3 and 4.9) and ifort (11.0) and find the same results, so it's likely that I am simply misunderstanding something about how reshape works.
Can anybody shed any light on where I'm going wrong and how I can achieve my goal?
Because I also feel the behavior of order for multi-dimensional arrays is quite non-intuitive, I made some code comparison below to make the situation even clear (in addition to the already complete #francescalus' answer). First, in a simple case, reshape() with and without order gives the following:
mat = reshape( [1,2,3,4,5,6,7,8], [2,4] )
=> [ 1 3 5 7 ;
2 4 6 8 ]
mat = reshape( [1,2,3,4,5,6,7,8], [2,4], order=[2,1] )
=> [ 1 2 3 4 ;
5 6 7 8 ]
This example shows that without order the elements are filled in the usual column-major way, while with order=[2,1] the 2nd dimension runs faster and so the elements are filled row-wise. The key point here is that the order specifies which dimension of the LHS (rather than the source array) runs faster (as emphasized in the above answer).
Now we apply the same mechanism to higher-dimensional cases. First, reshape() of the 5-dimensional array without order
matB = reshape( matA, [n3,n2,n4,n1,n5] )
corresponds to the explicit loops
k = 0
do i5 = 1, n5 !! (5)-th dimension of LHS
do i1 = 1, n1 !! (4)
do i4 = 1, n4 !! (3)
do i2 = 1, n2 !! (2)
do i3 = 1, n3 !! (1)-st dimension of LHS
k = k + 1
matB( i3, i2, i4, i1, i5 ) = matA_seq( k )
enddo;enddo;enddo;enddo;enddo
where matA_seq is a sequential view of matA
real, pointer :: matA_seq(:)
matA_seq( 1 : n1*n2*n3*n4*n5 ) => matA(:,:,:,:,:)
Now attaching order=[3,2,4,1,5] to reshape(),
matB = reshape( matA, [n3,n2,n4,n1,n5], order = [3,2,4,1,5] )
then the order of DO-loops is changed such that
k = 0
do i5 = 1, n5 !! (5)-th dim of LHS
do i3 = 1, n3 !! (1)
do i1 = 1, n1 !! (4)
do i2 = 1, n2 !! (2)
do i4 = 1, n4 !! (3)-rd dim of LHS
k = k + 1
matB( i3, i2, i4, i1, i5 ) = matA_seq( k )
enddo;enddo;enddo;enddo;enddo
This means that the 3rd dimension of matB (and thus i4) runs fastest (which corresponds to the second line in the above Answer). But what is desired by OP is
k = 0
do i5 = 1, n5 !! (5)-th dim of LHS
do i4 = 1, n4 !! (3)
do i3 = 1, n3 !! (1)
do i2 = 1, n2 !! (2)
do i1 = 1, n1 !! (4)-th dim of LHS
k = k + 1
matB( i3, i2, i4, i1, i5 ) = matA_seq( k )
enddo;enddo;enddo;enddo;enddo
which corresponds to
matB = reshape( matA, [n3,n2,n4,n1,n5], order = [4,2,1,3,5] )
i.e., the final line of the francescalus' answer.
Hope this comparison further clarifies the situation...
When order= is specified in reshape the elements of the result taken with permuted subscript order correspond to the elements of the source array. That probably isn't entirely clear. The Fortran 2008 standard states this as (ignoring the part about pad=)
The elements of the result, taken in permuted subscript order ORDER (1), ..., ORDER (n), are those of SOURCE in normal array element order ..
What this means is that from your example with order=[3,2,4,1,5] there is the mapping to
matA(1,1,1,1,1), matA(2,1,1,1,1), matA(3,1,1,1,1), matA(1,2,1,1,1), ...
of
matB(1,1,1,1,1), matB(1,1,2,1,1), matB(1,1,3,1,1), matB(1,1,4,1,1), ...
with offset changing most rapidly in the third index of matB corresponding to most rapidly varying in the first of matA. The next fastest varying in matB being dimension 2, then 4, and so on.
So, it's the elements matB(1,1,1:3,1,1) that correspond the matA(:,1,1,1,1).
I've been explicit in the extent of that matB slice because you've a problem with the shape of matB: you want the shape of matB to be the inverse of the permutation given by the order= specifier.
You could write your example as
implicit none
integer, parameter :: n1=3, n2=5, n3=7, n4=11, n5=13
integer matA(n1,n2,n3,n4,n5)
integer matB(n4,n2,n1,n3,n5) ! Inverse of permutation (3 2 4 1 5)
integer i1, i2, i3, i4, i5
forall (i1=1:n1, i2=1:n2, i3=1:n3, i4=1:n4, i5=1:n5) &
matA(i1,i2,i3,i4,i5)=i1+i2*10+i3*100+i4*10000+i5*1000000
print*,"Ad1 : ",matA(:,1,1,1,1),shape(matA)
matB = reshape(matA, shape(matB), order = [3,2,4,1,5])
print*,"Bd3 : ",matB(1,1,:,1,1),shape(matB)
end
Alternatively, if it's the shape of matB that you want, then it's the order permutation that wants inverting:
matB = reshape(matA, shape(matB), order = [4,2,1,3,5])
At first glance, it may be natural to view the order relating to the dimensions of the source. However, the following may clarify: the result of the reshaping is the same regardless of the shape of source (what is used are the elements of the array in natural order); the order= value has size equal to that of the shape= value. For the first of these, if the source were, say [1,2,3,4,5,6] (recall how we construct rank-2 arrays), then order= could never have any effect (it would have to be [1]) if it were used on the source.
Related
I don't even know if something like this is possible, but:
Let us say we have three numbers:
A = 6
B = 7.5
C = 24
I would like to find a few evenly spaced common multiples of these numbers between 0 and 2.
So the requirement is: one_of_these_numbers / common_multiple = an_integer (or almost an integer with a particular tolerance)
For example, a good result would be [0.1 , 0.5 , 1 , 1.5]
I have no idea if this is possible, because one can not iterate through a range of floats, but is there a smart way to do it?
I am using python, but a solution could be represented in any language of your preference.
Thank you for your help!
While I was writing my question, I actually came up with an idea for the solution.
To find common divisors using code, we have to work with integers.
My solution is to multiply all numbers by a factor = 1, 10, 100, ...
so that we can act as if they are integers, find their integer common divisors, and then redivide them by the factor to get a result.
Better explained in code:
a = 6
b = 7.5
c = 24
# Find a few possible divisors between 0 and 2 so that all numbers are divisible
by div.
# We define a function that finds all divisors in a range of numbers, supposing
all numbers are integers.
def find_common_divisors(numbers, range_start, range_end):
results = []
for i in range(range_start + 1, range_end + 1):
if all([e % i == 0 for e in numbers]):
results.append(i)
return results
def main():
nums = [a, b, c]
range_start = 0
range_end = 2
factor = 1
results = [1]
while factor < 11:
nums_i = [e * factor for e in nums]
range_end_i = range_end * factor
results += [e / factor for e in find_common_divisors(nums_i, range_start, range_end_i)]
factor *= 10
print(sorted(set(results)))
if __name__ == '__main__':
main()
For these particular numbers, I get the output:
[0.1, 0.3, 0.5, 1, 1.5]
If we need more results, we can adjust while factor < 11: to a higher number than 11 like 101.
I am curious to see if I made any mistake in my code.
Happy to hear some feedback.
Thank you!
Trying to understand Gram-Schmidt process from this explanation:
http://mlwiki.org/index.php/Gram-Schmidt_Process
The steps of the calculation make sense to me. However the Python implementation included in the same article doesn't seem to be aligned.
def normalize(v):
return v / np.sqrt(v.dot(v))
n = len(A)
A[:, 0] = normalize(A[:, 0])
for i in range(1, n):
Ai = A[:, i]
for j in range(0, i):
Aj = A[:, j]
t = Ai.dot(Aj)
Ai = Ai - t * Aj
A[:, i] = normalize(Ai)
From above code, we see it does dot product for V1 and b, however the (V1,V1) part is not done as the denominator (refer to below equation). I wonder how below equation is translated into code residing in the for loop?
This is what the code does exactly
Basically it normalize the previous vector (column in A) and project the current one to it and to be subtracted by the current one.
Normalization happens with every vector for neat calculation.
The V2 equation above doesn't normalize the previous vector hence the difference.
Try this vectorized implementation.
Also I would suggest to go through David C lay book for theory.
def replace_zero(array):
for i in range(len(array)) :
if array[i] == 0 :
array[i] = 1
return array
def gram_schmidt(self,A, norm=True, row_vect=False):
"""Orthonormalizes vectors by gram-schmidt process
Parameters
-----------
A : ndarray,
Matrix having vectors in its columns
norm : bool,
Do you need Normalized vectors?
row_vect: bool,
Does Matrix A has vectors in its rows?
Returns
-------
G : ndarray,
Matrix of orthogonal vectors
Gram-Schmidt Process
--------------------
The Gram–Schmidt process is a simple algorithm for
producing an orthogonal or orthonormal basis for any
nonzero subspace of Rn.
Given a basis {x1,....,xp} for a nonzero subspace W of Rn,
define
v1 = x1
v2 = x2 - (x2.v1/v1.v1) * v1
v3 = x3 - (x3.v1/v1.v1) * v1 - (x3.v2/v2.v2) * v2
.
.
.
vp = xp - (xp.v1/v1.v1) * v1 - (xp.v2/v2.v2) * v2 - .......
.... - (xp.v(p-1) / v(p-1).v(p-1) ) * v(p-1)
Then {v1,.....,vp} is an orthogonal basis for W .
In addition,
Span {v1,.....,vp} = Span {x1,.....,xp} for 1 <= k <= p
References
----------
Linear Algebra and Its Applications - By David.C.Lay
"""
if row_vect :
# if true, transpose it to make column vector matrix
A = A.T
no_of_vectors = A.shape[1]
G = A[:,0:1].copy() # copy the first vector in matrix
# 0:1 is done to to be consistent with dimensions - [[1,2,3]]
# iterate from 2nd vector to number of vectors
for i in range(1,no_of_vectors):
# calculates weights(coefficents) for every vector in G
numerator = A[:,i].dot(G)
denominator = np.diag(np.dot(G.T,G)) #to get elements in diagonal
weights = np.squeeze(numerator/denominator)
# projected vector onto subspace G
projected_vector = np.sum(weights * G,
axis=1,
keepdims=True)
# orthogonal vector to subspace G
orthogonalized_vector = A[:,i:i+1] - projected_vector
# now add the orthogonal vector to our set
G = np.hstack((G,orthogonalized_vector))
if norm :
# to get orthoNormal vectors (unit orthogonal vectors)
# replace zero to 1 to deal with division by 0 if matrix has 0 vector
# or normazalization value comes out to be zero
G = G/self.replace_zero(np.linalg.norm(G,axis=0))
if row_vect:
return G.T
return G
G = np.array([[1,0,0],[1,1,0],[1,1,1],[1,1,1]])
gram_schmidt(G)
>
array([[ 0.5 , -0.8660254 , 0. ],
[ 0.5 , 0.28867513, -0.81649658],
[ 0.5 , 0.28867513, 0.40824829],
[ 0.5 , 0.28867513, 0.40824829]])
I came across this question in a coding competition. Given a number n, concatenate the binary representation of first n positive integers and return the decimal value of the resultant number formed. Since the answer can be large return answer modulo 10^9+7.
N can be as large as 10^9.
Eg:- n=4. Number formed=11011100(1=1,10=2,11=3,100=4). Decimal value of 11011100=220.
I found a stack overflow answer to this question but the problem is that it only contains a O(n) solution.
Link:- concatenate binary of first N integers and return decimal value
Since n can be up to 10^9 we need to come up with solution that is better than O(n).
Here's some Python code that provides a fast solution; it uses the same ideas as in Abhinav Mathur's post. It requires Python >= 3.8, but it doesn't use anything particularly fancy from Python, and could easily be translated into another language. You'd need to write algorithms for modular exponentiation and modular inverse if they're not already available in the target language.
First, for testing purposes, let's define the slow and obvious version:
# Modulus that results are reduced by,
M = 10 ** 9 + 7
def slow_binary_concat(n):
"""
Concatenate binary representations of 1 through n (inclusive).
Reinterpret the resulting binary string as an integer.
"""
concatenation = "".join(format(k, "b") for k in range(n + 1))
return int(concatenation, 2) % M
Checking that we get the expected result:
>>> slow_binary_concat(4)
220
>>> slow_binary_concat(10)
462911642
Now we'll write a faster version. First, we split the range [1, n) into subintervals such that within each subinterval, all numbers have the same length in binary. For example, the range [1, 10) would be split into four subintervals: [1, 2), [2, 4), [4, 8) and [8, 10). Here's a function to do that splitting:
def split_by_bit_length(n):
"""
Split the numbers in [1, n) by bit-length.
Produces triples (a, b, 2**k). Each triple represents a subinterval
[a, b) of [1, n), with a < b, all of whose elements has bit-length k.
"""
a = 1
while n > a:
b = 2 * a
yield (a, min(n, b), b)
a = b
Example output:
>>> list(split_by_bit_length(10))
[(1, 2, 2), (2, 4, 4), (4, 8, 8), (8, 10, 16)]
Now for each subinterval, the value of the concatenation of all numbers in that subinterval is represented by a fairly simple mathematical sum, which can be computed in exact form. Here's a function to compute that sum modulo M:
def subinterval_concat(a, b, l):
"""
Concatenation of values in [a, b), all of which have the same bit-length k.
l is 2**k.
Equivalently, sum(i * l**(b - 1 - i)) for i in range(a, b)) modulo M.
"""
n = b - a
inv = pow(l - 1, -1, M)
q = (pow(l, n, M) - 1) * inv
return (a * q + (q - n) * inv) % M
I won't go into the evaluation of the sum here: it's a bit off-topic for this site, and it's hard to express without a good way to render formulas. If you want the details, that's a topic for https://math.stackexchange.com, or a page of fairly simple algebra.
Finally, we want to put all the intervals together. Here's a function to do that.
def fast_binary_concat(n):
"""
Fast version of slow_binary_concat.
"""
acc = 0
for a, b, l in split_by_bit_length(n + 1):
acc = (acc * pow(l, b - a, M) + subinterval_concat(a, b, l)) % M
return acc
A comparison with the slow version shows that we get the same results:
>>> fast_binary_concat(4)
220
>>> fast_binary_concat(10)
462911642
But the fast version can easily be evaluated for much larger inputs, where using the slow version would be infeasible:
>>> fast_binary_concat(10**9)
827129560
>>> fast_binary_concat(10**18)
945204784
You just have to note a simple pattern. Taking up your example for n=4, let's gradually build the solution starting from n=1.
1 -> 1 #1
2 -> 2^2(1) + 2 #6
3 -> 2^2[2^2(1)+2] + 3 #27
4 -> 2^3{2^2[2^2(1)+2]+3} + 4 #220
If you expand the coefficients of each term for n=4, you'll get the coefficients as:
1 -> (2^3)*(2^2)*(2^2)
2 -> (2^3)*(2^2)
3 -> (2^3)
4 -> (2^0)
Let the N be total number of bits in the string representation of our required number, and D(x) be the number of bits in x. The coefficients can then be written as
1 -> 2^(N-D(1))
2 -> 2^(N-D(1)-D(2))
3 -> 2^(N-D(1)-D(2)-D(3))
... and so on
Since the value of D(x) will be the same for all x between range (2^t, 2^(t+1)-1) for some given t, you can break the problem into such ranges and solve for each range using mathematics (not iteration). Since the number of such ranges will be log2(Given N), this should work in the given time limit.
As an example, the various ranges become:
1. 1 (D(x) = 1)
2. 2-3 (D(x) = 2)
3. 4-7 (D(x) = 3)
4. 8-15 (D(x) = 4)
Combinations without repetitions look like this, when the number of elements to choose from (n) is 5 and elements chosen (r) is 3:
0 1 2
0 1 3
0 1 4
0 2 3
0 2 4
0 3 4
1 2 3
1 2 4
1 3 4
2 3 4
As n and r grows the amount of combinations gets large pretty quickly. For (n,r) = (200,4) the number of combinations is 64684950.
It is easy to iterate the list with r nested for-loops, where the initial iterating value of each for loop is greater than the current iterating value of the for loop in which it is nested, as in this jsfiddle example:
https://dotnetfiddle.net/wHWK5o
What I would like is a function that calculates only one combination based on its index. Something like this:
tuple combination(i,n,r) {
return [combination with index i, when the number of elements to choose from is n and elements chosen is r]
Does anyone know if this is doable?
You would first need to impose some sort of ordering on the set of all combinations available for a given n and r, such that a linear index makes sense. I suggest we agree to keep our combinations in increasing order (or, at least, the indices of the individual elements), as in your example. How then can we go from a linear index to a combination?
Let us first build some intuition for the problem. Suppose we have n = 5 (e.g. the set {0, 1, 2, 3, 4}) and r = 3. How many unique combinations are there in this case? The answer is of course 5-choose-3, which evaluates to 10. Since we will sort our combinations in increasing order, consider for a minute how many combinations remain once we have exhausted all those starting with 0. This must be 4-choose-3, or 4 in total. In such a case, if we are looking for the combination at index 7 initially, this implies we must subtract 10 - 4 = 6 and search for the combination at index 1 in the set {1, 2, 3, 4}. This process continues until we find a new index that is smaller than this offset.
Once this process concludes, we know the first digit. Then we only need to determine the remaining r - 1 digits! The algorithm thus takes shape as follows (in Python, but this should not be too difficult to translate),
from math import factorial
def choose(n, k):
return factorial(n) // (factorial(k) * factorial(n - k))
def combination_at_idx(idx, elems, r):
if len(elems) == r:
# We are looking for r elements in a list of size r - thus, we need
# each element.
return elems
if len(elems) == 0 or len(elems) < r:
return []
combinations = choose(len(elems), r) # total number of combinations
remains = choose(len(elems) - 1, r) # combinations after selection
offset = combinations - remains
if idx >= offset: # combination does not start with first element
return combination_at_idx(idx - offset, elems[1:], r)
# We now know the first element of the combination, but *not* yet the next
# r - 1 elements. These need to be computed as well, again recursively.
return [elems[0]] + combination_at_idx(idx, elems[1:], r - 1)
Test-driving this with your initial input,
N = 5
R = 3
for idx in range(choose(N, R)):
print(idx, combination_at_idx(idx, list(range(N)), R))
I find,
0 [0, 1, 2]
1 [0, 1, 3]
2 [0, 1, 4]
3 [0, 2, 3]
4 [0, 2, 4]
5 [0, 3, 4]
6 [1, 2, 3]
7 [1, 2, 4]
8 [1, 3, 4]
9 [2, 3, 4]
Where the linear index is zero-based.
Start with the first element of the result. The value of that element depends on the number of combinations you can get with smaller elements. For each such smaller first element, the number of combinations with first element k is n − k − 1 choose r − 1, with potentially some of-by-one corrections. So you would sum over a bunch of binomial coefficients. Wolfram Alpha can help you compute such a sum, but the result still has a binomial coefficient in it. Solving for the largest k such that the sum doesn't exceed your given index i is a computation you can't do with something as simple as e.g. a square root. You need a loop to test possible values, e.g. like this:
def first_naive(i, n, r):
"""Find first element and index of first combination with that first element.
Returns a tuple of value and index.
Example: first_naive(8, 5, 3) returns (1, 6) because the combination with
index 8 is [1, 3, 4] so it starts with 1, and because the first combination
that starts with 1 is [1, 2, 3] which has index 6.
"""
s1 = 0
for k in range(n):
s2 = s1 + choose(n - k - 1, r - 1)
if i < s2:
return k, s1
s1 = s2
You can reduce the O(n) loop iterations to O(log n) steps using bisection, which is particularly relevant for large n. In that case I find it easier to think about numbering items from the end of your list. In the case of n = 5 and r = 3 you get choose(2, 2)=1 combinations starting with 2, choose(3,2)=3 combinations starting with 1 and choose(4,2)=6 combinations starting with 0. So in the general choose(n,r) binomial coefficient you increase the n with each step, and keep the r. Taking into account that sum(choose(k,r) for k in range(r,n+1)) can be simplified to choose(n+1,r+1), you can eventually come up with bisection conditions like the following:
def first_bisect(i, n, r):
nCr = choose(n, r)
k1 = r - 1
s1 = nCr
k2 = n
s2 = 0
while k2 - k1 > 1:
k3 = (k1 + k2) // 2
s3 = nCr - choose(k3, r)
if s3 <= i:
k2, s2 = k3, s3
else:
k1, s1 = k3, s3
return n - k2, s2
Once you know the first element to be k, you also know the index of the first combination with that same first element (also returned from my function above). You can use the difference between that first index and your actual index as input to a recursive call. The recursive call would be for r − 1 elements chosen from n − k − 1. And you'd add k + 1 to each element from the recursive call, since the top level returns values starting at 0 while the next element has to be greater than k in order to avoid duplication.
def combination(i, n, r):
"""Compute combination with a given index.
Equivalent to list(itertools.combinations(range(n), r))[i].
Each combination is represented as a tuple of ascending elements, and
combinations are ordered lexicograplically.
Args:
i: zero-based index of the combination
n: number of possible values, will be taken from range(n)
r: number of elements in result list
"""
if r == 0:
return []
k, ik = first_bisect(i, n, r)
return tuple([k] + [j + k + 1 for j in combination(i - ik, n - k - 1, r - 1)])
I've got a complete working example, including an implementation of choose, more detailed doc strings and tests for some basic assumptions.
Suppose I have the following array:
[6,3,3,5,6],
Is there an already implemented way to sort the array and that returns also the number of permutations that it had to make the algorithm to sort it?
For instance, I have to move 3 times to the right with the 6 so it can be ordered, which would give me parity -1.
The general problem would be to order an arbitrary array (all integers, with repeated indexes!), and to know the parity performed by the algorithm to order the array.
a=[6,3,3,5,6]
sortperm(a) - [ 1:size(a)[1] ]
Results in
3-element Array{Int64,1}:
1
1
1
-3
0
sortperm shows you where each n-th index should go into. We're using 1:size(a)[1] to compare the earlier index to its original indexation.
If your array is small, you can compute the determinant of the permutation matrix
function permutation_sign_1(p)
n = length(p)
A = zeros(n,n)
for i in 1:n
A[i,p[i]] = 1
end
det(A)
end
In general, you can decompose the permutation as a product of cycles,
count the number of even cycles, and return its parity.
function permutation_sign_2(p)
n = length(p)
not_seen = Set{Int}(1:n)
seen = Set{Int}()
cycles = Array{Int,1}[]
while ! isempty(not_seen)
cycle = Int[]
x = pop!( not_seen )
while ! in(x, seen)
push!( cycle, x )
push!( seen, x )
x = p[x]
pop!( not_seen, x, 0 )
end
push!( cycles, cycle )
end
cycle_lengths = map( length, cycles )
even_cycles = filter( i -> i % 2 == 0, cycle_lengths )
length( even_cycles ) % 2 == 0 ? 1 : -1
end
The parity of a permutation can also be obtained from the
number of inversions.
It can be computed by slightly modifying the merge sort algorithm.
Since it is also used to compute Kendall's tau (check less(corkendall)),
there is already an implementation.
using StatsBase
function permutation_sign_3(p)
x = copy(p)
number_of_inversions = StatsBase.swaps!(x)
number_of_inversions % 2 == 0 ? +1 : -1
end
On your example, those three functions give the same result:
x = [6,3,3,5,6]
p = sortperm(x)
permutation_sign_1( p )
permutation_sign_2( p )
permutation_sign_3( p ) # -1