Generating m -Ary Gray Codes and Related Algorithms

: In this work, we systematize several implementations of the Gray code over an alphabet with m ≥ 2 elements, which we present in C code so that they can be used directly after copying from the text. We consider two variants—reflected and modular (or shifted) m -ary Gray codes. For both variants, we present the ranking and unranking functions, as well as algorithms for generating only a part of the code, more precisely the codewords between two given vectors. Finally, we give algorithms that generate a maximal set of non-proportional vectors of length n over the given alphabet in a Gray code


Introduction
The classical version of a Gray code is an ordering of the 2 n binary vectors of length n such that each pair of adjacent vectors differ in a single position [1,2].If the last and the first vectors also differ in one position, the code is said to be cyclic.Gray codes are widely used in digital communications, effective generation of combinatorial objects, etc.In coding theory, Gray codes are used for the effective generation of the codewords of a given code in order to compute some of its parameters as weight enumerator and minimum distance.
To study linear codes over fields with more than two elements, we need an m-ary version of the Gray code [3][4][5].Let Z m = {0, 1, . . ., m − 1} be the ring of integers modulo m, where m > 1 is an integer, and Z n m be the set of all n-tuples (vectors) with coordinates from Z m .If we define addition and multiplication by a coefficient as follows a + b = (a 1 + b 1 , . . ., a n + b n ), αa = (αa 1 , . . ., αa n ) for all a = (a 1 , . . ., a n ), b = (b 1 , . . ., b n ) ∈ Z n m and all α ∈ Z m , we can consider Z n m as a module over Z m .If m is prime, then Z m is a prime field and Z n m is a vector space over this field.
Our motivation to study m-ary Gray codes in detail and develop algorithms for their generation is to use them in software to compute some important parameters and characteristics of linear codes over fields (and also over rings) with m elements.We can use already published algorithms, but our idea is to develop algorithms that are suitable for parallelization.The main reason for this is the development of computer technology in the direction of more processors, cores and threads.
A linear q-ary code C n of length n is a linear subspace of the vector space F n q , where F q is a finite field with q elements.If q is prime, then we can take F q = Z q .If q = p s is a prime power with s > 1, there is no natural ordering of the elements, so we need to define an ordering if we want to use a Gray code to generate the codewords in C n .One possibility is to consider the lexicographic ordering of the field elements in a polynomial representation.Another one is to consider the elements in multiplicative form, namely F q = {0, 1, α, α 2 , . . ., α q−2 }, where α ∈ F q is a primitive element of the field.Then, we can order the elements of the field using the bijection φ : F q → Z q defined by φ(0) = 0, φ(α s ) = s if s = 1, . . ., q − 2, q − 1.
All possible orderings of the elements are q! and the researchers can choose the one they think is most suitable for their investigations.
Some authors consider a more general version of Gray codes, namely Combinatorial Gray Codes, which are lists of combinatorial objects in which successive objects are "close" in some sense [6][7][8].These lists have many applications and also lead to interesting questions and results of a mathematical nature.The formal definition is the following.Let S be a finite set of combinatorial objects and R a relation on S, which we call the closeness relation.Then, a Combinatorial Gray Code for S is a listing s 1 , s 2 , . . ., s N of the elements of S, where |S| = N, such that (s i , s i+1 ) ∈ R for i = 1, 2, . . ., N − 1.Sometimes, we also insist that (s N , s 0 ) ∈ R; in this case, the code is said to be cyclic.
There is a graph G = G(S, R), in which the closeness graph, that is naturally associated with a combinatorial Gray code.The vertices of this graph are the objects of S and edges connect those objects that are "close"; in other words, G(S, R) is just the digraph of the relation R. In most instances, the closeness relation is symmetric and so G may be regarded as an undirected graph.The question of finding a Combinatorial Gray Code for S with respect to R becomes one of finding a Hamiltonian path (or cycle) in G. Conversely, a Hamiltonian path in G can be viewed as a Gray code for S.
The most popular Gray code is the binary Gray code, where S is the set of all bitstrings of fixed length and R is the relation of Hamming distance one.In this case, G is the hypercube.For the m-ary Gray codes, S = Z n m , and R is the same relation of Hamming distance one but combined with a difference in the changed position with +1 or −1.
Among the non-binary Gray codes, the most popular (and studied) are reflected and modular Gray codes [2].Both codes are natural generalizations of the binary reflected Gray code, where the m-ary vectors are ordered such that every two consecutive vectors differ by exactly one coordinate [4,5,9].Moreover, in an m-ary reflected Gray code, this difference is only +1 or −1, and in an m-ary modular Gray code, it is +1 modulo m.So, we have the minimum possible change between two consecutive vectors (codewords).
For both versions of the m-ary Gray code, we present algorithms: • Generation in recursive and non-recursive versions; • Ranking and unranking; • Generation only a part of the code, namely the part between given vectors a and b, where a, b ∈ Z n m .We summarize the most popular known algorithms and also introduce some new algorithms for m-ary Gray codes.Our versions of these algorithms are presented in C code so that they can be used directly by simply copying from the text.We present several algorithms for generation of the reflected m-ary Gray code in Section 2. Section 3 is devoted to the modular (shifted) m-ary Gray code.In this case, we have also included algorithms for generating a maximal set of non-proportional vectors in Z n m ordered in Gray code.

