尝试在列表中查找第二个最大值时出错

Error when trying to find 2nd maximum value in a list

提问人:Blue 提问时间:11/4/2023 最后编辑:mkrieger1Blue 更新时间:11/15/2023 访问量:506

问:

我正在尝试编写代码来查找列表的第二个最大值。

我是这样试的:

arr = map(int, input().split())
lista = list(arr)
max_value = lista[0]
run = lista[0]
for i in lista:
    if max_value < i:
        max_value = i
for j in lista:
    if run < j and run < max_value:
        run = j
print(run)

第二个最大值和最大值是相同的。我的程序中有什么错误?

Python 列表 最大

评论


答:

7赞 Karine Bauch 11/4/2023 #1

问题

顶级域名

    run = lista[0]
    ...
    if run < j and run < max_value:

应该是

          v
    run = min(lista)
    ...
                   v
    if run < j and j < max_value:

如何找到问题

1. 让代码更简单

理解而不是映射 - 列表构造函数继承

我们可以通过使用理解而不是连续的映射和列表构造函数来减少第一行的样板:

arr = map(int, input().split())
lista = list(arr)

成为:

lista = [int(n) for n in input().split()]

向 Python 询问最大值,不要自己动手

逻辑的下一个块是在这里找到列表的最大值。 你不需要自己写这段代码,你可以问 Python。这将降低代码的复杂性,使问题/错误更容易被发现。

max_value = lista[0]
for i in lista:
    if max_value < i:
        max_value = i

成为:

max_value = max(lista)

重命名变量

为了帮助我们的大脑理解发生了什么,让我们澄清变量名称:

lista = [int(n) for n in input().split()]
max_value = max(lista)
run = lista[0]
for j in lista:
    if run < j and run < max_value:
        run = j
print(run)

成为:

user_input = [int(n) for n in input().split()]
max_value = max(user_input)

second_max = user_input[0]
for current_value in user_input:
    if second_max < current_value and second_max < max_value:
        second_max = current_value
        
print(second_max)

2. 找到问题

现在,代码更小,更易于理解,出错的地方更少。如果有错误,我们不必看很多地方。

怎么了? 应该是第二个最大值,但事实并非如此。 是什么原因导致的?不应该发生更新。second_maxprevious_value

所以这就是问题可能发生的地方。

    if second_max < current_value and second_max < max_value:
        second_max = current_value

归因正确,条件应该错误。

