# Randomly Choose from list but meet conditions

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.

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 *)
• 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? Jan 5, 2021 at 21:54
• Thank you! What if there are no such values? Would this run indefinitely then? How could I prevent that from happening? Jan 5, 2021 at 21:58
• @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. Jan 5, 2021 at 22:18

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} *)

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 *)
• Doesn't this assume that the list is sorted? I was not sure that it would be. Jan 5, 2021 at 21:30
• Yes that's right. Is this also needed for an unsorted list? Then you would need MemberQ Jan 5, 2021 at 21:32
• 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. Jan 5, 2021 at 21:39

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]]

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]

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]]

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.)