The m-Ary Reflected Gray Code
Both m-ary Gray codes we study are generalizations of the binary reflected Gray code, but for the m-ary reflected Gray code, we have a similar recursive construction.So, we start with the properties and algorithms for this code.
We denote by G m (n) the sequence of vectors of Z n m in an m-ary reflected Gray code of length n.We define the code recursively, and for convenience, we associate the sequence with a matrix whose i-th row is the i-th vector in the sequence.
By A * , we denote the mirror image (reflection) of the rows of the p × q matrix A = (a 1 , a 2 , . . ., a p ) T , i.e., A * = (a p , . . ., a 2 , a 1 ) T .The reflected m-ary Gray code of length n is the list of the rows of the matrix G m (n).
As given in the definition, the elements in the first column in the matrix G m (k) increase.For the other columns, we have ascending and descending order of the elements.The following theorem, similar to Theorem 2 in [4], gives information about whether a given coordinate of a given codeword is in an ascending or descending subsequence in the corresponding column.
Theorem 1.Let v = (v 1 , v 2 , . . ., v n ) be a codeword of the m-ary reflected Gray code G m (n) for m ≥ 2 and n ≥ 2. When S i = ∑ i−1 j=1 v j is even, then v i , and for 1 < i ≤ n, is in a subsequence of digits in ascending order; otherwise, it is in a subsequence of digits in descending order.
Proof.We will use the following observation that follows directly from the definition and is more visible, if we look at the matrix G m (k).The first coordinate v 1 is always in a sequence in ascending order.For any coordinate position i, v i can be considered as an element in the first column in one of the matrices G m (n − i + 1) or G * m (n − i + 1) (according to the recursive structure of the matrices in (1)).Moreover, if v i is in the first column in a matrix G m (n − i + 1), then it is in a subsequence of digits in ascending order, but if v i is in the first column in a matrix G * m (n − i + 1), it is in a subsequence of digits in descending order.We will prove this theorem by induction.According to (1), if v 1 is even then v 2 is in the first column of G m (n − 1); otherwise, v 2 is in the first column of G * m (n − 1).Therefore, v 2 is in a subsequence of digits in ascending order when v 1 is even, and in descending order if v 1 is odd.
Suppose that the statement holds for i = k < n and consider the next coordinate v k+1 .Now, we consider the following cases: • Let S k be even.According to the induction hypothesis, v k is in a subsequence of digits in ascending order and can be considered as an element in the first column in a corresponding matrix G m (n − k + 1).Then, according to the base step, v k+1 is in a subsequence of digits in ascending order when v k is even, and in descending order is in a subsequence of digits in ascending order when S k+1 is even, and in descending order if S k+1 is odd.• Let S k be odd.Then, v k is in a subsequence of digits in descending order and can be considered as an element in the first column in a matrix G * m (n − k + 1).Therefore, we have the reverse situation of the previous case, namely v k+1 is in a subsequence of digits in descending order when v k is even, and in ascending order if v k is odd.Now, S k+1 = S k + v k ≡ v k + 1 (mod 2), and so again v k+1 is in a subsequence of digits in ascending order when S k+1 is even, and in descending order if S k+1 is odd.So the statement is true for all coordinates positions i = 2, . . ., n.
A convenient way to describe a Gray code is with the transition sequence [3,5], which is the ordered sequence of position changes from one word to the next.The transition sequence of a Gray code is an arrangement of positions showing the locations that undergo a change as one proceeds along the codewords.In most papers and books dealing with the transition sequence, the columns of G m (k) are numbered starting with 1 from right to left, but here, we number them as usual for matrices from left to right.In addition, because we consider a m-ary Gray code and the coordinates participate in decreasing and increasing subsequences, we multiply the current element in the transition sequence by −1 if the corresponding coordinate belongs to a decreasing subsequence.We denote the transition sequence for the reflected m-ary Gray code G m (n) by T m (n).