这似乎是正确的,因为我们只想在低于 current_value 时进行更新(这意味着 current 可能是真实值或等于 。 所以我们需要另一个条件:不应该是 ,否则,可以设置为 .second_max < current_valuesecond_maxsecond_maxmax_valuecurrent_valuemax_valuesecond_maxmax_value

而且,我们看一下第二个条件:。这是我们的错误。second_max < max_value

让我们修复条件,因为它应该低于 。 此外,如果第一个值是最大值,则需要将初始值设置为最小值。current_valuemax_valuesecond_max

user_input = [int(n) for n in input().split()]
max_value = max(user_input)

second_max = min(user_input)
for current_value in user_input:
    if second_max < current_value and current_value < max_value:
        second_max = current_value

print(second_max)  # 55

做。

备选方案:使用 set、sorted 和 index

如果您想要列表中的第二个最大值,则更容易对重复的列表进行排序,并在列表的索引 1 处打印元素,例如第二个元素。

分步示例

without_duplicates = {int(n) for n in input().split()}
ordered_without_duplicates = sorted(without_duplicates, reverse=True)
print(ordered_without_duplicates[1])

Oneliner 示例

print(sorted({int(n) for n in input().split()}, reverse=True)[1])

使用 heapq

print(heapq.nlargest(2, {int(n) for n in input().split()})[1])

评论

0赞 Blue 11/5/2023
如果输入为 [4,4,-4,4],则第二个最大值为 4
0赞 Blue 11/5/2023
上述异常的问题在于,它将运行器值(秒最大值)作为最大值开始,并在整个循环中保持不变
0赞 Dorian Turba 11/5/2023
@blue应该有效= min()
4赞 Luuk 11/4/2023 #2

第二个循环中的测试不正确,请使用以下命令:

arr = map(int, input().split())
lista = list(arr)
max_value = lista[0]
run = lista[0]
for i in lista:
    if max_value < i:
        max_value = i
for j in lista:
    if run<j and j!=max_value:
        run = j
print(run)

选中的值 () 不应等于 。jmax_value

0赞 pavan 11/4/2023 #3
def second_max(arr):
    first=second=0
    for i in arr:
        if i> first:
            second=first
            first=i
        elif i> second:
            second=i
    return second


  

评论

0赞 dawg 11/4/2023
伟大的贡献,但应该是.如果 中的所有条目都是负数怎么办?first=second=0first=second=arr[0]arr
2赞 dawg 11/4/2023 #4

有趣的是,与其他常用方法相比,您的方法(带有逻辑校正)非常快。它在下面的基准中。f1

这是一个在线基准测试

如果您想在自己的计算机上尝试,请执行以下代码:

import random 
import time 

def f1(lista):
    max1,max2 = [lista[0]]*2
    for i in lista:
        if max1 < i:
            max1 = i
            
    for j in lista:
        if max2<j and j!=max1:
            max2 = j
    return max1,max2

def f2(lista):
    max1=max(lista)
    return max1,max(set(lista)-{max1})

def f3(lista):
    return tuple(sorted(set(lista))[-2:][::-1])

def f4(lista):
    max1 = max(lista)
    max2 = max(lista, key=lambda x: min(lista)-1 if (x == max1) else x)
    return max1,max2 

def f5(lista):
    max1=max(lista)
    max2=max(x for x in lista if x < max1)
    return max1,max2 

def f6(lista):
    import heapq
    heap = [(-x, x) for x in lista]
    heapq.heapify(heap)
    _,max1=heapq.heappop(heap)
    _,max2=heapq.heappop(heap)
    return max1,max2 

def f7(lista):
    max1 = max(lista[0], lista[1]) 
    max2 = min(lista[0], lista[1]) 
    n = len(lista)
    for i in range(2,n): 
        if lista[i] > max1: 
            max2 = max1
            max1 = lista[i] 
        elif lista[i] > max2 and max1 != lista[i]: 
            max2 = lista[i]
        elif max1 == max2 and max2 != lista[i]:
                max2 = lista[i]
                
    return max1,max2      

def f8(lista):
    max1=max2=lista[0]
    for e in lista:
        if e > max1:
            max2=max1
            max1=e
        elif e > max2:
            max2=e
    return max1,max2       

def cmpthese(funcs, args=(), cnt=1000, rate=True, micro=True):
    """Generate a Perl style function benchmark"""                   
    def pprint_table(table):
        """Perl style table output"""
        def format_field(field, fmt='{:,.0f}'):
            if type(field) is str: return field
            if type(field) is tuple: return field[1].format(field[0])
            return fmt.format(field)     
        
        def get_max_col_w(table, index):
            return max([len(format_field(row[index])) for row in table])         
        
        col_paddings=[get_max_col_w(table, i) for i in range(len(table[0]))]
        for i,row in enumerate(table):
            # left col
            row_tab=[row[0].ljust(col_paddings[0])]
            # rest of the cols
            row_tab+=[format_field(row[j]).rjust(col_paddings[j]) for j in range(1,len(row))]
            print(' '.join(row_tab))                
            
    results={}
    for i in range(cnt):
        for f in funcs:
            start=time.perf_counter_ns()
            f(*args)
            stop=time.perf_counter_ns()
            results.setdefault(f.__name__, []).append(stop-start)
    results={k:float(sum(v))/len(v) for k,v in results.items()}     
    fastest=sorted(results,key=results.get, reverse=True)
    table=[['']]
    if rate: table[0].append('rate/sec')
    if micro: table[0].append('\u03bcsec/pass')
    table[0].extend(fastest)
    for e in fastest:
        tmp=[e]
        if rate:
            tmp.append('{:,}'.format(int(round(float(cnt)*1000000.0/results[e]))))
            
        if micro:
            tmp.append('{:,.1f}'.format(results[e]/float(cnt)))
            
        for x in fastest:
            if x==e: tmp.append('--')
            else: tmp.append('{:.1%}'.format((results[x]-results[e])/results[e]))
        table.append(tmp) 
        
    pprint_table(table)                    
    
if __name__=='__main__':
    lista=[random.randint(-1e6,1e6) for _ in range(100000)]
    funcs=[f1,f2,f3,f4,f5,f6,f7,f8]
    for f in funcs: print(f'{f.__name__}: {f(lista)}')
    cmpthese(funcs, [lista])

在最近的 Mac 上,这会打印:

f1: (999991, 999978)
f2: (999991, 999978)
f3: (999991, 999978)
f4: (999991, 999978)
f5: (999991, 999978)
f6: (999991, 999978)
f7: (999991, 999978)
f8: (999991, 999978)
   rate/sec μsec/pass     f3     f6     f2     f4     f5     f7     f1     f8
f3      104   9,656.6     --  -3.1% -11.4% -45.9% -61.6% -71.2% -82.9% -87.3%
f6      107   9,358.3   3.2%     --  -8.6% -44.1% -60.3% -70.3% -82.3% -86.9%
f2      117   8,557.1  12.8%   9.4%     -- -38.9% -56.6% -67.5% -80.7% -85.7%
f4      191   5,227.9  84.7%  79.0%  63.7%     -- -29.0% -46.8% -68.3% -76.5%
f5      269   3,712.2 160.1% 152.1% 130.5%  40.8%     -- -25.1% -55.4% -66.9%
f7      360   2,779.1 247.5% 236.7% 207.9%  88.1%  33.6%     -- -40.4% -55.9%
f1      604   1,655.3 483.4% 465.3% 416.9% 215.8% 124.3%  67.9%     -- -25.9%
f8      815   1,226.9 687.1% 662.8% 597.5% 326.1% 202.6% 126.5%  34.9%     --

评论

0赞 Dorian Turba 11/5/2023
如果这无疑很有趣,我不认为性能是 Blue 所问的。
0赞 Blue 11/5/2023
我已经更新了答案中的正确代码,现在可能需要更长的时间 ig
0赞 Blue 11/5/2023
它可能很快,但以最大值开头的列表除外
0赞 Blue 11/5/2023
但是我很好奇,找到列表的最大值:max(list)或使用for循环哪个更快?
0赞 Dorian Turba 11/5/2023
应该是 max(list),如果您使用的是 CPython,它是用 C 语言实现的,其中 for 循环是纯 python。
0赞 Blue 11/5/2023 #5

答案无法解决列表以最大值开头的异常。为了解决这个问题,

lista = [int(n) for n in input().split()]
max_value = max(lista)
for a in lista:
    if a != max_value:
        run = a
for j in lista:
    if run<j and j!=max_value:
        run = j
print(run)

其次,两者都有效。forj!=max_valuej<max_value

2赞 hughdbrown 11/6/2023 #6

我会这样写代码:

from heapq import nlargest
from typing import List

def second_largest(values: List[int]) -> int:
    result = nlargest(2, set(values))
    return result[1]

拆分函数后,代码更具可测试性。与发帖人代码相似的调用方如下所示:

def second_largest_helper(value_str: str) -> int:
    return second_largest(map(int, value_str.split()))

如果传入的列表中有单个唯一值或没有值,则代码将引发 IndexError 异常。

以下是我测试过的简单测试数据:

if __name__ == '__main__':
    test_data = [
        ([2, 2, 3, 5, 4, 6, 5, 3, 7], 6),
        ([2, 2, 3, 5, 4, 6, 5, 3], 5),
        ([2, 2, 3, 5, 4, 5, 3], 4),
        ([2, 2, 3, 4, 5, 3], 4),
        ([2, 2, 3, 4, 3], 3),
        ([2, 2, 3, 3], 2),
        ([2, 2, 3], 2),
    ]
    for data, expected in test_data:
        print(f"{data = } {expected = }")
        assert second_largest(data) == expected

2023-11-14

您可以为下面的基准测试代码编写如下代码:

def second_largest(values: List[int]) -> int:
    return nlargest(2, set(values))

或者对于重复项很少的数据集,如下所示:

def second_largest(values: List[int]) -> int:
    return nlargest(2, values)

根据基准测试代码,第二种实现是迄今为止最快的替代方案,运行速度比 f8 快 66%。

评论

0赞 Dorian Turba 11/6/2023
“将函数拆分出来后,代码更具可测试性。”Pff,你可以使用 subprocess.run 在没有函数的情况下测试它,它就像一个魅力;)