web-dev-qa-db-fra.com

Générer plusieurs coordonnées aléatoires (x, y), à l'exclusion des doublons?

Je veux générer un groupe (x, y) de coordonnées de 0 à 2500 qui exclut les points qui sont à moins de 200 les uns des autres sans récursivité.

En ce moment, je le fais vérifier à travers une liste de toutes les valeurs précédentes pour voir si certaines sont suffisamment éloignées de toutes les autres. C'est vraiment inefficace et si j'ai besoin de générer un grand nombre de points, cela prend une éternité.

Alors, comment pourrais-je faire cela?

26
user2901745

Il s'agit d'une variante de la suggestion de Hank Ditton qui devrait être plus efficace en termes de temps et de mémoire, surtout si vous sélectionnez relativement peu de points sur tous les points possibles. L'idée est que, chaque fois qu'un nouveau point est généré, tout ce qui se trouve à moins de 200 unités est ajouté à un ensemble de points à exclure, par rapport auquel tous les points fraîchement générés sont vérifiés.

import random

radius = 200
rangeX = (0, 2500)
rangeY = (0, 2500)
qty = 100  # or however many points you want

# Generate a set of all points within 200 of the Origin, to be used as offsets later
# There's probably a more efficient way to do this.
deltas = set()
for x in range(-radius, radius+1):
    for y in range(-radius, radius+1):
        if x*x + y*y <= radius*radius:
            deltas.add((x,y))

randPoints = []
excluded = set()
i = 0
while i<qty:
    x = random.randrange(*rangeX)
    y = random.randrange(*rangeY)
    if (x,y) in excluded: continue
    randPoints.append((x,y))
    i += 1
    excluded.update((x+dx, y+dy) for (dx,dy) in deltas)
print randPoints
12
jwodder

Je généraliserais les points, target_N < input_N, et filtrez-les à l'aide d'un KDTree . Par exemple:

import numpy as np
from scipy.spatial import KDTree
N   = 20
pts = 2500*np.random.random((N,2))

tree = KDTree(pts)
print tree.sparse_distance_matrix(tree, 200)

Me donnerait des points qui sont "proches" les uns des autres. À partir d'ici, il devrait être simple d'appliquer n'importe quel filtre:

  (11, 0)   60.843426339
  (0, 11)   60.843426339
  (1, 3)    177.853472309
  (3, 1)    177.853472309
6
Hooked

Quelques options:

  • Utilisez votre algorithme mais implémentez-le avec un kd-tree qui accélérerait la recherche des voisins les plus proches
  • Construisez une grille régulière sur le carré [0, 2500] ^ 2 et secouez tous les points au hasard avec une distribution normale bidimensionnelle centrée sur chaque intersection de la grille
  • Dessinez un plus grand nombre de points aléatoires, puis appliquez un algorithme k-means et ne conservez que les centroïdes. Ils seront éloignés les uns des autres et l'algorithme, bien qu'itératif, pourrait converger plus rapidement que votre algorithme.
3
damienfrancois

Cela a été répondu, mais c'est très tangentiellement lié à mon travail, alors j'ai essayé. J'ai implémenté l'algorithme décrit dans cette note que j'ai trouvé lié depuis ce billet de blog . Malheureusement, ce n'est pas plus rapide que les autres méthodes proposées, mais je suis sûr qu'il y a des optimisations à faire.

import numpy as np
import matplotlib.pyplot as plt

def lonely(p,X,r):
    m = X.shape[1]
    x0,y0 = p
    x = y = np.arange(-r,r)
    x = x + x0
    y = y + y0

    u,v = np.meshgrid(x,y)

    u[u < 0] = 0
    u[u >= m] = m-1
    v[v < 0] = 0
    v[v >= m] = m-1

    return not np.any(X[u[:],v[:]] > 0)

def generate_samples(m=2500,r=200,k=30):
    # m = extent of sample domain
    # r = minimum distance between points
    # k = samples before rejection
    active_list = []

    # step 0 - initialize n-d background grid
    X = np.ones((m,m))*-1

    # step 1 - select initial sample
    x0,y0 = np.random.randint(0,m), np.random.randint(0,m)
    active_list.append((x0,y0))
    X[active_list[0]] = 1

    # step 2 - iterate over active list
    while active_list:
        i = np.random.randint(0,len(active_list))
        rad = np.random.Rand(k)*r+r
        theta = np.random.Rand(k)*2*np.pi

        # get a list of random candidates within [r,2r] from the active point
        candidates = np.round((rad*np.cos(theta)+active_list[i][0], rad*np.sin(theta)+active_list[i][1])).astype(np.int32).T

        # trim the list based on boundaries of the array
        candidates = [(x,y) for x,y in candidates if x >= 0 and y >= 0 and x < m and y < m]

        for p in candidates:
            if X[p] < 0 and lonely(p,X,r):
                X[p] = 1
                active_list.append(p)
                break
        else:
            del active_list[i]

    return X

X = generate_samples(2500, 200, 10)
s = np.where(X>0)
plt.plot(s[0],s[1],'.')

Et les résultats:

Resulting sample pattern

2
aganders3

Par le lien, la méthode d'aganders3 est connue sous le nom d'échantillonnage de disque de Poisson. Vous pourrez peut-être trouver des implémentations plus efficaces qui utilisent une recherche de grille locale pour trouver des "chevauchements". Par exemple échantillonnage du disque de Poisson . Parce que vous contraignez le système, il ne peut pas être complètement aléatoire. L'emballage maximum pour les cercles avec des rayons uniformes dans un plan est de ~ 90% et est atteint lorsque les cercles sont disposés dans un réseau hexagonal parfait. À mesure que le nombre de points que vous demandez approche de la limite théorique, l'agencement généré devient plus hexagonal. D'après mon expérience, il est difficile d'obtenir un emballage supérieur à ~ 60% avec des cercles uniformes en utilisant cette approche.

0
Ender