Example 1.
We give examples with a ternary and a quaternary reflected Gray codes.

•
The ternary reflected Gray code of length 3 is given by the following sequence Its transition sequence is In this paper, we present a C implementation of the algorithms.All of them use a global array g, in which the current codeword is written, and a global constant Maxn, which applies to all arrays in the algorithms and sets the maximum length of the code to generate.The function print is used to print the codewords and the corresponding values of the transition sequence to the screen (see Algorithm 1).We use this function in all programs to show that the next codeword is available.

Algorithm 1 The function print and the global variables
for (int j = 1; j <= n; j++) { printf("%d", g[j]); } printf(" T= %d; \n", t); } The algorithms are unified, and in all of them, the arrays are denoted in the same way.The array S consists only of ±1, and indicates that when the corresponding coordinate will be in an ascending or descending sequence, the variable t indicates the corresponding value in the transition sequence, etc.The array S is optional, and in the implementation of the algorithms, we can choose whether to use this array or directly check whether the corresponding sum is even or odd (as shown in Theorem 1).We use both approaches in our algorithms.It is good to note that for an integer a we have a&1 = 0 if a is even, and a&1 = 1 if a is odd.
An algorithm that generates the set G m (n) is proposed in [4] and used in its original form in [5].In Algorithm 2, we present its C implementation with an addition, namely checking that the same value is not written more than once.It is good to note that the original functions write the same values more than once, and so the number of recursive calls can reach ≈1.5m n (1.5 times the number of the generated codewords).It is important to note that it is not always 1.5, but it is reached in the worst cases.As we already described, in g we put the currently generated codeword, m is the cardinality of the alphabet, and n is the length of the code.The additional check that the same value is not written more than once does not increase the time complexity of Algorithm 2-it remains Ω(m n ).
The algorithms in [4] have redundant steps, so we offer other variants that are based on a nested loop algorithm.In Algorithm 3, we present the variant for n = 4 with four nested loops.This algorithm is intuitive and easy to understand, and moreover all subsequent algorithms can be explained through it.
The n nested loops algorithm is defined by: (1) the number of loops; (2) the counter values of each loop; (3) the sequence in which the loops are executed.In Algorithm 3, k-th loop changes the k-th coordinate in the current codeword of G m (n).In the reflected Gray code, there is another component indicating when to change sign in an iteration (which corresponds to the question of when to change the order of coordinates at a given position in successive codewords from ascending to descending and vice versa).It is essential for the sequence of loops that after each change of a coordinate (generation of another codeword), one goes to the innermost cycle (we mean that intermediate loops do nothing).

