0

I have a task where I have to fill an array with 16 random numbers, in random indexes.

4 of those elements have to be -1, and all the other left indexes have to be 0-15, but different from another, meaning it is impossible for two different indexes have the same number (0-15).

Filling 4 random indexes is easy, and so is filling the other indexes with random numbers between 0-15, but how do I feel them in such way that they are necessarily different from each other?

There are also two more conditions which complicate this task much more, the first one is that the number of the index cannot have the same number within it, meaning arr[3] == 3 is impossible, and another condition is that

    (m[p] == j && m[j] == mp && m != j)

is something that we must take care of so it won't happen. For example, if arr[2] == 0 and arr[0] == 2, we have to change it so it won't happen.

I'm so confused, I had literally sat 8 hours yesterday in front of this, trying all sort of things, and I have no idea, honestly..

void FillArray(int *sites, int length) 
{
    int checkarr[N] = { 0 };
    int i, 
        cnt = 0, 
        j = 0, 
        t = 0, 
        k, 
        times = 0;
    int *p = sites;

    while (cnt < C)
    {
        i = rand() % length;
        if (p[i] - 1)
            cnt = cnt;
        p[i] = -1;
        cnt++;
    }

    while (j < length) 
    {
        if (p[j] == -1) j++;
        else 
        {
            p[j] = rand() % length;
            checkarr[p[j]]++;
            j++;
        }
    }

    j =0;
    while (j<length)
    {
        for (k=0; k<length;k++)
        {
            while (checkarr[k] > 1)
            {
                while (t < length) 
                {
                    if (p[j] == p[t] && p[j] != -1 && j != t)
                    {
                        checkarr[p[t]]--;
                        p[t] = rand() % length;
                        checkarr[p[t]]++;
                        times++;
                    }
                    else t++;
                }

                if (times < 11) 
                { 
                    j++;
                    t = 0;
                    times = 0;
                }
            }
        }
    }
}

I tried using the Fisher-Yates shuffle method, but for somereason it doesn't even fill the array. I don't know why

