【问题标题】:Low Complexity Python Sort (Small - Large - Small) by a Class Attribute低复杂度 Python 按类属性排序(小 - 大 - 小)
【发布时间】:2021-05-14 13:49:24
【问题描述】:

我希望将我的列表(类)按小 - 大 - 小的顺序排序,例如,如果它是纯数字并且列表是 [1,5,3,7,7,3,2]排序看起来像 [1,3,7,7,5,3,2]。

基本的类结构是:

class LaneData:
    def __init__(self):
        self.Name = "Random"
        self.laneWeight = 5

所以本质上,排序函数可以使用 LaneData.laneWeight 变量。 我找到了这个答案here,我不完全确定它是否可以在这个实例中使用一个类作为列表变量。

我的第二个想法是这样的(伪代码如下) [inspired by]:

def sortByWeight(e):
    return e.getLaneWeight()

newList = [] # create a new list to store
lanes.sort(key=sortByWeight) # python default sort by our class attrib
newList = lanes[:len(lanes) // 2] # get the first half of the sorted list (low-high)
lanes.sort(key=sortByWeight, reverse=True) # reverse the sort
newList = newList + lanes[:len(lanes) // 2] # get the first half of the sorted list (high-low)

如果可能的话,我想保持排序小而高效,如果需要的话,我不介意为它构建一个小算法。 你的想法团队是什么?

【问题讨论】:

  • 您实际上不需要第二次使用sort(尽管内置排序对于已经排序的列表很有效)。 newList = lanes[:n] + list(reversed(lanes[n:])) 就足够了。
  • 这个问题有几个问题:1.不清楚你的算法应该使用什么标准; 2.你提供的伪代码应该是MWE; 3.您提出的算法不适用于您的示例; 3. 你应该正确地抽象你的算法问题(即将类属性访问与问题陈述的其余部分分开,因为这在很大程度上是不相关的)。
  • @chepner 我明白你在说什么,但这如何在类属性上起作用?
  • @norok2 感谢您的反馈。 1.标准很简单,重新排序列表,使最大的数字在列表的中间,越往下/越高(索引)数字越小。任何一方的精确平衡都不是问题,更重要的是小-大-小的基本概念。 3. 我相信我说过,我错过了什么吗?我觉得值得一提,因为它是一个示例,以更基本的形式展示了我正在尝试的内容。

标签: python python-3.x list sorting


【解决方案1】:

您的解决方案有效,但您先按升序对列表末尾进行排序,然后按降序排序。

您可以通过以下方式对其进行优化:

  • 寻找max的索引,将max与中间位置的元素交换,最后分别对表的前半部分(升序)和后半部分(降序)进行排序。

  • 您可以简单地反转它(使用 reverse 函数),而不是对后半部分进行反向排序。这不太复杂(O(n)而不是O(nlog(n))

【讨论】:

  • 我很确定内置排序对于已排序的输入来说已经是 O(n),因为算法的一部分涉及在输入中寻找单调子序列。
  • 是的,我相信您的第一个优化应该有效,目前您认为它有效,但它有可能因为除以二而跳过一个列表项。
【解决方案2】:

讨论

鉴于你可以只使用key参数,我暂时忽略它。

给定序列的算法如下所示:

def middle_sort_flip_OP(seq, key=None):
    result = []
    length = len(seq)
    seq.sort(key=key)
    result = seq[:length // 2]
    seq.sort(key=key, reverse=True)
    result.extend(seq[:length // 2])
    return result
    

print(middle_sort_flip_OP([1, 5, 3, 7, 3, 2, 9, 8]))
# [1, 2, 3, 3, 9, 8, 7, 5]
print(middle_sort_flip_OP([1, 5, 3, 7, 3, 2, 8]))
# [1, 2, 3, 8, 7, 5]

第二个排序步骤是完全没有必要的(但计算复杂度与在 Python 中实现的 Timsort 算法的简单反转相同),因为您可以简单地将排序后的序列向后切片(确保计算正确“中间”元素的偏移量):

def middle_sort_flip(seq, key=None):
    length = len(seq)
    offset = length % 2
    seq.sort(key=key)
    return seq[:length // 2] + seq[:length // 2 - 1 + offset:-1]
    

print(middle_sort_flip([1, 5, 3, 7, 3, 2, 9, 8]))
# [1, 2, 3, 3, 9, 8, 7, 5]
print(middle_sort_flip([1, 5, 3, 7, 3, 2, 8]))
# [1, 2, 3, 8, 7, 5]

另一种理论上更有效的方法是分别对序列的左侧和右侧进行排序。这更有效,因为每个排序步骤都是O(N/2 log N/2),并且当组合时会给出O(N log N/2)(而不是O(N + N log N)):

def middle_sort_half(seq, key=None):
    length = len(seq)
    return \
        sorted(seq[:length // 2], key=key) \
        + sorted(seq[length // 2:], key=key, reverse=True)

但是,这些方法要么在整个右侧大于左侧 (middle_sort_flip()) 时给出很大程度上不平衡的结果,要么具有取决于输入的初始顺序的平衡 (middle_sort_half())。

通过提取和重新组合奇偶子序列可以获得更平衡的结果。由于切片操作,这在 Python 中非常简单,并且具有与 middle_sort_flip() 相同的渐近复杂度,但具有更好的平衡属性:

def middle_sort_mix(seq, key=None):
    length = len(seq)
    offset = length % 2
    seq.sort(key=key)
    result = [None] * length
    result[:length // 2] = seq[::2]
    result[length // 2 + offset:] = seq[-1 - offset::-2]
    return result


print(middle_sort_mix([1, 5, 3, 7, 3, 2, 9, 8]))
# [1, 3, 5, 8, 9, 7, 3, 2]
print(middle_sort_mix([1, 5, 3, 7, 3, 2, 8]))
# [1, 3, 5, 8, 7, 3, 2]

基准测试

在不使用key参数时,它们都非常相似,因为执行时间主要由复制来控制:

import random


nums = [10 ** i for i in range(1, 7)]
funcs = middle_sort_flip_OP, middle_sort_flip, middle_sort_half, middle_sort_mix
print(nums)
# [10, 100, 1000, 10000, 100000, 1000000]


def gen_input(num):
    return list(range(num))


for num in nums:
    print(f"N = {num}")
    for func in funcs:
        seq = gen_input(num)
        random.shuffle(seq)
        print(f"{func.__name__:>24s}", end="  ")
        %timeit func(seq.copy())
    print()
...
N = 1000000
     middle_sort_flip_OP  542 ms ± 54.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_flip  510 ms ± 49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_half  546 ms ± 4.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
         middle_sort_mix  539 ms ± 63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

另一方面,当key 参数不平凡时,您的方法与其他两个方法相比,函数调用量要大得多,这可能会导致middle_sort_OP() 的执行时间显着增加:

def gen_input(num):
    return list(range(num))


def key(x):
    return x ** 2


for num in nums:
    print(f"N = {num}")
    for func in funcs:
        seq = gen_input(num)
        random.shuffle(seq)
        print(f"{func.__name__:>24s}", end="  ")
        %timeit func(seq.copy(), key=key)
    print()
...
N = 1000000
     middle_sort_flip_OP  1.33 s ± 16.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_flip  1.09 s ± 23.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_half  1.1 s ± 27.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
         middle_sort_mix  1.11 s ± 8.88 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

或者,更接近您的用例:

class Container():
    def __init__(self, x):
        self.x = x
        
    def get_x(self):
        return self.x

    
def gen_input(num):
    return [Container(x) for x in range(num)]


def key(c):
    return c.get_x()


for num in nums:
    print(f"N = {num}")
    for func in funcs:
        seq = gen_input(num)
        random.shuffle(seq)
        print(f"{func.__name__:>24s}", end="  ")
        %timeit func(seq.copy(), key=key)
    print()
...
N = 1000000
     middle_sort_flip_OP  1.27 s ± 4.44 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_flip  1.13 s ± 13.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        middle_sort_half  1.24 s ± 12.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
         middle_sort_mix  1.16 s ± 8.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

这似乎没有那么戏剧化。

【讨论】:

    猜你喜欢
    • 2020-01-07
    • 2019-04-24
    • 2013-06-21
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    相关资源
    最近更新 更多