简体   繁体   English

选择不在列表 python 中的随机三元组

[英]Choose a random triple that isn't in list python

I am trying to generate a large amount of triples while insuring they aren't in a list as well as a few other factors.我正在尝试生成大量三元组,同时确保它们不在列表中以及其他一些因素。 The goal is to create a 1024x1024 image but the difference between two pixels is never the same but it also has to ensure that the difference doesn't make the pixel have color values outside of the acceptable range.目标是创建 1024x1024 图像,但两个像素之间的差异永远不会相同,但它还必须确保差异不会使像素的颜色值超出可接受的范围。 The problem with the code I have currently is that it is too slow and take over 12 hours to even get half way.我目前拥有的代码的问题是它太慢了,甚至需要超过 12 个小时才能完成一半。 Here is the segment of the code that chooses a single pixel's color values:这是选择单个像素颜色值的代码段:

def choose_color():
global color
global difference
global usedDifferences
found = 0
while found == 0:
    difference = [random.randint(-256, 256), random.randint(-256, 256), random.randint(-256, 256)]
    if difference not in usedDifferences:
        if 0 <= color[0] + difference[0] <= 255 and 0 <= color[1] + difference[1] <= 255 and 0 <= color[2] + difference[2] <= 255:
            usedDifferences.append(difference)
            found = 1
            color[0] += difference[0]
            color[1] += difference[1]
            color[2] += difference[2]

How can I make this faster?我怎样才能让它更快?

Your code can be modified to run in under 10 seconds on my machine by fixing a few issues.通过修复一些问题,您的代码可以在 10 秒内在我的机器上运行。

As a cautionary note, I'm not sure if you meant to only look at the diff with the one previous color, or maybe all the adjacent colors.作为警告,我不确定您是否只想查看具有前一种颜色的差异,或者可能是所有相邻的 colors。 If you want to ensure that ANY TWO pixels have a unique diff, that's a completely different question.如果您想确保任何两个像素具有唯一的差异,那是一个完全不同的问题。

The biggest problem is using a list.最大的问题是使用列表。 You need to store the used differences in a set.您需要将使用的差异存储在一个集合中。 This data structure exists for that basic purpose -- efficiently checking to see if a value is unique.这种数据结构的存在是为了这个基本目的——有效地检查一个值是否是唯一的。 This requires using something hashable like a tuple for the color, which I believe is better anyway.这需要使用一些可散列的东西,比如颜色的元组,我相信无论如何这更好。

Additionally, you are wasting a lot of effort selecting from a random range that includes invalid values.此外,您正在浪费大量精力从包含无效值的随机范围中进行选择。 You should compute the valid range first, then select a random number from that.您应该首先计算有效范围,然后 select 从中获得一个随机数。 Or in my solution I just picked a random color and computed the diff, so it has to be valid.或者在我的解决方案中,我只是选择了一种随机颜色并计算了差异,所以它必须是有效的。

Lastly, I implemented this as an generator.最后,我将其实现为生成器。 If we're just checking the diff with the previous one pixel, we don't need to store these values anywhere besides directly in the image (which I did not do here).如果我们只是检查前一个像素的差异,我们不需要将这些值直接存储在图像中(我在这里没有这样做)。 Keeping them in a list is a big waste of space and time.将它们保留在列表中是对空间和时间的极大浪费。

from itertools import islice
from random import randint
from datetime import datetime

def compute_diff(color1: tuple, color2: tuple):
    return (
        color2[0] - color1[0],
        color2[1] - color1[1],
        color2[2] - color1[2],
    )

