如何以相同的顺序打乱多个迭代?
How to shuffle multiple iterables in same order?
我寻求一个简单的 Python 函数,它接受任意数量的迭代(元组、列表、字典),并且 returns 它们以相同的顺序 随机排列 :
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
x, y, z = magic(a, b, c)
print(x, y, z, sep='\n')
# ({3: 4}, 1, 2)
# [[9, 0], (5, 6), [7, 8]]
# {0: ('x', 2), 'arrow': 5, 'knee': 'guard'}
函数必须:
- Return iterables 以相同的顺序洗牌(见上文)
- 接受任意数量的迭代器
- 保留迭代器类型
- 支持任何 depth 和 type
的嵌套迭代
- Not 打乱嵌套元素本身(例如上面的
[7,8]
不会变成 [8,7]
)
- Return 长度为最短迭代器长度的迭代器 w/o 引发错误(见上文)
如果在洗牌步骤中使用 Numpy、随机等(例如 np.random.shuffle(magic_packing)
),则可以,但 不能 是高级库方法(使用多处理、编码, 等等 - 应该是 'plain')
我见过 related SO's,但无法使它们适应这种普遍情况。如何实现?
这是一个基本方法:
import random
def shuffle_containers(*args):
min_length = min(map(len, args))
idx = list(range(min_length))
random.shuffle(idx)
results = []
for arg in args:
if isinstance(arg, list):
results.append([arg[i] for i in idx])
elif isinstance(arg, tuple):
results.append(tuple(arg[i] for i in idx))
elif isinstance(arg, dict):
items = list(arg.items())
results.append(dict(items[i] for i in idx))
else:
raise ValueError(
"Encountered", type(arg),
"expecting only list, dict, or tuple"
)
return results
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
x, y, z = shuffle_containers(a, b, c)
print(x, y, z, sep='\n')
请注意,这将忽略超过最小容器长度的任何项目,如果您不想这样,则需要更复杂的逻辑。
编辑:
这里是两行代码:
def shuffle_containers(*args):
min_length = min(map(len, args)); idx = list(range(min_length)); random.shuffle(idx)
return [ [arg[i] for i in idx] if isinstance(arg, list) else tuple(arg[i] for i in idx) if isinstance(arg, tuple) else dict(list(args.items())[i] for i in idx) ]
当然,上面的代码可读性、效率和简单性都比较差。不要那样做。
import random
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
def magic(*x):
out = []
# 6. length of shortest iterable
min_len = min(len(a_org) for a_org in x)
for a_org in x:
if isinstance(a_org, list) or isinstance(a_org, tuple):
indices = list(range(len(a_org)))
random.shuffle(indices)
a_copy = type(a_org)(a_org[i] for i in indices[:min_len])
elif isinstance(a_org, dict):
indices = list(a_org.keys())
random.shuffle(indices)
a_copy = {i:a_org[i] for i in indices[:min_len]}
else:
raise "not supported type"
out.append(a_copy)
return tuple(out)
print(magic(a, b, c))
def ordered_shuffle(*args):
args_types = [type(arg) for arg in args] # [1]
_args = [arg if type(arg)!=dict else arg.items() for arg in args] # [2]
args_split = [arg for arg in zip(*_args)] # [3]
args_shuffled = random.sample(args_split, len(args_split)) # [4]
args_shuffled = map(tuple, zip(*args_shuffled)) # [5]
return [args_types[i](arg) for i, arg in enumerate(args_shuffled)] # [6]
解释:
举个更简单的例子,一步一步来:
a = [1, 2, 3]
b = ([1,2], [3,4], [5,6])
c = {'a': 1, 'b': 2, 'c': 3}
# [1]: list, tuple, dict
# [2]: [[1, 2, 3],
# ([1, 2], [3, 4], [5, 6]),
# dict_items([('a', 1), ('b', 2), ('c', 3)])]
# [3]: [(1, [1, 2], ('a', 1)),
# (2, [3, 4], ('b', 2)),
# (3, [5, 6], ('c', 3))]
# [4]: [(1, [1, 2], ('a', 1)),
# (3, [5, 6], ('c', 3)),
# (2, [3, 4], ('b', 2))]
# [5]: (1, 2, 3)
# ([1, 2], [3, 4], [5, 6])
# (('a', 1), ('b', 2), ('c', 3))
# [6]: [(1, 2, {3: 4}),
# [(5, 6), [7, 8], [9, 0]],
# {'arrow': 5, 'knee': 'guard', 0: ('x', 2)}]
- 存储原始类型以便稍后恢复
- 默认情况下,Python 仅遍历字典 KEYS - 我们还需要值
- Python 字典不能直接改组 - 而是通过首先将键值对转换为 元组 来间接改组;所有可迭代对象也应该同时迭代以重组,完成w/
zip
- 使用Python的原生
random
- 也可以转换为
list
,但 tuple
效率更高
- 恢复原始类型和return作为要解压为
x, y = ordered_shuffle(a, b)
的元组
Lambda 解决方案:(来源:Gloweye)
ordered_shuffle = lambda *args:[type(args[i])(arg) for i, arg in enumerate(map(tuple,
zip(*random.sample([y for y in zip(*[x if type(x)!=dict else x.items()
for x in args])], min(len(z) for z in args)))))]
最短和最快的解决方案:(信用:GZ0)(缩短变量名称使其最短)
def ordered_shuffle(*args):
zipped_args = list(zip(*(a.items() if isinstance(a, dict) else a for a in args)))
random.shuffle(zipped_args)
return [cls(elements) for cls, elements in zip(map(type, args), zip(*zipped_args))]
我寻求一个简单的 Python 函数,它接受任意数量的迭代(元组、列表、字典),并且 returns 它们以相同的顺序 随机排列 :
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
x, y, z = magic(a, b, c)
print(x, y, z, sep='\n')
# ({3: 4}, 1, 2)
# [[9, 0], (5, 6), [7, 8]]
# {0: ('x', 2), 'arrow': 5, 'knee': 'guard'}
函数必须:
- Return iterables 以相同的顺序洗牌(见上文)
- 接受任意数量的迭代器
- 保留迭代器类型
- 支持任何 depth 和 type 的嵌套迭代
- Not 打乱嵌套元素本身(例如上面的
[7,8]
不会变成[8,7]
) - Return 长度为最短迭代器长度的迭代器 w/o 引发错误(见上文)
如果在洗牌步骤中使用 Numpy、随机等(例如 np.random.shuffle(magic_packing)
),则可以,但 不能 是高级库方法(使用多处理、编码, 等等 - 应该是 'plain')
我见过 related SO's,但无法使它们适应这种普遍情况。如何实现?
这是一个基本方法:
import random
def shuffle_containers(*args):
min_length = min(map(len, args))
idx = list(range(min_length))
random.shuffle(idx)
results = []
for arg in args:
if isinstance(arg, list):
results.append([arg[i] for i in idx])
elif isinstance(arg, tuple):
results.append(tuple(arg[i] for i in idx))
elif isinstance(arg, dict):
items = list(arg.items())
results.append(dict(items[i] for i in idx))
else:
raise ValueError(
"Encountered", type(arg),
"expecting only list, dict, or tuple"
)
return results
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
x, y, z = shuffle_containers(a, b, c)
print(x, y, z, sep='\n')
请注意,这将忽略超过最小容器长度的任何项目,如果您不想这样,则需要更复杂的逻辑。
编辑:
这里是两行代码:
def shuffle_containers(*args):
min_length = min(map(len, args)); idx = list(range(min_length)); random.shuffle(idx)
return [ [arg[i] for i in idx] if isinstance(arg, list) else tuple(arg[i] for i in idx) if isinstance(arg, tuple) else dict(list(args.items())[i] for i in idx) ]
当然,上面的代码可读性、效率和简单性都比较差。不要那样做。
import random
a = (1, 2, {3: 4}, 5)
b = [(5,6), [7,8], [9,0], [1,2]]
c = {'arrow': 5, 'knee': 'guard', 0: ('x',2)}
def magic(*x):
out = []
# 6. length of shortest iterable
min_len = min(len(a_org) for a_org in x)
for a_org in x:
if isinstance(a_org, list) or isinstance(a_org, tuple):
indices = list(range(len(a_org)))
random.shuffle(indices)
a_copy = type(a_org)(a_org[i] for i in indices[:min_len])
elif isinstance(a_org, dict):
indices = list(a_org.keys())
random.shuffle(indices)
a_copy = {i:a_org[i] for i in indices[:min_len]}
else:
raise "not supported type"
out.append(a_copy)
return tuple(out)
print(magic(a, b, c))
def ordered_shuffle(*args):
args_types = [type(arg) for arg in args] # [1]
_args = [arg if type(arg)!=dict else arg.items() for arg in args] # [2]
args_split = [arg for arg in zip(*_args)] # [3]
args_shuffled = random.sample(args_split, len(args_split)) # [4]
args_shuffled = map(tuple, zip(*args_shuffled)) # [5]
return [args_types[i](arg) for i, arg in enumerate(args_shuffled)] # [6]
解释: 举个更简单的例子,一步一步来:
a = [1, 2, 3]
b = ([1,2], [3,4], [5,6])
c = {'a': 1, 'b': 2, 'c': 3}
# [1]: list, tuple, dict
# [2]: [[1, 2, 3],
# ([1, 2], [3, 4], [5, 6]),
# dict_items([('a', 1), ('b', 2), ('c', 3)])]
# [3]: [(1, [1, 2], ('a', 1)),
# (2, [3, 4], ('b', 2)),
# (3, [5, 6], ('c', 3))]
# [4]: [(1, [1, 2], ('a', 1)),
# (3, [5, 6], ('c', 3)),
# (2, [3, 4], ('b', 2))]
# [5]: (1, 2, 3)
# ([1, 2], [3, 4], [5, 6])
# (('a', 1), ('b', 2), ('c', 3))
# [6]: [(1, 2, {3: 4}),
# [(5, 6), [7, 8], [9, 0]],
# {'arrow': 5, 'knee': 'guard', 0: ('x', 2)}]
- 存储原始类型以便稍后恢复
- 默认情况下,Python 仅遍历字典 KEYS - 我们还需要值
- Python 字典不能直接改组 - 而是通过首先将键值对转换为 元组 来间接改组;所有可迭代对象也应该同时迭代以重组,完成w/
zip
- 使用Python的原生
random
- 也可以转换为
list
,但tuple
效率更高 - 恢复原始类型和return作为要解压为
x, y = ordered_shuffle(a, b)
的元组
Lambda 解决方案:(来源:Gloweye)
ordered_shuffle = lambda *args:[type(args[i])(arg) for i, arg in enumerate(map(tuple,
zip(*random.sample([y for y in zip(*[x if type(x)!=dict else x.items()
for x in args])], min(len(z) for z in args)))))]
最短和最快的解决方案:(信用:GZ0)(缩短变量名称使其最短)
def ordered_shuffle(*args):
zipped_args = list(zip(*(a.items() if isinstance(a, dict) else a for a in args)))
random.shuffle(zipped_args)
return [cls(elements) for cls, elements in zip(map(type, args), zip(*zipped_args))]