确保np.random.choice()的列表内容总计为1

时间:2017-10-16 18:38:03

标签: python arrays numpy probability probability-density

上下文

在Python 3.5中,我正在创建一个生成具有不同生物群系的地图的函数 - 一个二维列表,第一层表示Y轴的线条,项目表示沿X轴的项目。 / p>

  

示例:

[
["A1", "B1", "C1"],
["A2", "B2", "C2"],
["A3", "B3", "C3"]
] 
     

显示为:

A1 B1 C1
A2 B2 C2
A3 B3 C3

目标

如果其邻居也是该生物群系,则地图上的给定位置应该更可能是某个生物群系。所以,如果给定方阵的邻居都是伍兹,那么这个方格几乎可以保证是伍兹。

我的代码(到目前为止)

所有生物群系都由类(woodsBiomedesertBiomefieldBiome)表示。它们都继承自baseBiome,它本身用于填充网格。

我的代码是一个函数的形式。它将最大X和Y坐标作为参数。这是:

def generateMap(xMax, yMax):
    areaMap = []  # this will be the final result of a 2d list

    # first, fill the map with nothing to establish a blank grid
    xSampleData = []  # this will be cloned on the X axis for every Y-line
    for i in range(0, xMax):
        biomeInstance = baseBiome()
        xSampleData.append(biomeInstance)  # fill it with baseBiome for now, we will generate biomes later
    for i in range(0, yMax):
        areaMap.append(xSampleData)

    # now we generate biomes
    yCounter = yMax  # because of the way the larger program works. keeps track of the y-coordinate we're on
    for yi in areaMap:  # this increments for every Y-line
        xCounter = 0  # we use this to keep track of the x coordinate we're on
        for xi in yi:  # for every x position in the Y-line
            biomeList = [woodsBiome(), desertBiome(), fieldBiome()]
            biomeProbabilities = [0.0, 0.0, 0.0]
            # biggest bodge I have ever written
            if areaMap[yi-1][xi-1].isinstance(woodsBiome):
                biomeProbabilities[0] += 0.2
            if areaMap[yi+1][xi+1].isinstance(woodsBiome):
                biomeProbabilities[0] += 0.2
            if areaMap[yi-1][xi+1].isinstance(woodsBiome):
                biomeProbabilities[0] += 0.2
            if areaMap[yi+1][xi-1].isinstance(woodsBiome):
                biomeProbabilities[0] += 0.2
            if areaMap[yi-1][xi-1].isinstance(desertBiome):
                biomeProbabilities[1] += 0.2
            if areaMap[yi+1][xi+1].isinstance(desertBiome):
                biomeProbabilities[1] += 0.2
            if areaMap[yi-1][xi+1].isinstance(desertBiome):
                biomeProbabilities[1] += 0.2
            if areaMap[yi+1][xi-1].isinstance(desertBiome):
                biomeProbabilities[1] += 0.2
            if areaMap[yi-1][xi-1].isinstance(fieldBiome):
                biomeProbabilities[2] += 0.2
            if areaMap[yi+1][xi+1].isinstance(fieldBiome):
                biomeProbabilities[2] += 0.2
            if areaMap[yi-1][xi+1].isinstance(fieldBiome):
                biomeProbabilities[2] += 0.2
            if areaMap[yi+1][xi-1].isinstance(fieldBiome):
                biomeProbabilities[2] += 0.2
            choice = numpy.random.choice(biomeList, 4, p=biomeProbabilities)
            areaMap[yi][xi] = choice

    return areaMap

说明:

正如你所看到的,我从一个空列表开始。我将baseBiome添加为占位符(最多xi == xMaxyi == 0),以生成我可以循环的2D网格。

我创建了一个列表biomeProbabilities,其中包含代表不同生物群系的不同索引。在地图中的位置骑行时,我会检查所选位置的邻居,并根据其生物群系调整biomeProbabilities中的值。

最后,我使用numpy.random.choice()biomeListbiomeProbabilities一起使用每个项目的给定概率从biomeList做出选择。

我的问题

如何确保biomeProbabilities中每个项目的总和等于1(以便numpy.random.choice允许随机概率选择)?我看到有两种逻辑解决方案:

a)分配新概率,以便给出排名最高的生物群系0.8,然后是第二个0.4和第三个0.2

b)向每个加上或减去相等的金额,直到总和== 1

哪个选项(如果有的话)会更好,我将如何实现它?

此外,有没有更好的方法来获得结果而不诉诸我在这里使用的无限if语句?

2 个答案:

答案 0 :(得分:3)

这听起来像解决问题的复杂方法。你很难以这种方式工作,因为你正在限制自己只有一次前进。

您可以这样做的一种方法是选择一个随机位置来启动生物群系,并且"展开"它与相邻的补丁有很高的概率(如0.9)。

(请注意,您的示例中存在代码错误,第10行 - 您必须复制内部列表)

import random
import sys


W = 78
H = 40

BIOMES = [
    ('#', 0.5, 5),
    ('.', 0.5, 5),
]

area_map = []

# Make empty map
inner_list = []
for i in range(W):
    inner_list.append(' ')
for i in range(H):
    area_map.append(list(inner_list))

def neighbors(x, y):
    if x > 0:
        yield x - 1, y
    if y > 0:
        yield x, y - 1
    if y < H - 1:
        yield x, y + 1
    if x < W - 1:
        yield x + 1, y

for biome, proba, locations in BIOMES:
    for _ in range(locations):
        # Random starting location
        x = int(random.uniform(0, W))
        y = int(random.uniform(0, H))

        # Remember the locations to be handled next
        open_locations = [(x, y)]
        while open_locations:
            x, y = open_locations.pop(0)

            # Probability to stop
            if random.random() >= proba:
                continue

            # Propagate to neighbors, adding them to the list to be handled next
            for x, y in neighbors(x, y):
                if area_map[y][x] == biome:
                    continue
                area_map[y][x] = biome
                open_locations.append((x, y))

for y in range(H):
    for x in range(W):
        sys.stdout.write(area_map[y][x])
    sys.stdout.write('\n')

screenshot of result

当然,更好的方法,通常用于那些类型的任务(例如在Minecraft中),是使用Perlin noise函数。如果特定区域的值高于某个阈值,请使用其他生物群系。优点是:

  • 懒人生成:您不需要提前生成整个区域地图,当您确实需要知道该区域时,您可以确定区域内的生物群系类型
  • 看起来更逼真
  • Perlin为您提供实际值作为输出,因此您可以将它用于更多的事情,例如地形高度,或混合多个生物群系(或者您可以将它用于&#34;潮湿&#34;,有0-20%是沙漠,20-60%是草,60-80%是沼泽,80-100%是水)
  • 您可以覆盖多个&#34;尺寸&#34;例如,噪音可以通过简单地将它们相乘来为您提供每个生物群系的详细信息

答案 1 :(得分:1)

我建议:

biomeProbabilities = biomeProbabilities / biomeProbabilities.sum()

对于你无休止的if语句,我建议使用预先分配的方向数组,例如:

directions = [(-1, -1), (0, -1), (1, -1),
              (-1,  0),          (1,  0),
              (-1,  1), (0,  1), (1,  1)]

并使用它进行迭代,如:

for tile_x, tile_y in tiles:
    for x, y in direction:
        neighbor = map[tile_x + x][tile_y + y]

@remram对你可能会或可能不会用来生成地形的算法做了很好的回答,所以我不会去看这个主题。