【问题标题】:How to shuffle multiple iterables in same order? [closed]如何以相同的顺序打乱多个迭代? [关闭]
【发布时间】:2020-01-30 07:13:23
【问题描述】:

我寻找一个简单的 Python 函数,它接受任意数量的可迭代对象(元组、列表、字典),并以相同的顺序返回它们

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'}

函数必须:

  1. 返回按相同顺序打乱的迭代(见上文)
  2. 接受任意数量的迭代
  3. 保留迭代类型
  4. 支持任何深度类型的嵌套迭代
  5. 洗牌嵌套元素本身(例如,上面的[7,8]不会变成[8,7]
  6. 返回长度为最短迭代长度且不引发错误的迭代(见上文)

如果在随机播放步骤中使用 Numpy、random 等(例如 np.random.shuffle(magic_packing)),则可以,但 不能 是高级库方法(使用多处理、编码等 - 应该是 'plain ')


我见过related SO's,但无法使它们适应这种笼统的情况。如何实现?

【问题讨论】:

  • 所以你的意思是tuplelistdict 对象只有?请注意,dict 对象仅在 Python +3.7 中排序...您还可以假设对象具有相同的长度吗?
  • 很抱歉,但这与numpy 有什么关系?
  • @OverLordGoldDragon 当然可以。在random 模块中。
  • VTCing 作为一个问题的多个关注点。这与 SO 无关,因为对更多读者没有用处。选择一个 - 在您的一长串功能列表中,您对哪个具体项目有疑问?
  • 如果您正在寻找改进现有解决方案的方法,那就是 Code Review 的领域(请阅读他们的指南)。

标签: python random python-3.7 iterable


【解决方案1】:

这是一个基本的方法:

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) ]

当然,上面的代码可读性差、效率低、简单。不要做那样的事情。

【讨论】:

  • @OverLordGoldDragon 什么会“更简单”,这很简单。无论如何,如果您想保持顺序,例如[1,2,3,4]['a','b','c'],这应该如何工作,如果它没有忽略第一个列表中的4,您会怎么做?
  • 这和什么有什么关系?我可以把它浓缩成一个可怕的列表理解,需要 6 行,但这不会是“更简单”,而是“不可读和不python”。
【解决方案2】:
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))

【讨论】:

  • 有效,但失败 6
  • 哦,我写这篇文章的时候并没有看到约束。看来我的回答与@juanpa.arrivillaga 具有相同的精神
【解决方案3】:
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)}]
  1. 存储原始类型以便以后恢复
  2. 默认情况下,Python 仅对 dict KEYS 进行迭代 - 我们还需要值
  3. Python 字典不能直接改组 ​​- 而是通过首先将键值对转换为 元组 来间接改组;所有可迭代对象也应该同时进行迭代以进行重组,通过zip 完成
  4. 使用Python原生的random
  5. 也可以转换成list,但tuple更高效
  6. 恢复原始类型并以元组形式返回以解包为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)(缩短 var 名称以使其最短)
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))]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-09
    • 1970-01-01
    • 2012-08-30
    • 2019-01-15
    • 2012-01-09
    • 2013-12-10
    • 2012-05-22
    相关资源
    最近更新 更多