while (j

    if (p[j] == -1)
        j++;
    else {
        while (m < length) {
            m = rand() % length;
            if (helpingArray[m] != -2)
            {
                p[j] = helpingArray[m];
                helpingArray[m] = -2;
                j++;
            }
            else if (helpingArray[m] == -2)
            {
                j = j;
            }

            for (w = 0; w < length; w++)
            {
                if (helpingArray[w] == -2)
                    count++;
            }
            if (count == 12) {
                m = length;
            }
        }
    }
}
}  
  • 1
    Why not keep generating numbers until they fit? Simply check every time, and try again if it hasn't worked. – AJF Jan 08 '19 at 18:45
  • can you show us your code? off the top of my head I would use 2 aux arrays to help with first 2 conditions – H.cohen Jan 08 '19 at 18:47
  • I know, I simply don't know how to translate it into code.. Too much whiles and ifs and it's hard for me to handle lol – סמי זלדין Jan 08 '19 at 18:47
  • Just fill the array with the values you want, but do random _permutations_ – Ctx Jan 08 '19 at 18:47
  • still, showing us what you did could help us see what the problem is in your code – H.cohen Jan 08 '19 at 18:49
  • @H.cohen Too long to put it in here :\ – סמי זלדין Jan 08 '19 at 18:51
  • Well I'll put two parts then: void FillArray(int *sites, int length) { int checkarr[N] = { 0 }; int i, cnt = 0, j = 0, t = 0, k, times = 0; int *p = sites; while (cnt < C) { i = rand() % length; if (p[i] - 1) { cnt = cnt; } p[i] = -1; cnt++; } while (j < length) { if (p[j] == -1) j++; else { p[j] = rand() % length; checkarr[p[j]]++; j++; } } – סמי זלדין Jan 08 '19 at 18:51
  • The question from the title called "shuffle". And here is a [Fisher-Yates](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) algorithm for that. The other conditions are extra though, which would require more clarification. – Eugene Sh. Jan 08 '19 at 18:52
  • j = 0; while (j 1) { while (t < length) { if (p[j] == p[t] && p[j] != -1 && j != t) { // p[2]=p[3] checkarr[p[t]]--; // checkarr[p[3]]-- p[t] = rand() % length; //p[3] = random checkarr[p[t]]++; //checkarr[p[3]]++; times++; // times +1 } else t++; } if (times < 11) { j++; t = 0; times = 0; } } } } } – סמי זלדין Jan 08 '19 at 18:52
  • 2
    @סמיזלדין Please delete these comments and put it as edit into your question – Ctx Jan 08 '19 at 18:53
  • 2
    Please _edit_ your question and put your code in a code block rather than adding it as a comment. – Craig Estey Jan 08 '19 at 18:53
  • Also, please clarify what you mean by the `-1` values. If you seed an array with 16 random values in the range 0-15, where do you want the `-1` values to go? – Craig Estey Jan 08 '19 at 18:55
  • @CraigEstey there you go – סמי זלדין Jan 08 '19 at 19:00
  • @CraigEstey Random places, it doesn't matter where. four elements need to be -1, while all the other twelve have to between 0-15 – סמי זלדין Jan 08 '19 at 19:01
  • is N defined 16 and C defined 12? – H.cohen Jan 08 '19 at 19:06
  • @H.cohen, No, N is 16 and C . I'm using C to fill 4 random elements in -1, and then using N to go through all the elements and putting random numbers 0-15 only in those who are not filled with -1. – סמי זלדין Jan 08 '19 at 19:13
  • 4
    Please provide all information in your question. Preferrably also turn it into a [mcve]. – Yunnosch Jan 08 '19 at 19:21
  • you can init an array greedily and then apply random swaps in the array to randomize it, while following the rules that you should respect – Greg K. Jan 08 '19 at 19:41
  • @EugeneSh. I tried it, and I have no idea why it doesn't work? I put it in the post – סמי זלדין Jan 08 '19 at 19:51
  • The condition `(m[p] == j && m[j] == mp && m != j)` is not correct; `mp` is not defined (a typo?), and `m != j` does not make sense since `m` is an array and `j` is an integer. What is intended there? If it is intended to be `(m[p] == j && m[j] == p && p 1= j)`, then, except for the −1 elements, you are looking for a swap-free derangement. (A derangement is a permutation where no element is mapped to itself (`m[j] != j`), and swap-free means there is no pair of elements `m[a] == b && m[b] == a`. – Eric Postpischil Jan 09 '19 at 14:47
  • About generating derangements, see these [Stack Overflow](https://stackoverflow.com/questions/25200220/generate-a-random-derangement-of-a-list) and [Stack Exchange](https://math.stackexchange.com/questions/302057/generating-a-random-derangement) questions. The latter points to a rejection-free algorithm. – Eric Postpischil Jan 09 '19 at 14:52
  • What probability distribution do you want? Should all possibilities that satisfy the constraints be equally likely? – Eric Postpischil Jan 09 '19 at 18:22

5 Answers5

1

I hope this will help, I tried to stay in the line with your first draft and what you were going for, just to note that this should work for an N length array. I changed the conditions on your second while to check the conditions before placing the value- and now you don't need to go over the set array and check and update the values.

you can also go another way as was commented here and just fill the array with values with help of one aux array to check each value is used only once and then randomly swap the indexes under the conditions.

I wrote this down but I didn't run tests- so make sure you understand whats going on and upgrade it to your needs. I do recommend using only one aux array, easy on memory and less whiles and checks.

GOOD LUCK

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define N 16
#define C 4

void FillArray(int *sites, int length) {
/*these aux arrays will keep track if an index was fill of if a value was used*/
int checkarrIndex[N] = { 0 };
int checkarrVal[N] = { 0 };

int i, cnt = 0, full=0; /*full is when all index are filled */
int *p = sites;
while (cnt < C) {
    i = rand() % length;
    if (checkarrIndex[i] == 0) /* checkarrIndex will let you know if an index has been gvin a value*/
    {
        ++checkarrIndex[i]; /*now  checkarrIndex[i] will be one so this index is now not valid for placement next time*/
        p[i] = -1;
        ++full;/*at the end of this while full will equal 4*/
        cnt++;
    }

}
while (full < length) /*here you need to draw a random index and a random value for it, 
                  not just a random value for a fixed index like you did, if I got this wrong just
                  go over the free indexes and place a rand value one at a time in the same manner*/
{
    int index; /*will store new random index */
    int value; /*will store new random value */
    index = rand() % N;
    value = rand() % N;/*max value is 15*/
    while(checkarrIndex[index]!= 0) /*check if this index was already placed */
    {
        index = rand() % N; /*try a another one */
    }
    /*I made this while loop to check all the con before filling the array */
    while(checkarrVal[value]!= 0 || p[value]== index || index == value) /*check if this value was already used  or if p[i]=j&&p[j]=i cond happens and make sure p[a] != a*/
    {
        value = rand() % N; /*try a another one */
    }
    ++checkarrIndex[index];/*set index as used */
    ++checkarrVal[value];/*set value as used */
    p[index] = value;
    ++full; /*another place was filled */


  }
}
static void PrintArray(int* arr, size_t size)
{
    int i = 0 ;
    for (i = 0 ; i< size; ++i)
    {
        printf("%d| ", arr[i]);
    }
    printf("\n");
}
int main(void)
{
    int array[N] = {0};
    FillArray(array, N);
    PrintArray(array, N);
    return 0;
}
H.cohen
  • 517
  • 3
  • 9
  • there are always extra values left so I don't think an endless loop is possible. good point about p[value]==index. It would be wise to check first that the index that equals value was set before checking that. as I wrote- did not test it thoroughly. @EricPostpischil – H.cohen Jan 09 '19 at 15:51
  • Okay, I see the loop does not occur because you place the four −1 elements first and increment `full`, so there are always five or six values remaining in the pool when assigning the last one or two elements, respectively. (I have to think how the probability distribution is affected.) You should rename `full` and change its description (which says “full is when all index are filled”). That makes it sound like a Boolean, but it is actually a count of the number filled so far. – Eric Postpischil Jan 09 '19 at 16:07
  • I think *Isfull* is more boolean, but I will give it some thought. tnx for your input =) @EricPostpischil – H.cohen Jan 09 '19 at 16:10
  • I think the distribution is affected. Consider the algorithm applied to N = 4, C = 2. For the solutions where the last two elements are −1, the possibilities for the first two elements are [1, 2], [1, 3], [2, 0], [2, 1], [2, 3], [3, 0], [3, 1], and [3, 2]. So, given the −1 assignments, 1 should appear in position 0 two times out of eight, but this algorithm generates it one time in three . (If it does position 0 first, it picks 1 one time of three. If it does position 1 first, it picks 0, 2, or 3, and then the probability for 1 in position 0 is 0, ½, ½, respectively. So ½•⅓ + ½•(0+½+½)/3 = ⅓.) – Eric Postpischil Jan 09 '19 at 16:58
  • what kind of distribution did you deduce the OP was going for? the constrains of the presented algorithm effect the "randomness" of the output, but whether the -1 random indexes are allocated first- last or in between should not change the probability of each outcome. i'm not sure I understood what you were getting at in your example. @EricPostpischil – H.cohen Jan 09 '19 at 18:09
  • Mathematicians use “random” in a technical sense to refer to a variable with possible values that are outcomes of some random process, which may have different distributions. Non-technical users often use the word “random” to include a uniform distribution. There are three possibilities: (a) OP intended a uniform distribution, which this answer does not achieve. (b) OP intended some particular distribution, which has not been stated, and so there is no reason to think the distribution achieved by this answer conforms to it. (c) OP did not intend any particular distribution, in which case… – Eric Postpischil Jan 09 '19 at 18:19
  • … any solution is satisfactory, including a routine that always returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, −1, −1, −1, −1], as that is the result of drawing from a distribution with a probability of 1 for that result and 0 for others. I think (a) is most likely, but, if it is (b), the question needs to be clarified. – Eric Postpischil Jan 09 '19 at 18:20
1

I'm not completely sure, but I think the following meets all your special constraints [hopefully].

The random list function is a variation on Fisher Yates. You could recode it to use Durstenfeld if you wish.

I'm not sure that the constraints can be done cleanly in a single pass. That is, apply them while generating the random list.

What I've done is to generate a simple random list. Then, try to detect/fix (by swapping) some of the constraint violations.

Then, fill with negative values, trying to fix the self constraint violations if possible.

If that can't be done, repeat the whole process.

Anyway, here is my version. I split up the large function into several smaller ones. I also added a check function and a diagnostic loop. It is quite a bit different from yours, but other answers did this as well:

#include <stdio.h>
#include <stdlib.h>

#define NEG     4

int opt_N;
int opt_v;
int opt_T;

#ifdef DEBUG
#define dbg(_fmt...) \
    do { \
        if (opt_v) \
            printf(_fmt); \
    } while (0)
#else
#define dbg(_fmt...)            /**/
#endif

// prtarray -- print array
void
prtarray(int *arr,int len)
{
    int idx;
    int val;
    int hangflg = 0;
    int cnt = 0;

    for (idx = 0;  idx < len;  ++idx) {
        val = arr[idx];
        if (val < 0)
            printf(" [%2.2d]=%d",idx,val);
        else
            printf(" [%2.2d]=%2.2d",idx,val);
        hangflg = 1;

        if (++cnt >= 8) {
            printf("\n");
            cnt = 0;
            hangflg = 0;
            continue;
        }
    }

    if (hangflg)
        printf("\n");
}

// fillrand -- generate randomized list (fisher yates?)
void
fillrand(int *arr,int len)
{
    char idxused[len];
    char valused[len];
    int fillcnt = 0;
    int idx;
    int val;

    for (idx = 0;  idx < len;  ++idx) {
        idxused[idx] = 0;
        valused[idx] = 0;
    }

    for (fillcnt = 0;  fillcnt < len;  ++fillcnt) {
        // get random index
        while (1) {
            idx = rand() % len;
            if (! idxused[idx]) {
                idxused[idx] = 1;
                break;
            }
        }

        // get random value
        while (1) {
            val = rand() % len;
            if (! valused[val]) {
                valused[val] = 1;
                break;
            }
        }

        arr[idx] = val;
    }
}

// swap2 -- swap elements that are (e.g.) arr[i] == arr[arr[i]])
int
swap2(int *arr,int len)
{
    int idx;
    int lhs;
    int rhs;
    int swapflg = 0;

    dbg("swap2: ENTER\n");

    for (idx = 0;  idx < len;  ++idx) {
        lhs = arr[idx];
        rhs = arr[lhs];

        // don't swap self -- we handle that later (in negfill)
        if (lhs == idx)
            continue;

        if (rhs == idx) {
            dbg("swap2: SWAP idx=%d lhs=%d rhs=%d\n",idx,lhs,rhs);
            arr[idx] = rhs;
            arr[lhs] = lhs;
            swapflg = 1;
        }
    }

    dbg("swap2: EXIT swapflg=%d\n",swapflg);

    return swapflg;
}

// negfill -- scan for values that match index and do -1 replacement
int
negfill(int *arr,int len)
{
    int idx;
    int val;
    int negcnt = NEG;

    dbg("negfill: ENTER\n");

    // look for cells where value matches index (e.g. arr[2] == 2)
    for (idx = 0;  idx < len;  ++idx) {
        val = arr[idx];
        if (val != idx)
            continue;

        if (--negcnt < 0)
            continue;

        // fill the bad cell with -1
        dbg("negfill: NEGFIX idx=%d val=%d\n",idx,val);
        arr[idx] = -1;
    }

    // fill remaining values with -1
    for (;  negcnt > 0;  --negcnt) {
        while (1) {
            idx = rand() % len;
            val = arr[idx];
            if (val >= 0)
                break;
        }

        dbg("negfill: NEGFILL idx=%d\n",idx);
        arr[idx] = -1;
    }

    dbg("negfill: EXIT negcnt=%d\n",negcnt);

    return (negcnt >= 0);
}

// fillarray -- fill array satisfying all contraints
void
fillarray(int *arr,int len)
{

    while (1) {
        // get randomized list
        fillrand(arr,len);

        if (opt_v)
            prtarray(arr,len);

        // swap elements that are (e.g. arr[i] == arr[arr[i]])
        while (1) {
            if (! swap2(arr,len))
                break;
        }

        // look for self referential values and do -1 fill -- stop on success
        if (negfill(arr,len))
            break;
    }
}

// checkarray -- check for contraint violations
// RETURNS: 0=okay
int
checkarray(int *arr,int len)
{
    int idx;
    int lhs;
    int rhs;
    int negcnt = 0;
    int swapflg = 0;

    dbg("checkarray: ENTER\n");

    if (opt_v)
        prtarray(arr,len);

    for (idx = 0;  idx < len;  ++idx) {
        lhs = arr[idx];
        if (lhs < 0) {
            ++negcnt;
            continue;
        }

        rhs = arr[lhs];

        if (rhs == idx) {
            printf("checkarray: PAIR idx=%d lhs=%d rhs=%d\n",idx,lhs,rhs);
            swapflg = 2;
        }

        if (lhs == idx) {
            printf("checkarray: SELF idx=%d lhs=%d\n",idx,lhs);
            swapflg = 1;
        }
    }

    if (negcnt != NEG) {
        printf("checkarray: NEGCNT negcnt=%d\n",negcnt);
        swapflg = 3;
    }

    dbg("checkarray: EXIT swapflg=%d\n",swapflg);

    return swapflg;
}

int
main(int argc,char **argv)
{
    char *cp;
    int *arr;

    --argc;
    ++argv;

    opt_T = 100;
    opt_N = 16;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        case 'N':
            opt_N = (cp[2] != 0) ? atoi(cp + 2) : 32;
            break;

        case 'T':
            opt_T = (cp[2] != 0) ? atoi(cp + 2) : 10000;
            break;

        case 'v':
            opt_v = ! opt_v;
            break;
        }
    }

    arr = malloc(sizeof(int) * opt_N);

    for (int tstno = 1;  tstno <= opt_T;  ++tstno) {
        printf("\n");
        printf("tstno: %d\n",tstno);
        fillarray(arr,opt_N);
        if (checkarray(arr,opt_N))
            break;
        prtarray(arr,opt_N);
    }

    free(arr);

    return 0;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • In `fillrand`, there is no point in selecting both `idx` and `val` randomly. One of them could be iterated consecutively without affecting the distribution. And the other could be selected from a pool of remaining unchosen values (Fisher-Yates) rather than randomly selecting from all values and rejecting previously chosen values, and that would improve the run time. – Eric Postpischil Jan 09 '19 at 15:00
  • It is not clear the method of selecting elements that map to themselves or that were in swaps as elements to receive the −1 values achieves the same distribution as a uniform distribution on all possible assignments matching the constraints specified in the question. – Eric Postpischil Jan 09 '19 at 15:01
0

My C is rusty, and I don't want to implement a Fisher-Yates shuffle or deal with the bad behavior of C PRNGs, so I'm expressing the algorithm in pseudo-code. Okay, I lie. It's Ruby, but it reads like pseudo-code and is heavily commented to show the logic of the solution. Consider the comments to be the solution, and the stuff in between a concrete illustration that the algorithm being described actually works.

N = 16

# Create + populate an array containing 0,...,N-1
ary = Array.new(N) { |i| i }

# Shuffle it
ary.shuffle!  

# Iterate through the array.  If any value equals its index, swap it with
# the value at the next index, unless it's the last array element
ary.each_index { |i| ary[i], ary[i + 1] = ary[i + 1], ary[i] if ary.length - i > 1 && ary[i] == i }

# If the last element equals its index, swap it with any other element
# selected at random.  The rand function generates a random integer
# between 0, inclusive, and its argument, exclusive.
last = ary.length - 1
if ary[last] == last
  random_index = rand(last)
  ary[last], ary[random_index] = ary[random_index], ary[last]
end

# Replace 4 randomly selected entries with -1
4.times { ary[rand(ary.length)] = -1 }

# The array now contains unique elements (except for the -1's),
# none of which are equal to their index value
p ary

# Produces, e.g.:  [4, 10, -1, 5, 9, -1, 15, 14, 7, 8, 12, 1, -1, 0, -1, 2]

All of this takes O(N) work. If your last constraint is violated, reject the solution and retry.

pjs
  • 18,696
  • 4
  • 27
  • 56
  • An [easy] optimization could be to replace any [up to 4] elements that violate the final constraint (i.e. `ary[x] == x`) with `-1` and randomly place the remaining `-1` values (if any). The detection/replacement could be done in a single pass, counting the number of final constraint violation cells. If it's <= 4, we're done (again, randomly placing any remaining `-1` values). Otherwise, as you've mentioned, reject the solution and retry. – Craig Estey Jan 08 '19 at 22:45
  • Eliminating elements that map to themselves by swapping them with a neighbor will generate a higher probability of `m[i+1] == i` than a uniform distribution would. – Eric Postpischil Jan 09 '19 at 15:20
  • It is not clear that generating full assignment satisfying the conditions excluding the −1 elements and then replacing some elements with −1 generates a uniform distribution over the solutions satisfying all the conditions. I think it may disfavor solutions where the assignments would have contained a swap or self-mapping element that is concealed by the −1 elements. – Eric Postpischil Jan 09 '19 at 15:21
  • @EricPostpischil Uniformity was never specified as a requirement. Elements that map to each other is covered by my last statement - check for it, and reject & retry if found. – pjs Jan 09 '19 at 15:30
  • @pjs: If uniformity is not a requirement, then a routine that always returns [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, −1, −1, −1, −1] satisfies the question, since it is simply a distribution with probability 1 for that solution and 0 for the others. The colloquial use of “random” often means uniform; naîve requestors for random samples usually mean to draw with uniform distribution. If you do not believe they intended a uniform distribution, you should request clarification. Certainly whatever distribution happens to fall out of an algorithm cobbled together is not what was intended. – Eric Postpischil Jan 09 '19 at 15:47
  • @EricPostpischil Randomness does not equate to uniformity, it is defined by lack of predictability. You seem to be conflating randomness with entropy. It's true that a uniform distribution has maximum Shannon entropy, but I doubt you would be so bold as to claim that other distributions, such as the Gaussian, are not random. Randomness is all that was requested. – pjs Jan 09 '19 at 20:50
  • @pjs: I have not made any claims about randomness or entropy except to state that non-technical speakers often simply ask for “random” when they want uniform. It is not a statement about the mathematics, just an observation of many instances. It is unlikely that mere randomness is “all” that was desired and they do not have any constraints on it, as what purpose could it serve to have some grossly skewed distribution without even knowing anything about it? – Eric Postpischil Jan 10 '19 at 02:20
0

I believe the following generates a solution to the constraints with uniform distribution over all the solutions that satisfy the constraints:

  • Put the numbers 0 to 15 in pool A.
  • Put the numbers 0 to 15 in pool B.
  • 12 times, draw a number a from pool A and a number b from pool B (in each case drawing randomly with uniform distribution and removing the drawn number from its pool, so it will not be chosen again later). Assign m[a] = b.
  • For each of the four numbers a remaining in pool A, assign m[a] = -1.
  • For all i from 0 to 15 (inclusive) and all j from i to 15 (inclusive), test whether m[i] == j && m[j] == i (note that this tests for both swaps and for m[i] == i, as it includes i == j). If such a case is found, reject the assignments and repeat the algorithm from the beginning.

I expect algorithmic improvements are possible to reduce or eliminate the frequency of rejection, but this establishes a baseline correct algorithm.

It is also possible to use a single pool instead of two and instead do some rearranging when the −1 elements are assigned, but the algorithm above is more easily expressed.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
-1

I am confused with your description. For placing N elements into N positions, I have a solution.

Question: Place N elements into N positions with constraints:

(1) arr[i] != i; 
(2) if arr[i] = j, then arr[j] != i

Solution: For current element i (0 <= i < N)

(1) Find candidate position count
    (a) count = N - i
    (b) if arr[i] is empty              =>    count -= 1
        else if arr[arr[i]] is empty    =>    count -= 1
(2) Select a random position from candidates
    (a) relative_index = random() % count
        (Note: relative_index means the position index in candidates)
    (b) Find absolute_index by searching candidates
        a candidate index j satisfies following constrains
            <1> arr[j] is empy
            <2> j != i
            <3> j != arr[i] when arr[i] is not empty
Joy Allen
  • 402
  • 3
  • 8