Algorithm 3 Nested loop algorithm for the reflected Gray code -example for
Proof for the correctness of Algorithm 3 with n nested loops.This algorithm uses the two arrays g and S. Recall that in g we store the current codeword of G m (n).The elements of S are plus and minus 1s and indicate whether we will get the next coordinates in the corresponding position in ascending or descending order.If S[i] = 1, then the algorithm generates the i-th coordinate in ascending order, and if S[i] = −1-in descending order.Hence, changing the sign in S[i], we change the order.
We will prove that this algorithm generates the reflected Gray code as defined in (1) by induction.If n = 1, the algorithm has only one cycle and generates G m (1).
Suppose that the algorithm with k − 1 < n nested loops generates G m (k − 1) and consider the algorithm with k nested cycles.If i1 = 1, then S[i] = 1 for all i = 1, . . ., n and so the other nested loops generate the reflected Gray code of length k − 1.Further, when i1 takes value 2, the last values of S [2], S[3], . . ., S[k] after the change have not yet been used, and therefore the algorithm in this part generates the mirror image of G m (k − 1) which is G * m (k − 1).Whenever i1 changes, the algorithm generates a mirror image of the reflected Gray code of length k − 1 generated at the previous value of i1.Thus, we have the same recursive construction as in Definition 1.
The following algorithms reduce the number of operations to obtain the next codeword, each with its own advantages.Algorithm 4 presents a recursive version of the nested loop algorithm.There are two recursive calls in the recursive procedure.The first call serves to reach the last level (bottom) of the recursion (i.e., the innermost loop in the nested loop variant).In Algorithm 4, the array S is not used, but the sum S i from Theorem 1 is directly checked whether it is even or odd.This algorithm can also be implemented using the array S, in which case there would be no need for the sum ss and the variable s.
For recursive algorithms (in both sections), the depth of the recursive entries corresponds to the number of the nested loops.In Algorithm 4, the variable h indicates the current depth in the recursion and takes values from 1 to n + 1, but if h = n + 1, the algorithm does nothing and simply goes back one step in the recursion.

Algorithm 4 Recursive generation of
for (int i = 0; i <= n; i++) g[i] = 0; print(g, n, 0); Gray_refl_rec (1, 0, m, n); // ss = 0 in the beginning } A short and simple non-recursive algorithm for generating the sequence G m (n) is presented in [10].It is derived after the proof of three theorems.Here we propose a similar algorithm derived in a simpler way.The idea is to modify each of the nested loops to perform reflection.When the loop variable reaches its final value m − 1, it should continue with its reflection, i.e., its values must decrease from m − 1 to 0 after increasing from 0 to m − 1, and vice versa-when the loop variable decreases and reaches its final value 0, the reflection ends and the loop variable starts increasing again from 0 to m − 1.We keep the arrays g for the current codeword (vector) of G m (n) and S, but add one additional array final_vals of the same type.The element final_vals[j] stores the limit value of the jth cycle, which is m − 1 when the jth coordinate increases (i.e., S[j]=1), or 0 when this coordinate decreases (i.e., S[j]= -1), for j = 1, 2, . . ., n.The C code of this algorithm is presented in Algorithm 5.
As can be seen, the algorithm continues until all elements of the array g reach their limit values (the limit value is 0 or m − 1).Then, the value of S [1] should reverse to −1 and the algorithm terminates, as the main loop do...while indicates.Through the (inner) while loop, the algorithm scans from right to left the elements of the array g, looking for an element that has not reached its final value.Meanwhile, for all elements g[k] with limit values, the corresponding elements S[k] reverse their signs and, depending on the sign, the corresponding elements final_vals[k] get required value: m − 1 or 0. The advantage of this algorithm (comparing with Algorithm 3) is that when at some step the sign of S[k] needs to be changed, the signs of the following elements of the array S are changed along with it, i.e., S[k + 1], . . .