def choose_color(last_color=(0, 0, 0), MAX_TRIES=1000):
    # This is absolutely the most important thing:
    # used_diffs MUST be a set.
    used_diffs = set()
    tries = 0
    while tries < MAX_TRIES:
        # Don't pick a random diff and check for bounds.
        # That's a lot of wasted work.
        # Instead just pick a valid color and compute the diff.
        color = (randint(0, 255), randint(0, 255), randint(0, 255))
        diff = compute_diff(color, last_color)
        if diff not in used_diffs:
            # Using a generator is generally more efficient
            yield color
            used_diffs.add(diff)
            last_color = color
            tries = 0
        tries += 1
        if tries > 10:
            # I added this just for debugging purposes. It never triggered.
            print('.', end='')
    raise RecursionError('choose_color took too many tries')

try:
    # demonstrate that we can pick a few valid colors
    print('demo 10 pixels')
    for color in islice(choose_color(), 10):
        print(color)

    # now time the selection of enough pixels to fill the image
    print('building full image...')
    start = datetime.now()
    for color in islice(choose_color(), 1024*1024):
        pass
    end = datetime.now()
    print(end-start)
except RecursionError as e:
    print (str(e))

What you are trying to do is (probably) not possible.你想要做的是(可能)不可能。 There are two big issues with your code currently in terms of correctness.就正确性而言,您的代码目前存在两个大问题。 First off, it is possible that the difference (5, 5, 5) is used, and that (-5, -5, -5) is also used.首先,有可能使用了差值 (5, 5, 5),也可能使用了 (-5, -5, -5)。 The absolute difference is the same, meaning that you may be using differences that are not unique.绝对差异是相同的,这意味着您可能使用的差异不是唯一的。

Secondly, (and much more important) you are not checking that the difference between all pixels is never repeated.其次,(更重要的是)您没有检查所有像素之间的差异是否永远不会重复。 Imagine this scenario:想象一下这个场景:

  1. I start at (0,0,0) and generate a difference of (1, 1, 1)我从 (0,0,0) 开始并产生 (1, 1, 1) 的差异
  2. I am now at (1, 1, 1) and generate a difference of (10, 10, 10)我现在在 (1, 1, 1) 并产生 (10, 10, 10) 的差异
  3. I am now at (11, 11, 11) and generate a difference of (-2, -2, -2)我现在在 (11, 11, 11) 并产生 (-2, -2, -2) 的差异
  4. I am now at (9, 9, 9) and generate a difference of (3, 3, 3)我现在在 (9, 9, 9) 并产生 (3, 3, 3) 的差异
  5. I am now at (12, 12, 12) which has a difference of (1, 1, 1) to (11, 11, 11) !我现在在 (12, 12, 12)有 (1, 1, 1) 到 (11, 11, 11) 的差异

Here is some sample code that fixes both of these problems and selects random colors that never have the same difference between each other (note: this code heavily benefits from this data structure that allows for quick insertion, deletion, and random sampling.)下面是一些示例代码,可以解决这两个问题,并选择随机 colors 彼此之间永远不会有相同的差异(注意:此代码极大地受益于这种允许快速插入、删除和随机采样的数据结构。)

import random
import operator

class ListDict(object):
    def __init__(self):
        self.item_to_position = {}
        self.items = []

    def add_item(self, item):
        if item in self.item_to_position:
            return
        self.items.append(item)
        self.item_to_position[item] = len(self.items)-1

    def remove_item(self, item):
        position = self.item_to_position.pop(item)
        last_item = self.items.pop()
        if position != len(self.items):
            self.items[position] = last_item
            self.item_to_position[last_item] = position

    def choose_random_item(self):
        return random.choice(self.items)

def get_colors():
    colors = ListDict()
    for i in range (0, 256):
        for j in range(0, 256):
            for k in range(0, 256):
                colors.add_item((i,j,k))
    return colors

def remove_color(color, colors):
    if color in colors.item_to_position:
            colors.remove_item(color)

print("Generating colors!")
colors = get_colors()

initial_color = [random.randint(0, 256), random.randint(0, 256), random.randint(0, 256)]
output_colors = [initial_color]
all_differences = []

