簡體   English   中英

在python中生成范圍之外的隨機數

[英]Generate random number outside of range in python

我正在進行pygame游戲,我需要在屏幕上隨機放置對象,除非它們不在指定的矩形內。 有沒有一種簡單的方法可以做到這一點,而不是連續生成一對隨機坐標,直到它在矩形之外?

這是屏幕和矩形外觀的粗略示例。

 ______________
|      __      |
|     |__|     |
|              |
|              |
|______________|

屏幕尺寸為1000x800,矩形為[x:500,y:250,寬度:100,高度:75]

一種更加面向代碼的方式來看待它

x = random_int
0 <= x <= 1000
    and
500 > x or 600 < x

y = random_int
0 <= y <= 800
    and
250 > y or 325 < y
  1. 將框分成一組子框。
  2. 在有效的子框中,選擇哪一個放置您的點,概率與其面積成比例
  3. 從所選子框內隨機選擇一個隨機點。

隨機子框

這將基於條件概率的鏈規則從有效區域上的均勻概率分布生成樣本。

這在時間和內存方面提供了O(1)方法。

合理

接受的答案以及其他一些答案似乎取決於生成所有可能坐標列表的必要性,或者在有可接受的解決方案之前重新計算。 兩種方法都需要更多的時間和內存。

注意,根據坐標生成均勻性的要求,存在如下所示的不同解決方案。

第一次嘗試

我的方法是隨機選擇指定框周圍的有效坐標(想想左/右上/下 ),然后隨意選擇哪一邊選擇:

import random
# set bounding boxes    
maxx=1000
maxy=800
blocked_box = [(500, 250), (100, 75)]
# generate left/right, top/bottom and choose as you like
def gen_rand_limit(p1, dim):
    x1, y1 = p1
    w, h = dim
    x2, y2 = x1 + w, y1 + h
    left = random.randrange(0, x1)
    right = random.randrange(x2+1, maxx-1)
    top = random.randrange(0, y1)
    bottom = random.randrange(y2, maxy-1)
    return random.choice([left, right]), random.choice([top, bottom])
# check boundary conditions are met
def check(x, y, p1, dim):
    x1, y1 = p1
    w, h = dim
    x2, y2 = x1 + w, y1 + h
    assert 0 <= x <= maxx, "0 <= x(%s) <= maxx(%s)" % (x, maxx)
    assert x1 > x or x2 < x, "x1(%s) > x(%s) or x2(%s) < x(%s)" % (x1, x, x2, x)
    assert 0 <= y <= maxy, "0 <= y(%s) <= maxy(%s)" %(y, maxy)
    assert y1 > y or y2 < y, "y1(%s) > y(%s) or y2(%s) < y(%s)" % (y1, y, y2, y)
# sample
points = []
for i in xrange(1000):
    x,y = gen_rand_limit(*blocked_box)
    check(x, y, *blocked_box)
    points.append((x,y))

結果

考慮到OP中概述的約束,這實際上根據需要在指定的矩形(紅色)周圍產生隨機坐標(藍色),但是省略了矩形之外的任何有效點但是落在相應的x或y維度內。矩形:

在此輸入圖像描述

# visual proof via matplotlib
import matplotlib
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
X,Y = zip(*points)
fig = plt.figure()
ax = plt.scatter(X, Y)
p1 = blocked_box[0]
w,h = blocked_box[1]
rectangle = Rectangle(p1, w, h, fc='red', zorder=2)
ax = plt.gca()
plt.axis((0, maxx, 0, maxy))
ax.add_patch(rectangle)

改進

通過僅限制x或y坐標可以很容易地解決這個問題(注意check不再有效,運行此部分的注釋):

def gen_rand_limit(p1, dim):
    x1, y1 = p1
    w, h = dim
    x2, y2 = x1 + w, y1 + h
    # should we limit x or y?
    limitx = random.choice([0,1])
    limity = not limitx
    # generate x, y O(1)
    if limitx:
        left = random.randrange(0, x1)
        right = random.randrange(x2+1, maxx-1)
        x = random.choice([left, right])
        y = random.randrange(0, maxy)
    else:
        x = random.randrange(0, maxx)
        top = random.randrange(0, y1)
        bottom = random.randrange(y2, maxy-1)
        y = random.choice([top, bottom])
    return x, y 

在此輸入圖像描述

調整隨機偏差

正如評論中所指出的,該解決方案受到對矩形的行/列之外的點的偏差的影響。 以下修復原則上通過給每個坐標提供相同的概率:

def gen_rand_limit(p1, dim):
    x1, y1 = p1Final solution -
    w, h = dim
    x2, y2 = x1 + w, y1 + h
    # generate x, y O(1)
    # --x
    left = random.randrange(0, x1)
    right = random.randrange(x2+1, maxx)
    withinx = random.randrange(x1, x2+1)
    # adjust probability of a point outside the box columns
    # a point outside has probability (1/(maxx-w)) v.s. a point inside has 1/w
    # the same is true for rows. adjupx/y adjust for this probability 
    adjpx = ((maxx - w)/w/2)
    x = random.choice([left, right] * adjpx + [withinx])
    # --y
    top = random.randrange(0, y1)
    bottom = random.randrange(y2+1, maxy)
    withiny = random.randrange(y1, y2+1)
    if x == left or x == right:
        adjpy = ((maxy- h)/h/2)
        y = random.choice([top, bottom] * adjpy + [withiny])
    else:
        y = random.choice([top, bottom])
    return x, y 

下圖有10'000個點來說明點的均勻放置(覆蓋框'邊界的點是由於點大小)。

免責聲明:請注意,此圖表將紅色框放在正中間,使得top/bottomleft/right彼此具有相同的概率。 因此,調整相對於阻塞框,但不是針對圖的所有區域。 最終的解決方案需要分別調整每個概率的概率。

在此輸入圖像描述

更簡單的解決方案,但略有修改的問題

事實證明,調整坐標系不同區域的概率非常棘手。 經過一番思考后,我想出了一個略微修改過的方法:

實現在任何2D坐標系上,阻擋矩形將該區域划分為N個子區域(在問題的情況下N = 8),其中可以選擇有效坐標。 以這種方式看,我們可以將有效子區域定義為坐標框。 然后我們可以隨機選擇一個方框,並從該方框內隨機選擇一個坐標:

def gen_rand_limit(p1, dim):
    x1, y1 = p1
    w, h = dim
    x2, y2 = x1 + w, y1 + h
    # generate x, y O(1)
    boxes = (
       ((0,0),(x1,y1)),   ((x1,0),(x2,y1)),    ((x2,0),(maxx,y1)),
       ((0,y1),(x1,y2)),                       ((x2,y1),(maxx,y2)),
       ((0,y2),(x1,maxy)), ((x1,y2),(x2,maxy)), ((x2,y2),(maxx,maxy)),
    )
    box = boxes[random.randrange(len(boxes))]
    x = random.randrange(box[0][0], box[1][0])
    y = random.randrange(box[0][1], box[1][1])
    return x, y 

請注意,這不是一般化的,因為被阻止的盒子可能不在中間,因此boxes看起來會有所不同。 由於這導致每個框以相同的概率選擇,我們在每個框中得到相同數量的點。 顯然,小盒子的密度更高:

在此輸入圖像描述

如果要求是在所有可能的坐標之間生成均勻分布,則解決方案是計算boxes ,使得每個框與阻塞框大小相同。 因人而異

我已經發布了一個我仍然喜歡的不同答案,因為它簡單明了,並不一定很慢......無論如何,這並不是OP所要求的。

我想到了它,我設計了一個算法來解決他們的約束中的OP問題:

  1. 將屏幕分成9個矩形並包含“洞”。
  2. 考慮中心孔周圍的8個矩形(“瓷磚”)
  3. 對於每個圖塊,計算原點(x,y),高度和面積(以像素為單位)
  4. 計算平鋪區域的累積總和,以及平鋪的總面積
  5. 對於每次提取,選擇0和瓷磚總面積之間的隨機數(包括和排除)
  6. 使用累積和確定隨機像素位於哪個區塊中
  7. 使用divmod確定列中的列和行(dx,dy)
  8. 使用屏幕坐標中的圖塊的原點,計算屏幕坐標中的隨機像素。

為了實現上面的想法,其中有一個初始化階段,我們計算靜態數據和一個階段,我們重復使用這些數據,自然數據結構是一個類,這里​​是我的實現

from random import randrange

class make_a_hole_in_the_screen():

    def __init__(self, screen, hole_orig, hole_sizes):
        xs, ys = screen
        x, y = hole_orig
        wx, wy = hole_sizes
        tiles = [(_y,_x*_y) for _x in [x,wx,xs-x-wx] for _y in [y,wy,ys-y-wy]]
        self.tiles = tiles[:4] + tiles[5:]
        self.pixels = [tile[1] for tile in self.tiles]
        self.total = sum(self.pixels)
        self.boundaries = [sum(self.pixels[:i+1]) for i in range(8)]
        self.x = [0,    0,    0,
                  x,          x,
                  x+wx, x+wx, x+wx]
        self.y = [0,    y,    y+wy,
                  0,          y+wy,
                  0,    y,    y+wy]

    def choose(self):
        n = randrange(self.total)
        for i, tile in enumerate(self.tiles):
            if n < self.boundaries[i]: break
        n1 = n - ([0]+self.boundaries)[i]
        dx, dy = divmod(n1,self.tiles[i][0])
        return self.x[i]+dx, self.y[i]+dy