, S[n].
The correctness of this algorithm follows from Definition 1 and the way of emulating the execution of n nested loops.It corresponds to Theorem 1 and can be proved analogously by induction on n.Regarding its time complexity, apparently, the algorithm performs O(n) operations to generate each vector after the current one.Regarding the exact estimate of the time complexity, we note that the while loop runs in total (m n + m [2].For each run, it performs 2 comparisons, 3 assignments, one subtraction, and one sign change.Thus, the while loop performs Θ(m n ) operations.In the body of the main loop do...while, more Θ(m n ) comparisons, assignments, and additions are performed (excluding print operations).Therefore, the total time complexity of Algorithm 5 is Θ(m n ) + Θ(m n ) = Θ(m n ) and the average time to generate one vector is constant; hence, it is a constant amortized-time (CAT) algorithm [7].
In [11], the authors used stack to generate the code and the transition sequence, but only in the binary case.The stack operates as follows: the stack initially contains n, n − 1, . . ., 1 (with 1 on top).The algorithm pops off the top element i and puts it into the transition sequence (respectively changes the corresponding coordinate in the current codeword); the elements i − 1, i − 2, . . ., 1 are then pushed onto the stack.The stack operates in a highly restricted manner since when j > 1 is placed in the stack, we know a priori that j − 1, . . ., 1 will be placed above it.
In this implementation, each subsequent codeword is obtained after a constant number of operations and so Algorithm 6 is a loopless algorithm (for loopless algorithms see [2,11]).In Figures 1 and 2, we have graphically presented how the algorithm works.3 assignments, one subtraction, and one sign change.Thus, the while loop performs Θ(m n ) operations.In the body of the main loop do...while, more Θ(m n ) comparisons, assignments, and additions are performed (excluding print operations).Therefore, the total time complexity of Algorithm 5 is Θ(m n ) + Θ(m n ) = Θ(m n ) and the average time to generate one vector is constant, hence it is a constant amortized-time (CAT) algorithm [6].
In [11], the authors used stack to generate the code and the transition sequence, but only in the binary case.The stack operates as follows: the stack initially contains n, n − 1, . . ., 1 (with 1 on top).The algorithm pops off the top element i and puts it into the transition sequence (respectively changes the corresponding coordinate in the current codeword); the elements i − 1, i − 2, . . ., 1 are then pushed onto the stack.The stack operates in a highly restricted manner since when j > 1 is placed in the stack, we know a priori that j − 1, . . ., 1 will be placed above it.
In this implementation, each subsequent codeword is obtained after a constant number of operations and so Algorithm 6 is a loopless algorithm (for loopless algorithms see [11] and [8]).In Figures 1 and 2, we have graphically presented how the algorithm works.3 assignments, one subtraction, and one sign change.Thus, the while loop performs Θ(m n ) operations.In the body of the main loop do...while, more Θ(m n ) comparisons, assignments, and additions are performed (excluding print operations).Therefore, the total time complexity of Algorithm 5 is Θ(m n ) + Θ(m n ) = Θ(m n ) and the average time to generate one vector is constant, hence it is a constant amortized-time (CAT) algorithm [6].
In [11], the authors used stack to generate the code and the transition sequence, but only in the binary case.The stack operates as follows: the stack initially contains n, n − 1, . . ., 1 (with 1 on top).The algorithm pops off the top element i and puts it into the transition sequence (respectively changes the corresponding coordinate in the current codeword); the elements i − 1, i − 2, . . ., 1 are then pushed onto the stack.The stack operates in a highly restricted manner since when j > 1 is placed in the stack, we know a priori that j − 1, . . ., 1 will be placed above it.
In this implementation, each subsequent codeword is obtained after a constant number of operations and so Algorithm 6 is a loopless algorithm (for loopless algorithms see [11] and [8]).In Figures 1 and 2, we have graphically presented how the algorithm works.The implementation of Algorithm 6 is done in a very elegant way by using a single array.We give an generalization of the algorithm for the m-ary reflected Gray code, and when m > 2 we need the additional array M. In each cell of the stack, there are initially m elements, which are taken one by one.As can be seen, Algorithm 6 performs a constant number of operations to generate each subsequent codeword, regardless of n and m.The implementation of Algorithm 6 is carried out in a very elegant way by using a single array.We give a generalization of the algorithm for the m-ary reflected Gray code, and when m > 2 we need the additional array M. In each cell of the stack, there are initially m elements, which are taken one by one.As can be seen, Algorithm 6 performs a constant number of operations to generate each subsequent codeword, regardless of n and m.
and in this case the codewords of the corresponding Gray code are taken in reverse order.Hence, the number of g ′ if this code is m n−1 − 1 − a g ′ , where a g ′ is the number of g ′ ∈ G m (n − 1).Since the ciphers of this number for radix m are m − 1 − a 2 , m − 1 − a 3 , . . ., m − 1 − a n , we have the same formula for a i , i = 1, . . ., n.
. ., n, be a nonnegative integer, 0 ≤ a ≤ m n − 1, and g = (g 1 , g 2 , . . ., g n ) be the codeword in G m (n) in the a-th row according to Definition 1. Then where S 1 = 0 and S i = ∑ i−1 j=1 g j for 2 ≤ i ≤ n.
Tables 1 and 2 show the ranking of the codewords in G 3 (3) and G 4 (2), respectively.The implementation of the Formulas (2) and ( 3) is given in Algorithm 7. Additionally, we have incorporated the decimal representation of the rank (sequence number) into these algorithms.Obviously, each of the rank and unrank functions runs in Θ(n) time.

Algorithm 7 Ranking and unranking for the reflected Gray code
void Gray_refl_unrank (int g[], int m, int n, int value) { int a[Maxn]; for (int i = 0; i < n; i++) { a[n-i] = value % m; value = value / m; } g [1] = a [1]; int sum = g [1]; for (int i = 2; i <= n Algorithm 8 is very convenient if one wants to generate only a part of the codewords, namely the codewords from A to B, where 0 ≤ A < B ≤ m n − 1.The main thing in it is to find a codeword by a given number in the sequence (unranking).We follow the algorithm for G 2 (n) from [13], but present an extended version for the m-ary Gray code.
Its time complexity analysis is similar to that of Algorithm 5 or can be used as a generalization of that in [13].Finally, we get that Algorithm 8 is again a CAT algorithm.

The m-Ary Modular (Shifted) Gray Code
In [3], the authors define the directional distance of b from a, where a, b ∈ Z m , denoted by σ(b − a) and equal to (b − a) (mod m).Next, they define the directional distance of a vector from another vector in Z n m as the sum of directional distances between their respective components, or It is worth mentioning that the directional distance is not a metric in Z n m because there are vectors u, v ∈ Z n m , such that d D (v, u) ̸ = d D (u, v).In [3], ([Lemma 1]), it was proved that , where d H is the Hamming distance between the vectors.
In this section, we study the m-ary Gray code defined in [3] as an ordered sequence of the vectors of Z n m , such that each vector differs from the preceding vector by directional distance one.This means that to obtain the next codeword, we add one modulo m to exactly one coordinate and do not change the other coordinates.We denote the thus-defined code that starts from the zero vector by G(m, n) and call it a modular (or shifted) Gray code.In the m-ary modular Gray code, the m-ary vectors are ordered such that every two consecutive vectors differ by exactly one coordinate, and the difference is +1 modulo m.If m = 2, this Gray code coincides with the reflected Gray code, so we can consider it as another generalization of the binary reflected Gray code.We present the ternary modular Gray code of length 3 in Table 3.
A formula how from the m-adic representation of the integer a to obtain the corresponding codeword g = (g 1 , . . ., Algorithm 9 implements these formulas with Θ(n) running time.
The authors in [3] also present a generating recursive procedure for the code.As in the previous section, we again use the matrix representation of the codes.Obviously, be obtained on cyclically shifting of the elements of G(m, 1) by i positions.Then Algorithm 9 Ranking and unranking for the modular Gray code void Gray_modular_unrank (int g[], int m, int n, int num) { int a[Maxn]; for (int i = 0; i < n; i++) { a[n-i] = num % m; num = num / m; } g [1] = a [1]; for (int i = 2; i <= n; i++) { g The last codeword g m n −1 (m, n) in G(m, n) is the last row in the above matrix, namely (mod m) = (0, 0, . . ., 0), then g m n −1 (m, n) = (m − 1, 0, 0, . . ., 0).Hence, the modular Gray code G(m, n) is cyclic.
The transition sequence for this Gray code is similar to the transition sequence T m (n) of the reflected Gray code G m (n), as the only difference is that we do not use negative numbers here.If T(m, n) is the transition sequence for the code G(m, n), then where T m (n) = (t 0 , . . ., t m n −1 ).
Algorithm 10 generates the modular Gray code using nested loops.This algorithm repeats Algorithm 3 to generate the corresponding Gray code with nested loops, but in a simpler version, as the modular Gray code does not need the array S to change the sign.Instead, we add 1 modulo m to the corresponding coordinate.The proof of the correctness of Algorithm 10 is similar to the proof of the nested loop algorithm for the reflected Gray code (Algorithm 3).Its implementation using recursion is given in Algorithm 11.
In the end, we present an algorithm that generates only nonproportional vectors from Z n m in a Gray code.Let S m (n) be the set of all vectors in Z n m whose first nonzero coordinate is 1.For example, Denote by G(m, n, 1) the modular Gray code after adding +1 modulo m to the first coordinate in each codeword, and consider the following recursive construction: First, we will give one example, and then we will prove that the presented construction generates a Gray code over S m (n).
Example 2. We present the matrix for m = n = 3. Proof.Obviously, represents a Gray code.Assume that H m (n − 1) forms a Gray code.By the induction hypothesis, each vector h i differs from the preceding vector by directional distance one, where h i is the i-th row of the matrix H m (n) given in (8), for 2 ≤ i ≤ m n−1 −1 m−1 .Since G(m, n − 1) is a Gray code, the same is true for i = m n−1 −1 m−1 + 2, . . . ,m n −1 m−1 .To complete the proof, we have to compare the last codeword in (0, H m (n − 1)) and the first one in (1, G(m, n − 1, 1)).The first vector in G(m, n, 1) is (1, 0, . . ., 0) and the last one is (0, 0, . . ., 0).Since H m (n − 1) is generated by the same construction, its last row is (1, 0, . . ., 0).Hence, the rows with numbers m n−1 −1 m−1 − 1 and m n−1 −1 m−1 in H m (n) are (0, 1, 0, . . ., 0) and (1, 1, 0, . . ., 0).They differ only in the first coordinate and therefore they are consecutive codewords and H m (n) forms a Gray code.Example 3. We present one more example, namely the Gray code H 4 (3).for (int j = n; j > 0; j--) { for (int i = 0; i <= n; i++) g[i]=0; g[j] = 1; if (j == n) print (g, n, j); else { for (int i = 1; i <= m; i++) { g[j + 1] = i % m; print (g, n, j+1); Gray_modular_rec (j + 2, n, m); } } } } sequence in the Gray code with the idea that we can use them in a parallel implementation of some of our programs for computing parameters of linear codes over a finite field with q elements, where q is a prime power.Some of the proposed algorithms are variants of already known ones, and others are generalizations of algorithms for generating binary Gray codes.Some of the algorithms produce the same result but have their advantages and disadvantages.Recursive algorithms are more compact, but in most cases recursion slows down the execution.Some of the algorithms are very convenient for generating part of the codewords, which allows breaking the code into parts that are generated separately, but with less efficiency.We have also considered in detail the issue of generating a maximum set of non-proportional vectors in Z n m in a Gray code, which is important in coding theory.
Comparing the algorithms, we can say that those for the modular Gray code are simpler (and some of them faster) than the corresponding algorithms for the reflected Gray code.However, the corresponding algorithms have time complexity of the same type as follows: • The nested loop Algorithms 3 and 10, the examples for n = 4, have time complexity Θ(m 4 ).In the general case, for n nested loops, they have a time complexity of the type Θ(m n ).

•
The non-recursive algorithms: 5, 12, 8 and 13 to generate all or part of all codewords, are CAT-algorithms.Also the stack Algorithms 6 and 15 are loopless algorithms, i.e., the number of operations to obtain the next codeword is constant and does not depend on n and m.

•
All ranking and unranking Algorithms 7 and 9 have time complexity Θ(n).
We also want to pay special attention to another of our motivations for this paper, namely to generate interest in research on non-binary Gray codes.While binary Gray codes have an enormous research interest, e.g., defining equivalences, relating graphs and other combinatorial configurations [14], for non-binary Gray codes there are only a few papers on their generation.

H 3 (Theorem 3 .
The rows of the matrix H m (n) form a Gray code with m n −1 m−1 codewords from the set S m (n).