Search code examples
algorithmpseudocode

Algorithm to return combinantions of k elements in a specific circle


It is intended to paint a disc with n circular crowns and m sectors, using k distinct colors (color names can be switched by numbers). In order for there to be some diversity in the painting of the discs, but for differences to be blurred, the painting must obey the following rules:

1- each sector in each crown has only one of the colors

2- there can not be two sectors exactly with the same color setting

2-two adjacent sectors may only differ in color from one of their crowns

From a disc with n=2, m=9 e K=3 we can get this list

[

  [ "red"; "red" ],
  [ "red"; "green" ],
  [ "red"; "blue" ],
  [ "green"; "blue" ],
  [ "blue"; "blue" ],
  [ "blue"; "green" ],
  [ "green"; "green" ],
  [ "green"; "red" ],
  [ "blue"; "red" ] ]

As u can see the last sector combines with the first within the proposed conditions...

From the disks below, both with n = 3, m = 8 and k = 2, only the one on the left is painted according to the rules. Like the one on the right, it is not the "black-white-black" pattern that repeats as were the adjacent sectors that differ in most of a crown (the sector above is different from its adjacent neighbors) more internal.

enter image description here

I've tried some algorithms such as using simple combinations but it doesn´t work because it's a circle so the last color set has to match with the first one.


