Add optional seed inputs to pop-random nodes.
This commit is contained in:
@@ -760,13 +760,17 @@ class DataListPopRandom(ComfyNodeABC):
|
||||
|
||||
This node takes a list as input and returns the list with the random element removed
|
||||
and the removed element itself. If the list is empty, it returns None for the element.
|
||||
An optional seed can be provided for reproducible selection.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY, {}),
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"seed": (IO.INT, {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY, IO.ANY)
|
||||
@@ -779,13 +783,24 @@ class DataListPopRandom(ComfyNodeABC):
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
seed = kwargs.get("seed")
|
||||
if seed is None:
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
# INPUT_IS_LIST wraps scalars in a list
|
||||
if isinstance(seed, list):
|
||||
seed = seed[0] if seed else None
|
||||
if seed is None:
|
||||
return float("NaN")
|
||||
return seed
|
||||
|
||||
def pop_random_element(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
|
||||
from random import randrange
|
||||
import random
|
||||
input_list = kwargs.get('list', []).copy()
|
||||
seed_values = kwargs.get('seed')
|
||||
seed = seed_values[0] if seed_values is not None else None
|
||||
rng = random.Random(seed) if seed is not None else random
|
||||
if input_list:
|
||||
random_element = input_list.pop(randrange(len(input_list)))
|
||||
random_element = input_list.pop(rng.randrange(len(input_list)))
|
||||
return input_list, random_element
|
||||
return input_list, None
|
||||
|
||||
|
||||
@@ -826,13 +826,17 @@ class DictPopRandom(ComfyNodeABC):
|
||||
This node takes a dictionary as input, removes a random key-value pair,
|
||||
and returns the modified dictionary along with the removed key and value.
|
||||
If the dictionary is empty, it returns empty values.
|
||||
An optional seed can be provided for reproducible selection.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT", IO.STRING, IO.ANY, IO.BOOLEAN)
|
||||
@@ -843,15 +847,19 @@ class DictPopRandom(ComfyNodeABC):
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
seed = kwargs.get("seed")
|
||||
if seed is None:
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
return seed
|
||||
|
||||
def pop_random(self, input_dict: Mapping[KT, VT]) -> tuple[Mapping[KT, VT], _U[KT, str], _O[VT], bool]:
|
||||
def pop_random(self, input_dict: Mapping[KT, VT], seed: _O[int] = None) -> tuple[Mapping[KT, VT], _U[KT, str], _O[VT], bool]:
|
||||
if not input_dict:
|
||||
return input_dict, "", None, False
|
||||
|
||||
rng = random.Random(seed) if seed is not None else random
|
||||
result = dict(input_dict)
|
||||
try:
|
||||
random_key = random.choice(list(result.keys()))
|
||||
random_key = rng.choice(list(result.keys()))
|
||||
random_value = result.pop(random_key)
|
||||
except Exception:
|
||||
return input_dict, "", None, False
|
||||
|
||||
@@ -595,13 +595,17 @@ class ListPopRandom(ComfyNodeABC):
|
||||
|
||||
This node takes a LIST as input and returns the LIST with the random element removed
|
||||
and the removed element itself. If the LIST is empty, it returns None for the element.
|
||||
An optional seed can be provided for reproducible selection.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", IO.ANY)
|
||||
@@ -612,13 +616,17 @@ class ListPopRandom(ComfyNodeABC):
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
seed = kwargs.get("seed")
|
||||
if seed is None:
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
return seed
|
||||
|
||||
def pop_random_element(self, list: list[Any]) -> tuple[list[Any], Any]:
|
||||
def pop_random_element(self, list: list[Any], seed=None) -> tuple[list[Any], Any]:
|
||||
import random
|
||||
rng = random.Random(seed) if seed is not None else random
|
||||
result = list.copy()
|
||||
if result:
|
||||
random_index = random.randrange(len(result))
|
||||
random_index = rng.randrange(len(result))
|
||||
random_element = result.pop(random_index)
|
||||
return result, random_element
|
||||
return result, None
|
||||
|
||||
@@ -505,13 +505,17 @@ class SetPopRandom(ComfyNodeABC):
|
||||
|
||||
This node takes a SET as input and returns the SET with a random element removed
|
||||
and the removed element itself. If the SET is empty, it returns None for the element.
|
||||
An optional seed can be provided for reproducible selection.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set": ("SET", {}),
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET", IO.ANY)
|
||||
@@ -522,13 +526,17 @@ class SetPopRandom(ComfyNodeABC):
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
seed = kwargs.get("seed")
|
||||
if seed is None:
|
||||
return float("NaN") # Not equal to anything -> trigger recalculation
|
||||
return seed
|
||||
|
||||
def pop_random_element(self, set: set[Any]) -> tuple[set[Any], Any]:
|
||||
def pop_random_element(self, set: set[Any], seed=None) -> tuple[set[Any], Any]:
|
||||
import random
|
||||
rng = random.Random(seed) if seed is not None else random
|
||||
result = set.copy()
|
||||
if result:
|
||||
random_element = random.choice(list(result))
|
||||
random_element = rng.choice(list(result))
|
||||
result.remove(random_element)
|
||||
return result, random_element
|
||||
return result, None
|
||||
|
||||
@@ -303,6 +303,13 @@ def test_pop_random():
|
||||
# Empty list case
|
||||
assert node.pop_random_element(list=[]) == ([], None)
|
||||
|
||||
# Same seed should produce the same popped item
|
||||
original_list = [1, 2, 3, 4]
|
||||
result1, item1 = node.pop_random_element(list=original_list, seed=[42])
|
||||
result2, item2 = node.pop_random_element(list=original_list, seed=[42])
|
||||
assert item1 == item2
|
||||
assert result1 == result2
|
||||
|
||||
|
||||
def test_first():
|
||||
node = DataListFirst()
|
||||
|
||||
@@ -151,6 +151,11 @@ def test_dict_pop_random(dict_type):
|
||||
assert empty_value is None
|
||||
assert empty_success is False
|
||||
|
||||
# Same seed should produce the same popped key/value
|
||||
result1 = node.pop_random(my_dict, seed=42)
|
||||
result2 = node.pop_random(my_dict, seed=42)
|
||||
assert result1 == result2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dict_type", _tested_dict_types)
|
||||
def test_dict_keys(dict_type):
|
||||
|
||||
@@ -87,6 +87,13 @@ def test_list_pop_random():
|
||||
# Test with empty list
|
||||
assert node.pop_random_element([]) == ([], None)
|
||||
|
||||
# Same seed should produce the same popped item
|
||||
result1, item1 = node.pop_random_element(original_list, seed=42)
|
||||
result2, item2 = node.pop_random_element(original_list, seed=42)
|
||||
assert item1 == item2
|
||||
assert result1 == result2
|
||||
assert original_list == [1, 2, 3, 4] # Input not mutated
|
||||
|
||||
|
||||
def test_list_first():
|
||||
node = ListFirst()
|
||||
|
||||
@@ -132,6 +132,13 @@ def test_set_pop_random():
|
||||
empty_set = set()
|
||||
assert node.pop_random_element(empty_set) == (set(), None)
|
||||
|
||||
# Same seed should produce the same popped item for the same set contents
|
||||
seeded_set = {1, 2, 3, 4}
|
||||
result1, item1 = node.pop_random_element(seeded_set, seed=42)
|
||||
result2, item2 = node.pop_random_element(seeded_set, seed=42)
|
||||
assert item1 == item2
|
||||
assert result1 == result2
|
||||
|
||||
|
||||
def test_set_union():
|
||||
node = SetUnion()
|
||||
|
||||
Reference in New Issue
Block a user