【问题标题】:Combine lists having a specific merge order in a pythonic way?以pythonic方式组合具有特定合并顺序的列表?
【发布时间】:2017-11-22 20:24:20
【问题描述】:

我想从yz 两个列表中构造列表x。我希望y 中的所有元素都放在ypos 元素指向的位置。例如:

y = [11, 13, 15]
z = [12, 14]
ypos = [1, 3, 5]

所以,x 必须是 [11, 12, 13, 14, 15]

另一个例子:

y = [77]
z = [35, 58, 74]
ypos = [3]

所以,x 必须是 [35, 58, 77, 74]

我已经编写了我想要的功能,但它看起来很丑:

def func(y, z, ypos):
    x = [0] * (len(y) + len(z))
    zpos = list(range(len(y) + len(z)))
    for i, j in zip(y, ypos):
        x[j-1] = i
        zpos.remove(j-1)
    for i, j in zip(z, zpos):
        x[j] = i
    return x

如何用pythonic的方式写?

【问题讨论】:

  • 我猜你需要把这个问题发到codereview.stackexchange.com
  • @TigranSaluev 更改名称就可以了,所以我认为不是。
  • @martineau,抱歉,已修复
  • @scharette 他有工作代码,并正在寻求改进它的风格以更适合一种语言。这不在 SO 的范围内。

标签: python


【解决方案1】:

如果列表很长,重复调用insertmight not be very efficient。或者,您可以从列表中创建两个iterators,并通过从任一迭代器中获取next 元素来构造一个列表,具体取决于当前索引是否在ypos(或其中的set)中:

>>> ity = iter(y)
>>> itz = iter(z)
>>> syp = set(ypos)
>>> [next(ity if i+1 in syp else itz) for i in range(len(y)+len(z))]
[11, 12, 13, 14, 15]

注意:这将按照它们在y 中出现的顺序插入来自y 的元素,即y 的第一个元素插入到ypos最低 索引处,不一定在 yposfirst 索引处。如果y 的元素应该插入到ypos对应元素 的索引处,那么ypos 必须按升序排列(即ypos 的第一个索引是也是最低的),或者y 的迭代器必须按照与ypos 中的索引相同的顺序进行排序(之后,ypos 本身不必排序,因为我们将其转换为@987654340 @反正)。

>>> ypos = [5,3,1]   # y and z being same as above
>>> ity = iter(e for i, e in sorted(zip(ypos, y)))
>>> [next(ity if i+1 in syp else itz) for i in range(len(y)+len(z))]
[15, 12, 13, 14, 11]

【讨论】:

  • 优秀的方法。它模仿了你从两副牌中分配卡片的方式。
  • 可以等效地使i+1 只是i 并将范围更改为range(1, len(y)+len(z)+1),因为它会删除n - 1 i+1 操作,但这实际上只是一个挑剔的微优化。
  • 一开始并不清楚,但您基本上再次对ypos 进行排序,因为您使用增加的i 进行迭代。你可以试试f([15, 13, 11], [12, 14], [5, 3, 1])。它返回[15, 12, 13, 14, 11],好像ypos[1, 3, 5]
  • @EricDuminil 啊,现在我明白你的意思了。但是ypos 不必排序,相反,我的方法忽略了ypos 的任何顺序,只是按照它们的顺序添加来自y 的元素,而不是在ypos 中的“对应”位置。有趣的一点。
  • @tobias_k:确实。 ypos 可以按照您想要的任何顺序编写,但 y 应该按照与排序后的 ypos 相同的顺序编写。 :)
【解决方案2】:

你应该使用list.insert,这就是它的用途!

def func(y, z, ypos):
    x = z[:]
    for pos, val in zip(ypos, y):
        x.insert(pos-1, val)
    return x

还有一个测试:

>>> func([11, 13, 15], [12, 14], [1,3,5])
[11, 12, 13, 14, 15]

【讨论】:

  • 因为问题是关于构建一个新列表,您应该复制z,这样它就不会修改原始列表。
  • 假设索引是有序的
  • @C.Feenstra 问题假设他们是。不可能知道应该如何订购它们(即我们是只订购indexes,还是我们zip 然后订购)所以我认为这没有必要。但是,如果 OP 明确说明他们在这方面的立场,那么无论如何,我会更新答案 :)
  • 是的,x 中的顺序必须与您的答案一样
  • 如果ypos可能没有被订购,你可以使用sorted(ypos)for pos, val in zip(sorted(ypos), y):
【解决方案3】:

对于大型列表,使用numpy 可能是个好主意。

算法

  • 创建一个与y + z一样大的新数组
  • 计算z 值的坐标
  • y 值分配给x ypos
  • z 值分配给x zpos

复杂度应该是O(n)n 是值的总数。

import numpy as np

def distribute_values(y_list, z_list, y_pos):
    y = np.array(y_list)
    z = np.array(z_list)
    n = y.size + z.size
    x = np.empty(n, np.int)
    y_indices = np.array(y_pos) - 1
    z_indices = np.setdiff1d(np.arange(n), y_indices, assume_unique=True)
    x[y_indices] = y
    x[z_indices] = z
    return x

print(distribute_values([11, 13, 15], [12, 14], [1, 3, 5]))
# [11 12 13 14 15]
print(distribute_values([77], [35, 58, 74], [3]))
# [35 58 77 74]

作为奖励,当ypos 未排序时,它也可以正常工作:

print(distribute_values([15, 13, 11], [12, 14], [5, 3, 1]))
# [11 12 13 14 15]
print(distribute_values([15, 11, 13], [12, 14], [5, 1, 3]))
# [11 12 13 14 15]

性能

n 设置为100 万,这种方法比@tobias_k's answer 快一点,比@Joe_Iddon's answer 快500 倍。

列表是这样创建的:

from random import random, randint
N = 1000000
ypos = [i+1 for i in range(N) if random()<0.4]
y = [randint(0, 10000) for _ in ypos]
z = [randint(0, 1000) for _ in range(N - len(y))

以下是%timeit 和 IPython 的结果:

%timeit eric(y, z, ypos)
131 ms ± 1.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit tobias(y, z, ypos)
224 ms ± 977 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit joe(y,z, ypos)
54 s ± 1.48 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

  • 不错的时序分析,但我会说我只需要 1.5 倍的时间(事实上,我分别得到了 135 和 179 毫秒)。 :-P 惊讶的乔的速度并不慢,不过,我希望它是二次的。
  • @tobias_k:是的,这取决于系统和底层库。对于这么大的列表,因子 1.5 或 2 并不多。 Joe 用较小的列表回答让我感到惊讶。例如,使用n=100,它实际上比你的要快一点。
【解决方案4】:

假设ypos 索引已排序,这是另一种使用迭代器的解决方案,虽然这个解决方案也支持未知或无限长度的ypos

import itertools

def func(y, ypos, z):
    y = iter(y)
    ypos = iter(ypos)
    z = iter(z)
    next_ypos = next(ypos, -1)
    for i in itertools.count(start=1):
        if i == next_ypos:
            yield next(y)
            next_ypos = next(ypos, -1)
        else:
            yield next(z)

【讨论】:

    【解决方案5】:

    如果您希望将ypos 中的元素放置在x 索引处,其中ypos 中每个元素的索引应与相同的y 索引元素对应:

    1. 使用所有空值将x 初始化为所需的大小。
    2. 遍历压缩后的yypos 元素,将每个对应的y 元素填入x
    3. 遍历x 并将每个剩余的空值替换为z 值,其中每个替换将从z 中选择增加 订购。

    y = [11, 13, 15]
    z = [12, 14]
    ypos = [1, 5, 3]
    
    x = [None] * (len(y) + len(z))
    for x_ypos, y_elem in zip(ypos, y):
        x[x_ypos - 1] = y_elem
    
    z_iter = iter(z)
    x = [next(z_iter) if i is None else i for i in x]
    # x -> [11, 12, 15, 14, 13]
    

    【讨论】:

      【解决方案6】:

      Python 方式

      y = [11, 13, 15]
      z = [12, 14]
      ypos = [1, 3, 5]
      
      x = z[:]
      
      for c, n in enumerate(ypos):
          x.insert(n - 1, y[c])
      
      print(x)
      

      输出

      [11、12、13、14、15]

      在函数中

      def func(y, ypos, z):
          x = z[:]
          for c,n in enumerate(ypos):
              x.insert(n-1,y[c])
          return x
      
      print(func([11,13,15],[1,2,3],[12,14]))
      

      输出

      [11、12、13、14、15]

      使用 zip

      y, z, ypos = [11, 13, 15], [12, 14], [1, 3, 5]
      
      for i, c in zip(ypos, y):
          z.insert(i - 1, c)
      
      print(z)
      

      [输出:]

      > [11, 12, 13, 14, 15]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-07
        • 1970-01-01
        • 2015-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-15
        • 1970-01-01
        相关资源
        最近更新 更多