Solution

  • As far as I understand it, this question asks for an algorithm to generate a cyclic k-ary Gray code of length n. (I'm assuming that the intent is that m always be exactly kn so that all possible colour sequences are present in the circle. See below for a note about how to deal with finding shorter Gray sequences.)

    A Gray code is an encoding system where consecutive codes have Hamming distance 1; that is, they differ in only one position. While the most usual Gray code is binary, the concept and the algorithm can easily be extended to base k for any k.

    The classic binary Gray code is formed from a binary number by cumulatively XORing digits from "left to right" (i.e. high-order digit first). The following algorithm, copied without modification from Wikipedia, replaces the XOR with "sum modulo k"; that works also if k is 2 because XOR is exactly the modulo 2 sum of its arguments. [Notes 1 and 2]

    I added a driver program which print out the kn distinct sequences for any n-length sequence of k colours, repeating the first line so as to make it clear that it is cyclic.

    Actually, it is easy to prove that the last sequence differs in only one position from the first sequence, since the result of applying the algorithm to kn−1, which is the base-k number consisting entirely of the digit k−1, has a 0 in every position other than the first position.

    // The following was copied without modification from
    // https://en.wikipedia.org/w/index.php?title=Gray_code&oldid=869851753#n-ary_Gray_code
    
    // inputs: base, digits, value
    // output: Gray
    // Convert a value to a Gray code with the given base and digits.
    // Iterating through a sequence of values would result in a sequence
    // of Gray codes in which only one digit changes at a time.
    void toGray(unsigned base, unsigned digits, unsigned value, unsigned gray[digits])
    { 
            unsigned baseN[digits]; // Stores the ordinary base-N number, one digit per entry
            unsigned i;             // The loop variable
    
            // Put the normal baseN number into the baseN array. For base 10, 109 
            // would be stored as [9,0,1]
            for (i = 0; i < digits; i++) {
                    baseN[i] = value % base;
                    value    = value / base;
            }
    
            // Convert the normal baseN number into the Gray code equivalent. Note that
            // the loop starts at the most significant digit and goes down.
            unsigned shift = 0;
            while (i--) {
                    // The Gray digit gets shifted down by the sum of the higher
                    // digits.
                    gray[i] = (baseN[i] + shift) % base;
                    shift = shift + base - gray[i]; // Subtract from base so shift is positive
            }
    }
    
    /* Here is a simple driver program to demonstrate the sequence */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int main(int argc, char* argv[]) {
      if (argc < 3) {
        fprintf(stderr, "Usage: %s N colour...\n", argv[0]);
        argv = (char*[]){"3", "red", "green", "blue"};
      }
      unsigned n = atoi(argv[1]);
      unsigned k = argc - 2;
      argv += 2;
      int maxlen = 1;
      for (unsigned i = 0; i < k; ++i) if (strlen(argv[i]) > maxlen) maxlen = strlen(argv[i]);
      maxlen += 1; 
    
      unsigned gray[n];
      unsigned count = 1; for (unsigned i = n; i; --i) count *= k;
    
      for (unsigned v = 0; v <= count; ++v) {
        toGray(k, n, v, gray);
        for (unsigned i = 0; i < n; ++i) printf("%-*s", maxlen, argv[gray[i]]);
        putchar('\n');
      }
      return 0;
    }
    

    Here are some sample runs of this code:

    ./graynk 3 cyan yellow magenta    ./graynk 2 red green blue    ./graynk 3 black white
    cyan    cyan    cyan              red   red                    black black black 
    yellow  cyan    cyan              green red                    white black black 
    magenta cyan    cyan              blue  red                    white white black 
    magenta yellow  cyan              blue  green                  black white black 
    cyan    yellow  cyan              red   green                  black white white 
    yellow  yellow  cyan              green green                  white white white 
    yellow  magenta cyan              green blue                   white black white 
    magenta magenta cyan              blue  blue                   black black white 
    cyan    magenta cyan              red   blue                   black black black 
    cyan    magenta yellow            red   red   
    yellow  magenta yellow  
    magenta magenta yellow  
    magenta cyan    yellow  
    cyan    cyan    yellow  
    yellow  cyan    yellow  
    yellow  yellow  yellow  
    magenta yellow  yellow  
    cyan    yellow  yellow  
    cyan    yellow  magenta 
    yellow  yellow  magenta 
    magenta yellow  magenta 
    magenta magenta magenta 
    cyan    magenta magenta 
    yellow  magenta magenta 
    yellow  cyan    magenta 
    magenta cyan    magenta 
    cyan    cyan    magenta 
    cyan    cyan    cyan    
    

    Now, to briefly consider the case where it is desired to produce an incomplete Gray sequence of length m < kn. I'll assume here that k>2 because for k=2, not all values of m have solutions. (In particular, if k is 2 and m is odd, there is no solution; this can be proven with a simple parity argument.)

    First, suppose m ≥ 2kn−1.

    I'll call an element in the Gray sequence final if it differs from both its predecessor and its sucessor only in the final position. It can be seen that final elements appear in runs of length k−2, separated by pairs of non-final elements. A final element can be deleted, because its predecessor and its successor must also differ from each other only in their final position. There are 2kn−1 non-final elements, and if m ≥ 2kn−1 we can remove knm final elements and still have a Gray sequence. This subsequence can be generated by with a filter which passes:

    • non-final elements
    • the first m−2kn−1 final elements

    The second case are values of m which are even and less than kkn−1, where k' is the largest even number not greater than k. (That is, k' is k if k is even, and k−1 if k is odd.) In that case, we can make use of a subsequence of a reflected Gray code. Specifically, we use a Gray sequence for the mixed-base number whose digit weights are k' for the high-order digit position and k for all other digits. We start with the sequences:

    • Gn, the sequence generated by the above algorithm
    • Ĝn, the reverse of the sequence generated by the above algorithm

    Now, we define the sequence S as follows:

    • S = ⟨0Gn−1⟩ ⟨1Ĝn−1⟩ … ⟨k'Ĝn−1⟩ (where ⟨xG⟩ means G with x prepended to every element).

    It should be clear that the i-th element from the beginning of S and the i-th element from the end of S differ only in their first digit. Consequently, we can use S to produce a Gray sequence of length 2i for any i up to k'/2.

    Finally, if we need the sequence of length m where m is odd and less than kkn−1, we can use the above construct, leaving out the second element in the sequence.


    Notes

    1. The code puts the high-order digit in the last position in the array, so the "number" is effectively printed low-order digit first. That doesn't change anything important, although in retrospect I would have been better off printing the sequences backwards.

    2. The Wikipedia edit history shows that the cited code is under dispute. In case it disappears again, I put a timestamped (i.e. permanent) URL in a comment in the code.