for i in range(1024 * 1024):
    try:
        new_color = colors.choose_random_item()
    catch IndexError:
        print("Ran out of colors!")
        break

    differences = []
    for color in output_colors:
        difference = tuple(map(operator.sub, color, new_color))
        difference_neg = tuple(map(operator.mul, difference, (-1, -1, -1)))
        differences.append(difference)
        differences.append(difference_neg)

    for color in output_colors:
        for difference in differences:
            remove_color(tuple(map(operator.add, color, difference)), colors)

    all_differences += differences
    output_colors.append(new_color)

    for difference in all_differences:
        remove_color(tuple(map(operator.add, new_color, difference)), colors)

    print(i, len(colors.items))

print(output_colors)

There is an issue with this code: it doesn't finish.这段代码有一个问题:它没有完成。 It can typically generate around ~1250 colors before it runs out of viable options, In addition.此外,它通常可以生成约 1250 colors,然后再用尽可行的选项。 as you may have noticed it starts to slow down a lot after a while, On my machine.您可能已经注意到,在我的机器上,一段时间后它开始变慢很多。 it takes around 20 minutes to run all the way, This is because we need to check every single difference with every new color we add, and check each new difference with all the colors we have already randomly selected!一路运行大约需要 20 分钟,这是因为我们需要检查每个添加的新颜色的每一个差异,并检查我们已经随机选择的所有 colors 的每个新差异!

To answer your question "can my code performance be improved?"回答您的问题“我的代码性能可以提高吗?” if you do not care about the two issues I just pointed out in your approach here is a much faster version of your code which runs in around 4 minutes on my machine如果您不关心我刚刚在您的方法中指出的两个问题,这里是您的代码的更快版本,它在我的机器上运行大约 4 分钟

import random
import operator

class ListDict(object):
    def __init__(self):
        self.item_to_position = {}
        self.items = []

    def add_item(self, item):
        if item in self.item_to_position:
            return
        self.items.append(item)
        self.item_to_position[item] = len(self.items)-1

    def remove_item(self, item):
        position = self.item_to_position.pop(item)
        last_item = self.items.pop()
        if position != len(self.items):
            self.items[position] = last_item
            self.item_to_position[last_item] = position

    def choose_random_item(self):
        return random.choice(self.items)


def get_differences():
    differences = ListDict()
    for i in range (0, 256):
        for j in range(0, 256):
            for k in range(0, 256):
                differences.add_item((i,j,k))
    return differences

def check_bounds(color, diff):
    for i in range(len(color)):
        if (color[i] + diff[i] < 0) or (color[i] + diff[i] > 255):
            return False
    return True

print("Generating differences!")
differences = get_differences()

initial_color = [random.randint(0, 256), random.randint(0, 256), random.randint(0, 256)]
colors = [initial_color]


print("Generating colors!")
while(len(colors) != 1024 * 1024):
    random_diff = differences.choose_random_item()
    viable_differences = []
    current_color = colors[-1]

    for one in range(2):
        for two in range(2):
            for three in range(2):
                viable_diff = (random_diff[0] if one else -random_diff[0],
                                random_diff[1] if two else -random_diff[1],
                                random_diff[2] if three else -random_diff[2])
                if check_bounds(current_color, viable_diff):
                    viable_differences.append(viable_diff)

    if len(viable_differences):
        random_viable_diff = random.choice(viable_differences)
        colors.append(tuple(map(operator.add, current_color, random_viable_diff)))

    differences.remove_item(random_diff)

print(colors)

In summary: you are not currently checking to make sure that no two differences are ever repeated.总而言之:您当前没有检查以确保没有重复任何两个差异。 If we check for this thoroughly we actually run out of colors without even finding enough to fill a single 1024-pixel row, With the second piece of code, you will still repeat.如果我们彻底检查这一点,我们实际上用完了 colors 甚至没有找到足以填充单个 1024 像素行的代码,使用第二段代码,您仍然会重复。 but it will be rare.但这将是罕见的。 Let me know if you have any questions.如果您有任何问题,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM