3

I've been struggling to find a simple enough solution to the following problem:

Let's say I have a class with two variables defined, like below (points on a x-y grid):

class Target:
    def __init__(self):  
        self.x = random.randint(1, 9)  
        self.y = random.randint(1, 9)  

I need to generate a given amount of those, let's say 10:

for i in range(10):  
    t = Target()  

Of course, sometimes the coordinates overlap.

What would be the simplest way to assure none of the objects have the same x and y variables? I've been trying some solutions, but they feel overwrought and I feel there must be a relatively simple one.

5
  • please use back ticks for insertig code in code formatting Commented Mar 9, 2023 at 10:11
  • It is not clear whether you want to avoid creating duplicated instances (what would you do after creating 81 instances?) or you want 10 different instances (which would better be in a container, maybe a set). Commented Mar 9, 2023 at 10:20
  • 1
    Take a look at this: Prevent duplicate objects in python Commented Mar 9, 2023 at 10:21
  • By "overlap" you mean a.x == b.x and a.y == b.y, right? Commented Mar 9, 2023 at 11:21
  • Yes, that's what I meant Commented Mar 9, 2023 at 12:34

3 Answers 3

2

First, adjust your class to take the x and y coordinates as initialization parameters.

class Target:
    def __init__(self, x, y):  
        self.x = x
        self.y = y 

Then you could generate a list of all valid coordinate pairs.

xy = [(x, y) for x in range(1, 10) for y in range(1, 10)]

Then take a random sample of size 10 without replacement.

xy_sample = random.sample(xy, 10)

Then initialize 10 instances of your class.

targets = [Target(x, y) for x, y in xy_sample]

Caveat: if the xy-space is huge then just keeping track of the generated x, y pairs in a set and rerolling the randomly generated numbers in the unlikely case that you hit a duplicate may be more feasible.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks to your answer, I realize I misunderstood the question !
xy can be created with list(itertools.combinations_with_replacement(range(1, 10), 2)) too
Thanks to all, this is nice and simple - however, the range should be (1, 10) - the right boundary is excluded in a generator, unlike in random.randint
2

Assuming your class constructor's interface is set and you can't change it (or don't want to), so the randomization part needs to be inside __init__. And also assuming that just "assuring" there are no dupes in a list of Targets is primarily what you want, without any further assumptions made on what you're exactly trying to achieve.

Then, as a first step, you would require for the Targets to be comparable to each other, based on their x and y values.This can be implemented in the __eq__ method:

def __eq__(self, other):
     return self.x == other.x and self.y == other.y

Alongside, you need to create __hash__, which is required to be equal for objects that are equal based on __eq__, and also because reimplementing __eq__ resets the default __hash__ so Target wouldn't be hashable without:

def __hash__(self):
     return hash((self.x, self.y))  # just use the tuple's hash

Now you can compare Targets with one another based on their (x,y) data:

Target() == Target()

This worked before, too, but would return if the two targets are the same object, which may be False even with the same x and y.

With this as the basis, you can now assert there are no dupes in your list of targets

targets = [Target() for in range(10)]

by turning it into a set, which removes dupes based on equality based on __eq__, and then comparing the length of the result:

assert len(targets) == len(set(targets))

This is may not be what makes the most sense to do, but a direct answer to your question nonetheless.

Comments

1

This uses a generator that remebers the old choices. Carefull: The function can turn into an infinity loop if you create more classes of Target than there are available combinations...

import numpy as np

class CustomGenerator(object):
    def __init__(self, lower_bound: int, upper_bound: int):
        self.old_values = []
        self.lower_bound = lower_bound
        self.upper_bound = upper_bound

    def __iter__(self):
        return self

    def __next__(self):
        return self.next()

    def next(self):
        while True:
            new_vals = tuple(np.random.randint(self.lower_bound, self.upper_bound, 2))
            if new_vals not in self.old_values:
                break
        return new_vals

gen = CustomGenerator(0, 100)

class Target:
    def __init__(self, gen):  
        self.x, self.y = next(gen)

    def __repr__(self) -> str:
        return f"{self.x}, {self.y}"

t = Target(gen)
print(t)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.