【问题标题】:Order mapping - more efficient Python solution for sorting?订单映射 - 更高效的 Python 排序解决方案?
【发布时间】:2014-06-08 23:53:50
【问题描述】:

我得到了一些数据,需要创建一个排序映射。实际排序由 C 代码完成,它从我的代码中获取整数列表 flt_neworder。这是我目前的解决方案:

# Demo data
data = [
    "Option A",  # 0
    "Option B",  # 1
    "Blabla",    # 2
    "Some text"  # 3
]

class Item:
    def __init__(self, label):
        self.label = label

col = [Item(d) for d in data]

# Create sorting mapping
flt_neworder = [
   x[1] for x in sorted(
       zip(
           [x[0] for x in sorted(enumerate(col), key=lambda x: x[1].label)],
           range(len(col))
       )
   )
]

# Output: [1,2,0,3]
print(flt_neworder)
  • 所需的输出:[1,2,0,3]不是[2,0,1,3]

  • flt_neworder 中的位置 = col 中项目的原始索引

  • 整数 = 新位置

什么是更高效或至少可读性更好的解决方案?

我成功测试了这个单行:

tuple({k: i for i, (k, v) in enumerate(sorted(enumerate(data), key=operator.itemgetter(1)))}.values())

但它仍然很难阅读,我相信我正在利用 dicts 在 CPython 实现中排序的事实......

编辑

我想出的另一个解决方案:

flt_neworder = [None] * len(col)
for j, (_, i) in enumerate(sorted(zip((item.label for item in col), range(len(col))))): flt_neworder[i] = j

还有一个,但速度很慢:

flt_neworder = list(map(get(0), sorted(enumerate(sorted(enumerate(item.label for item in col), key=get(1))), key=get(1))))


感谢 Ryan P 提供替代解决方案和测试时间的脚本!

我在一个大型数据集(1k 个唯一字符串,full script)上测试了解决方案,与 Ryan 的小型数据集相比,它们在时间上存在惊人的差异:

orig: 2.116799074272876
origmod: 2.118176033553482
orignew: 1.1691872433702883
orig3: 1.4400411206224817
orig4: 2.0643228139915664
rewrite: 26.06907118537356
rewriteop: 25.91357442379376
rewriteuniq: 10.783081019086694

获胜者是orignew(),而rewriteuniq() 对小型数据集来说速度很快,但对大型数据集却不是很好。

【问题讨论】:

  • 你能解释一下“排序映射”是什么意思吗?
  • 我需要提供一个old index -> new index 映射,其中旧索引是结果列表中的位置,包含的值是新索引(0 = 项目将移至顶部)。 [1,2,0] 表示数据中的第一项应该在中间(位置 1)结束,第二项在末尾(位置 2),第三项在顶部(位置 0)。

标签: python sorting mapping


【解决方案1】:

这比您的原始代码更快,并且更易于阅读 IMO:

data = [
    'Option A',
    'Option B',
    'Blabla',
    'Some text'
]
idata = list(enumerate(data))  # add indexes to uniquely identify items
sdata = sorted(idata, key=lambda x: x[1])  # sort the items by label
flt_neworder = [sdata.index(x) for x in idata]  # find the position to move to

timeit 的结果:

orig: 12.3757910728
origmod: 7.85222291946
orignew: 6.15745902061
rewrite: 6.31552696228

(origmod 就像您的原始代码,但没有 Item 类,因为您似乎没有使用它;orignew 是您的单行代码)

您的单行字稍快,但我认为更难阅读。


好的,这次我将包含我的完整测试代码。我将Item 的创建从orig 中移出,因为您创建这些只是为了模仿真实世界的数据。除了orig3(您的新代码)和rewriteoprewriteoperator.itemgetter)之外,我还添加了一个额外的测试rewriteuniq,以防您的字符串是唯一的。

结果:

orig: 7.641715765
origmod: 7.38071417809
orignew: 5.82565498352
orig3: 5.67061495781
rewrite: 5.95284795761
rewriteop: 5.61896586418
rewriteuniq: 1.90719294548

代码:

import operator
from timeit import timeit

data = [
    'Option A',
    'Option B',
    'Blabla',
    'Some text',
]

desired_output = [1, 2, 0, 3]

class Item:
    def __init__(self, label):
        self.label = label

col = [Item(d) for d in data]


def orig():
    flt_neworder = [
        x[1] for x in sorted(
            zip(
                [x[0] for x in sorted(enumerate(col), key=lambda x: x[1].label)],
                range(len(col))
            )
        )
    ]

    assert flt_neworder == desired_output

def origmod():
    flt_neworder = [
        x[1] for x in sorted(
            zip(
                [x[0] for x in sorted(enumerate(data), key=lambda x: x[1])],
                range(len(data))
            )
        )
    ]

    assert flt_neworder == desired_output

def orignew():
    flt_neworder = list({k: i for i, (k, v) in enumerate(sorted(enumerate(data), key=operator.itemgetter(1)))}.values())
    assert flt_neworder == desired_output

def orig3():
    flt_neworder = [None] * len(col)
    for j, (_, i) in enumerate(sorted(zip((item.label for item in col), range(len(col))))): flt_neworder[i] = j

    assert flt_neworder == desired_output

def rewrite():
    idata = list(enumerate(data))
    sdata = sorted(idata, key=lambda x: x[1])
    flt_neworder = [sdata.index(x) for x in idata]

    assert flt_neworder == desired_output

def rewriteop():
    idata = list(enumerate(data))
    sdata = sorted(idata, key=operator.itemgetter(1))
    flt_neworder = [sdata.index(x) for x in idata]

    assert flt_neworder == desired_output

def rewriteuniq():
    sdata = sorted(data)
    flt_neworder = [sdata.index(x) for x in data]

    assert flt_neworder == desired_output

for fn in (orig, origmod, orignew, orig3, rewrite, rewriteop, rewriteuniq):
    print fn.__name__ + ':', timeit(fn)

【讨论】:

  • 谢谢,你的看起来确实更容易理解(除非打单线)。我不使用那个 Item 类,但它模拟了actual setup。你会这么好心并添加我添加到问题中的第三个解决方案的时间吗?也许还看看operator.itemgetter(1) 是否比你的 lambda 快?
  • 是的,operator.itemgetter 比 lambda 更快(通常应该如此)。我包含了我的测试代码,以便您更轻松地添加您想到的任何其他测试。 :)
  • 非常感谢您所做的一切! .index() 对于小型数据集来说速度非常快,但它不能很好地扩展 - 请参阅我在问题中的编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-22
  • 1970-01-01
  • 2019-03-21
  • 2021-05-06
  • 1970-01-01
  • 2018-06-21
  • 2022-01-18
相关资源
最近更新 更多