11

I am new in programming. I have an array containing, let's say 20 elements and first I want to randomly select two elements from it and record them. Then I will select two elements from the array of twenty elements again and record them. The process continues, let's say 10 times and in these 10 draws, I do not want to repeat any of the two elements that I have already drawn.

I used RandomSample to generate random numbers.

j = 0;
For[i = 0, i < 2, i++,
  Yarray[j] = RandomSample[Range[8], 2];
  Print[Yarray[j]];
  j++;]
Sjoerd C. de Vries
  • 65,815
  • 14
  • 188
  • 323
Daniel
  • 319
  • 6
  • 12

3 Answers3

13

You can do this in a number of ways:

  • Use RandomSample to generate a random list with twice the sample size you need and partition:

Example: Draw 5 random pairs from Range[20]:

 Partition[RandomSample[Range[20], 10], 2]
 (*  result:  {{20, 14}, {19, 8}, {5, 1}, {12, 15}, {17, 2}} *)
  • Nest a composite function that draws one pair and deletes it from the base list sequentially:

Example: Draw 5 random pairs from the list of letters {a,...,z}:

samples = {}; 
Nest[( samples = Append[samples, RandomSample[#, 2]]; 
      Complement[#, Flatten@samples]) &,  CharacterRange["a", "z"], 5]; 
samples
(* result: {{"m", "c"}, {"g", "h"}, {"r", "l"}, {"n", "x"}, {"u", "a"}} *)
kglr
  • 394,356
  • 18
  • 477
  • 896
8

Given

elements = Table[Unique["el"], {20}]

{el3, el4, el5, el6, el7, el8, el9, el10, el11, el12, el13, el14, \ el15, el16, el17, el18, el19, el20, el21, el22}

What you want is equivalent to

Partition[RandomSample[elements], 2]

{{el22, el17}, {el12, el6}, {el5, el19}, {el7, el18}, {el9, 
  el4}, {el13, el21}, {el11, el3}, {el10, el16}, {el14, el15}, {el8, 
  el20}}

If you want less sets (say 3), you can always do

Partition[RandomSample[elements], 2]~Take~3

{{el4, el18}, {el10, el8}, {el19, el21}}

or more efficiently

Partition[RandomSample[elements, 2 3], 2]

However, if we want to implement your algorithm as you described it (3 times in this example)

Reap[Nest[Complement[#, Sow@RandomSample[#, 2]] &, elements, 3]][[2, 1]]

I'll break this down. Complement[#, Sow@RandomSample[#, 2]] &. This is a function that takes one argument. The argument should be a list with the remaining elements. It takes 2 random elements of those (RandomSample[#, 2]) and Sows them (puts them aside for later recollection). Then, it returns the original list without those 2 elements.

We apply that function to the whole list of elements, 3 times (Nest[function, elements, 3])

Finally, we collect the stuff we put aside in the process (Reap[...][[2, 1]])

Rojo
  • 42,601
  • 7
  • 96
  • 188
6

Here is one possible solution:

SetAttributes[draw, HoldFirst]

draw[var_, n_] /; n <= Length@var :=
  # &[Take[var, n], var = Drop[var, n]]

Initialize the set:

set = RandomSample@Range@20;

Draw your elements. I use Table rather than the For loop.

Table[
 draw[set, 2],
 {10}
]

Output:

{{7, 16}, {2, 11}, {17, 14}, {9, 12}, {10, 3},
 {15, 13}, {19, 1}, {20, 6}, {8,4}, {18, 5}}

Confirm that set is now empty:

set
{}

So how does this work? Let me break it down. The set of shuffled numbers is stored as an assignment to a Symbol named set. To handle this Symbol itself my function needs a Hold* attribute. I choose HoldFirst:

SetAttributes[draw, HoldFirst]

I then define a function using pattern matching that takes two parameters: var and n, and I include a check on these elements with Condition (/;). I want my function to do two things:

  1. take the first two elements from the set given as var. This is done with Take[var, n]

  2. remove these from set itself. This can be done with var = Drop[var, n]

Since I want to return the result of the first operation, but it must be performed before the second, I use a "pure function" (using Function and Slot): # & which simply returns (only) the first argument it is given, and then give each of the operations above as arguments:

draw[var_, n_] /; n <= Length@var :=
  # &[Take[var, n], var = Drop[var, n]]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371