為了測試實現的正確性,這里是我在python 2.7上運行的粗略檢查,

drilled_screen = make_a_hole_in_the_screen((200,100),(30,50),(20,30))
for i in range(1000000):
    x, y = drilled_screen.choose()
    if 30<=x<50 and 50<=y<80: print "***", x, y
    if x<0 or x>=200 or y<0 or y>=100: print "+++", x, y

可能的優化包括使用二分算法來找到相關的區塊來代替我實現的更簡單的線性搜索。

需要一些思考來生成具有這些約束的均勻隨機點。 我能想到的最簡單的蠻力方法是生成所有有效點的列表,並使用random.choice()從該列表中進行選擇。 這會為列表使用幾MB的內存,但生成一個點非常快:

import random

screen_width = 1000
screen_height = 800
rect_x = 500
rect_y = 250
rect_width = 100
rect_height = 75

valid_points = []
for x in range(screen_width):
    if rect_x <= x < (rect_x + rect_width):
        for y in range(rect_y):
            valid_points.append( (x, y) )
        for y in range(rect_y + rect_height, screen_height):
            valid_points.append( (x, y) )
    else:
        for y in range(screen_height):
            valid_points.append( (x, y) )

for i in range(10):
    rand_point = random.choice(valid_points)
    print(rand_point)

可以生成一個隨機數並將其映射到屏幕上的有效點,該點使用較少的內存,但它有點亂,並且需要更多時間來生成該點。 可能有一種更簡潔的方法,但是使用與上面相同的屏幕尺寸變量的一種方法是:

rand_max = (screen_width * screen_height) - (rect_width * rect_height) 
def rand_point():
    rand_raw = random.randint(0, rand_max-1)
    x = rand_raw % screen_width
    y = rand_raw // screen_width
    if rect_y <= y < rect_y+rect_height and rect_x <= x < rect_x+rect_width:
        rand_raw = rand_max + (y-rect_y) * rect_width + (x-rect_x)
        x = rand_raw % screen_width
        y = rand_raw // screen_width
    return (x, y)

這里的邏輯類似於在舊的8位和16位微處理器上從x和y坐標計算屏幕地址的方式的倒數。 變量rand_max等於有效屏幕坐標的數量。 計算像素的x和y坐標,如果它在矩形內,則像素被推到rand_max ,進入第一次調用無法生成的區域。

如果你不太關心均勻隨機的這一點,這個解決方案很容易實現,而且非常快。 x值是隨機的,但如果所選的X位於帶有矩形的列中,則Y值會受到約束,因此矩形上方和下方的像素將被選擇的概率高於矩形左側和右側的pizels :

def pseudo_rand_point():        
    x = random.randint(0, screen_width-1)
    if rect_x <= x < rect_x + rect_width: 
        y = random.randint(0, screen_height-rect_height-1)
        if y >= rect_y:
            y += rect_height
    else:
        y = random.randint(0, screen_height-1)
    return (x, y)

另一個答案是計算像素在屏幕的某些區域中的概率,但他們的答案還不是很正確。 這是一個使用類似想法的版本,計算像素在給定區域中的概率,然后計算它在該區域內的位置:

valid_screen_pixels = screen_width*screen_height - rect_width * rect_height
prob_left = float(rect_x * screen_height) / valid_screen_pixels
prob_right = float((screen_width - rect_x - rect_width) * screen_height) / valid_screen_pixels
prob_above_rect = float(rect_y) / (screen_height-rect_height)
def generate_rand():
    ymin, ymax = 0, screen_height-1
    xrand = random.random()
    if xrand < prob_left:
        xmin, xmax = 0, rect_x-1
    elif xrand > (1-prob_right):
        xmin, xmax = rect_x+rect_width, screen_width-1
    else:
        xmin, xmax = rect_x, rect_x+rect_width-1
        yrand = random.random()
        if yrand < prob_above_rect:
            ymax = rect_y-1
        else:
            ymin=rect_y+rect_height
    x = random.randrange(xmin, xmax)
    y = random.randrange(ymin, ymax)
    return (x, y)

如果是想要避免的隨機生成,而不是循環,則可以執行以下操作:

  1. 在[0,1]中生成一對隨機浮點坐標
  2. 縮放坐標以在外部矩形中給出一個點。
  3. 如果您的點位於內部矩形之外,請將其返回
  4. 重新縮放以將內部矩形映射到外部矩形
  5. 轉到第3步

如果內部矩形與外部矩形相比較小,則這將最有效。 它可能應該僅限於在生成新的隨機和再次嘗試之前經過循環最多次。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM