-2

How should I make it so that the same variable can't be chosen twice? How would I make the smaller of the two variables get removed for the rest of the program. Then the remaining two variables are both shown printed to the screen. I define a, b, c in the case of B and C being chosen B will be eliminated due to it being smaller all of the other get an extra point.

import random

a = 0
b = 1
c = 2

vars = [a, b, c]

x = random.sample(vars, 1)
y = random.sample(vars, 1)

print(x)
print(y)
  • Can or can't? The question title and the description say two different things. – BrokenBenchmark Mar 26 '22 at 00:47
  • @BrokenBenchmark I changed it, my bad I meant to say, can't – Vidhu Thalla Mar 26 '22 at 00:56
  • Welcome to Stack Overflow! Please take the [tour]. What do you mean by "make the smaller of the two variables get removed for the rest of the program"? And why do you define `a, b, c` when they could be inlined like `vars = [0, 1, 2]`? Please [edit] to clarify. It'd help to provide some desired outputs. For more tips, see [ask] and [mre]. – wjandrea Mar 26 '22 at 01:06
  • 2
    Beside the point, but `vars` is a not a great variable name since it [shadows](https://en.wikipedia.org/wiki/Variable_shadowing) the [builtin `vars` function](https://docs.python.org/3/library/functions.html#vars). In example code it's not a big problem, just a bit confusing. But it'd be better to use a more descriptive name in any case. – wjandrea Mar 26 '22 at 01:07
  • @wjandrea By removed for the rest of the program I mean that it can never be chosen again even if another two random variables are chosen. I define a, b, c because I also want to change the value of those variables later. For example after b get eliminated all other variables get an extra point. I'll look at the tour, thanks. – Vidhu Thalla Mar 26 '22 at 01:24

2 Answers2

0

You can do it by keeping track of what variables have already been chosen. If if the values are unique and hashable, a very efficient way to check for membership is to use a set as shown below. If the values aren't unique or hashable you can use a list instead, but that could potentially makes things a lot slower depending on many variables there are.

The code below makes use of an assignment expression (aka as "the walrus operator"), which was added in Python 3.8, to simplify the code slightly.

import random

a = 0
b = 1
c = 2

elements = [a, b, c]
chosen = set()

while (x := random.sample(elements, 1)[0]) in chosen:
    pass
chosen.add(x)

while (y := random.sample(elements, 1)[0]) in chosen:
    pass
chosen.add(y)

print(x)
print(y)

However the better way, if you know the number of samples needed in advance, is to just get them all at once and allow random.sample() handle things because it always returns a list of unique elements when you ask for more than one (and doesn't care whether the values are unique or hashable):

x, y = random.sample(elements, 2)
print(x)
print(y)

Update

If you do this a lot, it might be worth creating a class that encapsulates the bookkeeping need to simplify usage. Whenever one or more samples are needed, you can merely call the instance and it'll keep track of everything. Here's what I mean:

from collections.abc import Set, Sequence
import random

class RandSampler:
    """Return a single element or variable length list of unique elements
    randomly chosen from the population sequence or set. Used for random
    sampling without replacement.
    """
    def __init__(self, population):
        if isinstance(population, Set):
            population = tuple(population)
        if not isinstance(population, Sequence):
            raise TypeError('Population must be a sequence or set.')
        self._elements = list(population)
        self._chosen = set()  # For tracking selections.

    def __call__(self, k=1):
        if k == 1:
            elements, chosen = self._elements, self._chosen  # Optimize access.
            while (sample := random.sample(elements, 1)[0]) in chosen:
                pass
            self._chosen.add(sample)
            return sample
        else:
            samples = random.sample(self._elements, k)
            self._chosen.update(samples)
            return samples

    def reset(self):
        """Clear internal set of elements that have already beem chosen."""
        self._chosen.clear()


if __name__ == '__main__':
    a = 0
    b = 1
    c = 2
    elements = [a, b, c]
    sampler = RandSampler(elements)

    x = sampler()
    y = sampler()
    print(x)
    print(y)

    sampler.reset()
    x, y = sampler(2)
    print()
    print(x)
    print(y)

martineau
  • 119,623
  • 25
  • 170
  • 301
0

Here's another way of doing things that I've implemented as a custom class to simplify its usage. Unlike in my other answer, a separate container of values that have already been "chosen" isn't created and maintained — instead all the sample value are shuffled randomly and stored into a deque from which items can be popped-off as needed. This means is should be very fast regardless of the number of values involved because it doesn't have to check and see if it as returned them before.

from collections import deque
from collections.abc import Iterable
import random

class RandSampler:
    """ Return a single element or variable length list of unique elements
    randomly chosen from an iterable values each time an instance is called.
    Does random sampling without replacement.
    """
    def __init__(self, values):
        if not isinstance(values, Iterable):
            raise TypeError('Population must be an iterable.')
        values = list(values)  # Temp list of the values.
        random.shuffle(values)
        self._elements = deque(values)  # Save shuffled list in a deque.

    def __call__(self, k=1):
        """Returns one or more elements of values without repeated elements."""
        return (self._elements.pop() if k == 1
                else [self._elements.pop() for _ in range(k)])


if __name__ == '__main__':
    a = 0
    b = 1
    c = 2
    elements = [a, b, c]
    sampler = RandSampler(elements)

    x = sampler()
    y = sampler()
    print(x)
    print(y)

    sampler = RandSampler(elements)
    x, y = sampler(2)
    print()
    print(x)
    print(y)

martineau
  • 119,623
  • 25
  • 170
  • 301