4
$\begingroup$

I have a list of integers from 0-99, but some of the integers have been randomly removed.

I want to pick a random integer that is still in the list, let’s call it ‘a’ but if a+1, a+2, and a+3 are not also in the list I need to pick a new random integer from the list and check again until I get a number that works.

$\endgroup$
3
$\begingroup$

Here is a procedural approach that does not require the list to be sorted.

I will first make some test data:

testlist = RandomSample[Range[0, 99], 50];

Then define a selector function:

testlist = RandomSample[Range[0, 99], 50];

ClearAll[selector]
selector[list_] :=
 TimeConstrained[
  Module[{choice},
   While[! 
     ContainsAll[list, (choice = RandomChoice[list]) + {0, 1, 2}]];
   choice
   ],
  0.5,
  "None found"
  ]

selector[testlist]

Wrapping the function in TimeConstrained ensures that, in case there are no such elements, the function won't get stuck in an endless loop. The return value could be the default ($Aborted), or it could be modified to taste as shown above. For example:

selector[Range[0, 99, 2]]

(* Out: None found *)
$\endgroup$
3
  • $\begingroup$ We need a chosen at random from the list, but also a+1, a+2, and a+3, that is we need 4 sequential integers, so (choice = RandomChoice[list]) + {1, 2, 3}. Am I right? $\endgroup$
    – creidhne
    Jan 5 at 21:54
  • $\begingroup$ Thank you! What if there are no such values? Would this run indefinitely then? How could I prevent that from happening? $\endgroup$ Jan 5 at 21:58
  • $\begingroup$ @Kenneth Good point. I've edited the function to add a time constraint so, if it gets stuck in a loop, it aborts. Execution is always very fast, so even a short time constraint should work here. $\endgroup$
    – MarcoB
    Jan 5 at 22:18
5
$\begingroup$

Here's a way that constructs a set of candidates by sorting the list and then breaking it into runs of consecutive elements and only keeping elements with enough successors to be viable. This is probably a good approach if you are going to be making multiple draws.

RandomSeed[1337];
test = RandomInteger[{0, 99}, 95]
(* {37, 84, 80, 98, 26, 32, 51, 65, 19, 33, 10, 88, 25, 13,
    77, 68, 20, 39, 27, 83, 89, 75, 78, 87, 22, 58, 94, 49, 
    70, 73, 60, 44, 59, 86, 30, 12, 90, 5, 55, 63, 38, 35, 
    72, 81, 31, 36, 29, 93, 95, 8, 34, 15, 9, 42, 52, 50, 
    48, 4, 92, 71, 56, 2, 69, 54, 74, 91, 45, 64, 0, 82, 
    96, 85, 16, 6, 7} *) 
runs = Split[Sort[test], Subtract /* EqualTo[-1]];

Only runs of length $ > 3 $ will contain elements $ a $ such that $ a + 1 $, $ a + 2 $, and $ a + 3 $ are present:

longRuns = Select[runs, Length /* GreaterThan[3]];

Drop elements that don't have enough successors and flatten.

candidates = Flatten[Drop[#, -3] & /@ longRuns]
(* {4, 5, 6, 7, 29, 30, 31, 32, 33, 34, 35, 36, 48, 49, 68, 
    69, 70, 71, 72, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
    90, 91, 92, 93} *)

Now you can make random draws.

RandomChoice[candidates, 10]
(* {34, 85, 89, 90, 4, 81, 83, 29, 93, 85} *)
$\endgroup$
2
$\begingroup$

To create some data for testing we get even numbers <100 and insert one odd number:

d = Table[i, {i, 0, 99, 2}];
d = Insert[d, 33, 18];

Now we can pick out a sequence of 3 integers in a row by:

SequenceCases[d, {x_, y_, z_} /; z == (y + 1) == x + 2, 1]
(*  {{32, 33, 34}}  *)

If you only want the first number, you would say:

SequenceCases[d, {x_, y_, z_} /; z == (y + 1) == x + 2 -> x, 1][[1]]
(* 32 *)
$\endgroup$
3
  • $\begingroup$ Doesn't this assume that the list is sorted? I was not sure that it would be. $\endgroup$
    – MarcoB
    Jan 5 at 21:30
  • $\begingroup$ Yes that's right. Is this also needed for an unsorted list? Then you would need MemberQ $\endgroup$ Jan 5 at 21:32
  • $\begingroup$ I don't know, maybe OP could chime in. You could just Sort the list before applying SequenceCases, but that would leave a strong bias towards always selecting the lowest number, which is far from random. Perhaps return all possible sequences, then select one randomly, e.g. with RandomChoice? Even then, you would still have some bias. Consider a list that contains .., 17, 18, 19, 20, 21, 22, .... SequenceCases will return the disjoint sequences {17, 18, 19} and {20, 21, 22}, so you could select 17 or 20. However, 18 and 19, which are viable choices, would be overlooked. $\endgroup$
    – MarcoB
    Jan 5 at 21:39
2
$\begingroup$

Since the input list not too large, we can also play with alternative approaches, among them RelationGraph:

SeedRandom[1]
length = 60;
inputlist = RandomSample[Range[0, 99], length]
{80, 14, 0, 67, 3, 65, 23, 68, 74, 15, 24, 4, 83, 70, 1, 30, 48, 25, 44, 73,
 69, 56, 47, 28, 92, 26, 75, 10, 43, 33, 81, 18, 38, 29, 84, 17, 27, 85, 5, 40,
 82, 22, 2, 39, 36, 20, 99, 46, 52, 54, 35, 9, 63, 42, 95, 89, 98, 49, 64, 45}
rg = RelationGraph[#2 == # + 1 &, inputlist, ImageSize -> Large, 
  VertexSize -> .7, VertexStyle -> White, 
  VertexLabels -> Placed["Name", Center]]

enter image description here

candidates = VertexList[rg, _?(Length[VertexOutComponent[rg, #]] >= 4 &)]
 {80, 0, 67, 23, 24, 1, 25, 44, 26, 43, 81, 27, 82, 22, 2, 46, 42, 45}
HighlightGraph[rg, Style[#, Orange] & /@ candidates]

enter image description here

Use RandomSample or RandomChoice to pick any number of elements from candidates:

SeedRandom[123]
randomselection = RandomSample[candidates, 5]
{2, 80, 67, 25, 44}
HighlightGraph[rg, 
 Join[Style[# + Range[3], Yellow] & /@ randomselection, List /@ randomselection]]

enter image description here

$\endgroup$
1
$\begingroup$

First generate a random sample of integers between 0 and 99 (and this can be with or without replacement - but I've used "without replacement" in the example):

nOriginal = 60;
original = RandomSample[Range[0, 99], nOriginal]

Now find all of the available starting numbers (a) that have a+1, a+2, and a+3 available:

available = Select[original,
  MemberQ[original, # + 1] && MemberQ[original, # + 2] && MemberQ[original, # + 3] &]

Now you can select from available with or without replacement as appropriate for your sampling objective.

This approach should work whether or not the original list has duplicate numbers or not. (However, this approach is much slower than that of @Pillsy.)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.