AIO Castle Cavalry-我的代码太慢了,有什么办法可以缩短它?

时间:2019-08-18 04:48:23

标签: python

因此,我目前正在为比赛做准备(澳大利亚信息学奥林匹克竞赛),在training hub中,AIO 2018中间件存在一个问题,称为Castle Cavalry。我完成了:

input = open("cavalryin.txt").read()
output = open("cavalryout.txt", "w")
squad = input.split()
total = squad[0]
squad.remove(squad[0])
squad_sizes = squad.copy()
squad_sizes = list(set(squad))
yn = []

for i in range(len(squad_sizes)):
    n = squad.count(squad_sizes[i])

    if int(squad_sizes[i]) == 1 and int(n) == int(total):
        yn.append(1)
    elif int(n) == int(squad_sizes[i]):
        yn.append(1)
    elif int(n) != int(squad_sizes[i]):
        yn.append(2)

ynn = list(set(yn))

if len(ynn) == 1 and int(ynn[0]) == 1:
    output.write("YES")
else:
    output.write("NO")

output.close()

我提交了此代码,但没有通过,因为它太慢了,只有1.952秒。时间限制为1.000秒。我不确定如何缩短它,对我来说看起来还不错。请记住,我仍在学习,而我只是一个业余爱好者。我从今年才开始编写代码,因此,如果答案很明显,很抱歉浪费您的时间?。

谢谢您的帮助!

3 个答案:

答案 0 :(得分:1)

一个性能问题是在同一实体上或已经int()上反复调用int

if int(squad_sizes[i]) == 1 and int(n) == int(total):
elif int(n) == int(squad_sizes[i]):
elif int(n) != int(squad_sizes[i]):
if len(ynn) == 1 and int(ynn[0]) == 1:

但是真正的问题是您的代码无法正常工作。并且使其更快不会改变这一点。考虑输入:

4
2
2
2
2

您的代码将输出"NO"(缺少换行符),尽管它是有效的配置。这是由于您在代码的早期使用set()折叠了小队的大小。您已经舍弃了重要信息,而实际上只是测试数据的一个子集。为了进行比较,这是我认为可以正确处理输入的完整重写:

with open("cavalryin.txt") as input_file:
    string = input_file.read()

total, *squad_sizes = map(int, string.split())

success = True

while squad_sizes:
    squad_size = squad_sizes.pop()

    for _ in range(1, squad_size):
        try:
            squad_sizes.remove(squad_size)  # eliminate n - 1 others like me
        except ValueError:
            success = False
            break

    else:  # no break
        continue

    break

with open("cavalryout.txt", "w") as output_file:

    print("YES" if success else "NO", file=output_file)

请注意,我很早就将所有输入都转换为int,因此不必再次考虑该问题。我不知道这是否会满足AIO的时间限制。

答案 1 :(得分:0)

我可以看到其中一些可能效率不高的东西,但是优化代码的最佳方法是对其进行概要分析:使用概要分析器和示例数据运行它。

您可以轻松地浪费时间尝试加速不需要的零件,而不会产生太大的影响。阅读标准库中的cProfile模块,以了解如何执行此操作并解释输出。概要分析教程可能太长了,无法在此处复制。

我的建议,没有描述,

squad.remove(squad[0])

删除大列表的开头很慢,因为列表的其余部分必须在向下移动时复制。 (删除列表的末尾速度更快,因为列表通常由始终过度分配(比元素更多的插槽)的数组作为后盾,以使.append()更快,因此只需减少长度即可保持相同数组。

最好将其设置为虚拟值,然后在将其转换为集合时将其删除(集合由哈希表支持,因此删除速度很快),例如

dummy = object()
squad[0] = dummy  # len() didn't change. No shifting required.
...
squad_sizes = set(squad)
squad_sizes.remove(dummy)  # Fast lookup by hash code.

由于我们知道这些都是字符串,因此您可以仅使用None而不是虚拟对象,但是即使您的列表中可能包含None,上述技术也可以使用。

squad_sizes = squad.copy()

此行不是必需的;它只是在做额外的工作。 set()已经进行了浅拷贝。

n = squad.count(squad_sizes[i])

这行可能是真正的瓶颈。它实际上是一个循环中的一个循环,因此它基本上必须扫描整个列表以了解每个外部循环。考虑将collections.Counter用于此任务。您可以在循环外生成一次计数表,然后只需查找每个字符串的数字即可。

如果这样做,也可以完全避免生成集合。只需使用Counter对象的键进行设置即可。


与性能无关的另一点。在不需要索引时使用[i]这样的索引是不切实际的。 for循环可以从迭代器中获取元素,然后一步将其分配给变量:

from collections import Counter
...
count_table = Counter(squad)

for squad_size, n in count_table.items():
    ...

答案 2 :(得分:0)

您可以收集字典中每个骑士的所有出现的首选编号。 然后测试具有给定首选号码的骑士人数是否可以被该人数整除。

with open('cavalryin.txt', 'r') as f:
    lines = f.readlines()

# convert to int
list_int = [int(a) for a in lines]

#initialise counting dictionary: key: preferred number, item: empty list to collect all knights with preferred number. 
collect_dict = {a:[] for a in range(1,1+max(list_int[1:]))}

print(collect_dict)

# loop though list, ignoring first entry. 
for a in list_int[1:]:
    collect_dict[a].append(a)

# initialise output
out='YES'

for key, item in collect_dict.items():

    # check number of items with preference for number is divisilbe 
    # by that number
    if item: # if list has entries:
        if (len(item) % key) > 0:
            out='NO'
            break

with open('cavalryout.txt', 'w') as f:
    f.write(out)