来自:Coursera上的计算原理第1部分课程

我在OWLTEST上获得了-2分,其中使用了Pylint作为样式指南。错误状态:


分支太多(17/12)
函数“合并”,第7行,


是什么意思是什么意思?

我非常努力地使该程序起作用,并从头开始编写了它。我还想知道是否有一些技术可以使清洁器更清洁和/或利用最佳实践。我知道可能有一些更好的方式来编写此代码,因为现在我的代码看起来真的很乱。 >
# -*- coding: utf-8 -*-
"""
Created on Thu Sep  3 17:55:56 2015
2048_merge_attempt1.py
@author: Rafeh
"""
def merge(nums):
    '''
    Takes a list as input 
    returns merged pairs with
    non zero values shifted to the left.
    [2, 0, 2, 4] should return [4, 4, 0, 0]
    [0, 0, 2, 2] should return [4, 0, 0, 0]
    [2, 2, 0, 0] should return [4, 0, 0, 0]
    [2, 2, 2, 2, 2] should return [4, 4, 2, 0, 0]
    [8, 16, 16, 8] should return [8, 32, 8, 0]
    '''
    slide = []  # Append non-zeroes first
    for num in nums:
        if num != 0:
            slide.append(num)
    for num in nums:
        if num == 0:
            slide.append(num)
    pairs = []
    for idx, num in enumerate(slide):
        if idx == len(slide)-1:
            pairs.append(num)
            if len(pairs) != len(nums):
                pairs.append(0)
            break
        if num == slide[idx+1]:
            if num != 0:
                pairs.append(num*2)
                slide[idx+1] -= slide[idx+1]
                # slide[idx+1], slide[idx+2] = slide[idx+2], slide[idx+1]
            else:
                pairs.append(num)
        else:
                pairs.append(num)  # Even if they don't match you must append
    slide = []  # Append non-zeroes first
    for num in pairs:
        if num != 0:
            slide.append(num)
    for num in nums:
        if num == 0:
            slide.append(num)
    for _ in range(len(nums) - len(slide)):
        if len(nums) != len(slide):
            slide.append(0)
    return slide


评论

缩进有点奇怪,但是我不确定它是仅来自复制/粘贴还是属于您要查看的内容,因此我不想编辑它。特别是:编码行以两个空格缩进,而代码的其余部分以另外两个空格缩进,两者都是多余的。同样在第40行中,pairs.append(num)的缩进太深了一个级别(四个空格),这可能会产生误导,尤其是在Python中。
@ mkrieger1是的,第40行缩进不正确。感谢您抓住我现在在代码中修复的问题。很奇怪,没有样式指南分析能抓住它。我经历了5个不同的过程。完成代码后,是否可以自动格式化或重组代码,使其符合最佳样式指南标准?至于缩进,它不是我的实际代码中那样。不知道为什么在这里如此缩进。

编辑帖子时,缩进四个空格的所有内容均视为代码块。因此,在编写问题时,第一行缩进了六个空格,其余行缩进了八个空格。插入代码块的一种安全方法是粘贴代码块而没有任何额外的缩进,然后将其突出显示并按Ctrl-K或按{}按钮,这会将所有内容统一缩进四个空格。
那就是我尝试过的...没有用?我拿走了整个代码,并粘贴了0个缩进,然后按Ctrl-K,它不起作用。所以我最终放弃了。 :(

看看其中一些代码。

#1 楼

在Python中对成对的项目进行迭代的几种典型模式为:

for prev, next in zip(nums[:-1], nums[1:]):
   ...




prev = None
for next in nums:
    if prev is None:
        prev = next
        continue
    ...


以您的情况,我认为第二种方法更适合:

def merge(nums):
    prev = None
    store = []

    for next_ in nums:
        if not next_:
            continue
        if prev is None:
            prev = next_
        elif prev == next_:
            store.append(prev + next_)
            prev = None
        else:
            store.append(prev)
            prev = next_
    if prev is not None:
        store.append(prev)
    store.extend([0] * (len(nums) - len(store)))
    return store


评论


\ $ \ begingroup \ $
干净的解决方案,我喜欢它。
\ $ \ endgroup \ $
– Rubik
2015年9月5日在5:52



\ $ \ begingroup \ $
continue语句有什么作用?例如,如果next_ = 5,则代码将跳至下一个if语句。如果next_ = 0,代码将继续,然后简单地跳转到下一个if语句?更新:哦!在循环的下一个迭代中继续运行!哇,学到了新东西。将此评论留在这里希望对其他人有所帮助。
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日在8:29



\ $ \ begingroup \ $
我已经读过这篇文章,并且我了解vs ==的区别在于前者是对象身份检查器,其中==检查它们是否等效。但是,我对此并不完全理解。在我的终端中,None is not None返回与None!= None相同的结果。请解释。请注意,我知道当a = [1,2]和b = [1,2]时,语句a为b将返回False,而==校验将返回True。但是,在这种情况下(无不为无),我看不到区别
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日在8:52

\ $ \ begingroup \ $
有很多好的答案。很难在@SuperBiasedMan答案和此答案之间做出选择。我决定采用此答案,因为它非常干净且具有Python风格。我了解了迭代值对的方法,这是一种特别有用的技术,以后我将可以重新实现它。我还了解了列表上的extend方法和continue函数。这可以帮助我显着清理代码并使代码超级易于阅读。就列表理解而言,另一个答案非常有用。但是,我必须选择一个,而这个简单地携带了更多有用的概念。
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日,9:11

\ $ \ begingroup \ $
实际上,我实际上会从itertools文档中建议pairwise()来进行对的迭代。可以从more-itertools包中导入实现。
\ $ \ endgroup \ $
– David Z
2015年9月5日,12:59

#2 楼

使用自动测试

在文档字符串中声明:

    [2, 0, 2, 4] should return [4, 4, 0, 0]
    [0, 0, 2, 2] should return [4, 0, 0, 0]
    [2, 2, 0, 0] should return [4, 0, 0, 0]
    [2, 2, 2, 2, 2] should return [4, 4, 2, 0, 0]
    [8, 16, 16, 8] should return [8, 32, 8, 0]


但是我要么


手动测试所有输入(非常无聊)
(不要个人,但代码准确性不应基于个人主观信任)

相反,您可以这样写:

>>> merge([2, 0, 2, 4])
[4, 4, 0, 0]
>>> merge([0, 0, 2, 2])
[4, 0, 0, 0]
>>> merge([2, 2, 0, 0])
[4, 0, 0, 0]
>>> merge([2, 2, 2, 2, 2])
[4, 4, 2, 0, 0]
>>> merge([8, 16, 16, 8])
[8, 32, 8, 0]


这样,每次模块执行时,doctest将自动运行所有测试。这项技术将为您节省以后繁琐的手动测试。

评论


\ $ \ begingroup \ $
我是新手,能否请您进一步解释如何使用doctest?我读了这篇文章:docs.python.org/2/library/doctest.html,我仍然在程序底部创建了一个if名称main函数。我导入了doctest,将您在文档字符串中提到的内容包括在内,并执行了代码。但是,即使如此,它仍然无法正常工作。
\ $ \ endgroup \ $
–聪明的程序员
2015年9月4日在11:24

\ $ \ begingroup \ $
首先导入doctest。 doctest.testmod()在您的函数之后。无输出表示一切正确。
\ $ \ endgroup \ $
– Caridorc
2015年9月4日在11:31

\ $ \ begingroup \ $
最好从命令行运行python -m doctest 的名称,这样,您的模块就不会与doctest紧密耦合,也可以在没有它的情况下使用。
\ $ \ endgroup \ $
–mkrieger1
2015年9月4日在11:38



\ $ \ begingroup \ $
是的,但是随后您必须先运行doctests,然后再对其进行编辑,才能使用该模块。
\ $ \ endgroup \ $
–mkrieger1
15年9月4日在11:45

\ $ \ begingroup \ $
@ mkrieger1为什么运行doctest会打扰您?
\ $ \ endgroup \ $
– Caridorc
2015年9月4日14:03在

#3 楼

列表理解是您的朋友。它们实际上将for循环折叠为一行,并将其直接分配到列表中,从而节省了您无用的时间。语法类似于列表括号[statement for var in iterable]中包含的反向for循环。您首先可以这样制作slide:稍后再次构建幻灯片时,您当然可以模仿这两行。

为什么使用此行:

    slide = [num for num in nums if num != 0]
    slide += [num for num in nums if num == 0]


基本上什么时候都可以成为:

slide[idx+1] -= slide[idx+1]


最后,您可以使用列表乘法来避免最终的if not num循环:

slide[idx+1] = 0


(如果True相同,则它们的差值为num无论如何,因此您无需检查长度差异)

评论


\ $ \ begingroup \ $
更好:slide.extend(如果num == 0,则num为num的num)
\ $ \ endgroup \ $
–user14393
2015年9月4日23:03

\ $ \ begingroup \ $
谢谢您的回答。我从中学到了很多!我在最佳答案的评论部分中说明了使用@Jaime答案的原因。这是一个艰难的决定!我最终将使用您的建议在Coursera上提交我的代码,因为您的建议与我当前的代码和思考过程完全吻合。另外,Hurkyl很棒的建议!
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日在9:20



\ $ \ begingroup \ $
注意:Pylint对我大喊,因为我使用i而不是idx。 @超人
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日,12:54

\ $ \ begingroup \ $
@Rafeh Glad我帮助了!而我的使用可能会受到一些质疑。我确实经常看到它,所以是否听Pylint取决于您。我想我应该注意idx并没有什么问题,很明显,我只是喜欢输入更少的内容,并且使用i进行这样的简单循环也从未遇到过麻烦。
\ $ \ endgroup \ $
–SuperBiasedMan
2015年9月11日9:37

\ $ \ begingroup \ $
我完全同意!仅仅是我为此丢掉了分数,仅此而已。否则我在for循环中使用“ i”我自己就不会有问题!
\ $ \ endgroup \ $
–聪明的程序员
2015年9月11日9:38在

#4 楼


是什么意思?


分支是指执行下一行代码以外的内容时。根据OWLTEST,您有17个分支机构,其中12个分支机构是可接受的最大值。

长话短说,它不喜欢您的风格。同一函数中有许多循环和许多if。拆分功能将大大提高可读性。

它可能抱怨的另一点是重复。彼此次超过nums。您是否考虑过一次迭代,将所有num != 0附加到第一张幻灯片上,将所有num == 0附加到第二张幻灯片上,然后再将这些幻灯片粘贴在一起? 。迭代很昂贵。

#5 楼

我必须承认,我对您的代码感到很困惑。它需要很多变量,周期和if语句。单看它,我很难理解它的作用。

为了使用Python进行一些练习,我尝试做同样的练习,然后我想到了这一点(结合了Caridorc的答案):


这是与原始代码相比的主要区别:


没有任何一行看起来与其他任何代码相似。通常,许多类似的行表示存在更好的方法来使代码更轻便,更易于阅读。
最小化使用额外的变量。每次声明新变量时,都必须了解您打算将其用于什么!通常,使用许多支持变量(尤其是复杂的支持变量)也会影响性能。
使用while循环而不是for循环。当您不知道先验范围时,while是一个完美的循环。它的语法比for循环更简单,如果保持简单,也很容易阅读。
避免缩进级别过多。您越深入,代码就越难理解。

我希望这有助于继续练习编写越来越好的代码!如评论中所建议:

def merge(nums):
  '''
  Takes a list as input 
  returns merged pairs with
  non zero values shifted to the left.
  test with: 
    python -m doctest 2048.py
  No output == OK!

  >>> merge([2, 0, 2, 4])
  [4, 4, 0, 0]
  >>> merge([0, 0, 2, 2])
  [4, 0, 0, 0]
  >>> merge([2, 2, 0, 0])
  [4, 0, 0, 0]
  >>> merge([2, 2, 2, 2, 2])
  [4, 4, 2, 0, 0]
  >>> merge([8, 16, 16, 8])
  [8, 32, 8, 0]
  '''
  # save the initial length of the list
  size = len(nums)
  # remove all the zeroes - looked up on stackoverflow
  nums[:] = (value for value in nums if value != 0)
  # now do the merging, use an index to run through the list
  i = 0
  while (i < len(nums)-1 ):
    if (nums[i]==nums[i+1]): # if a number is the same as the following
      nums[i] *= 2           # double it 
      del nums[i+1]          # remove the following
    i += 1
  # restore the initial list length appending zeroes
  while (len(nums) < size):
    nums.append(0)
  # done
  return nums


可以在一个额外的列表中将c语言循环进行更多的pythonic复制,但是我更喜欢这样。 >

评论


\ $ \ begingroup \ $
最后一个while(len(nums)<...可以写成nums.extend([0] *(size-len(nums))))更好,而while(i <...应该是为i在range(len(nums))中编写,最后留下不必要的括号,最后,您应该列出一个新列表,而不要使用nums [:]
\ $ \ endgroup \ $
–WorldSEnder
2015年9月5日,0:59



\ $ \ begingroup \ $
@WorldSEnder的第一点是。对于第二个否定:随着您的建议从列表中删除,索引超出范围。
\ $ \ endgroup \ $
– DarioP
2015年9月5日,下午1:05

\ $ \ begingroup \ $
在这里,您正在就地修改参数nums,同时还返回了它。总的来说,尤其是在这种情况下,我建议不要这样做。
\ $ \ endgroup \ $
– Sjoerd Job Postmus
2015年9月5日于7:44

\ $ \ begingroup \ $
感谢您非常有用的指针@Dario。我不明白分配给nums [:]与nums的原因。如果您能详细说明一下,那就太好了。我同意WorldSEnder使用.extend方法。那太整齐了。
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日于10:29

#6 楼

在最初的两个循环中,我们仅需要第一个循环。我们真的只想要非零值;直到最后,我们不需要处理零。

def merge(nums):
    slide = []  
    for num in nums:
        if num != 0:
            slide.append(num)


接下来,我们将摆脱主要的for循环。它将变成一个while循环;我们将自行处理。

    pairs = []
    idx = 0
    while idx < len(slide):
        num = slide[idx]
        if idx == len(slide)-1:
            pairs.append(num)
            break


我们也不需要在这里检查长度,稍后我们将处理尾部填充。我们还保留break,这是我们摆脱循环的方式。

现在进行主检查。我们不再麻烦检查零相等性,我们已经知道slide中没有零。

        if num == slide[idx+1]:
            pairs.append(num*2)
            idx += 1 


请注意额外的增量,因此我们跳过了下一个数字。我们具有与您相同的标准else子句。最后,我们自己增加计数器。

        else:
                pairs.append(num) 
        idx += 1


现在,这时,我们有了所需的正确值,我们只需要在右边用零填充即可。幸运的是,您已经可以做到这一点。

    for _ in range(len(nums) - len(pairs)):
        pairs.append(0) 
    return pairs


我刚刚将变量名称从slide更改回pairs。此时,以最初使用的方式重用变量名是一种不好的做法,应避免使用它。

此外,我们不需要循环中的长度检查。

所以,总的来说,代码看起来像是

def merge(nums):
    slide = [] 
    for num in nums:
        if num != 0:
            slide.append(num)

    pairs = []
    idx = 0

    while idx < len(slide):
        num = slide[idx]

        if idx == len(slide)-1:
            pairs.append(num)
            break

        if num == slide[idx+1]:
            pairs.append(num*2)
            idx += 1 

        else:
                pairs.append(num)  

        idx += 1

    for _ in range(len(nums) - len(pairs)):
        pairs.append(0) 
    return pairs


您会注意到,随着函数体的变小,它变得更容易阅读。同样,在三个变量之间重用两个名称会使代码难以遵循。另外,将功能的“段落”分开的一些空格使它更易于阅读。

您的风格还不错。缩进是一致的,您坚持使用公认的python缩写(例如idx,虽然简单地使用i也可以接受),并且使用了体面的变量名(除了我已经提到的问题)。

评论


\ $ \ begingroup \ $
感谢您的深刻见解。这个while循环肯定会帮助我保持代码更干净!是的,当我再次尝试遍历代码时,我也意识到我不需要0附加器,因为我可以简单地反向工程并在最后附加所有需要的0。对于像我这样的初学者/中级程序员,您的答案非常彻底,非常有用!
\ $ \ endgroup \ $
–聪明的程序员
2015年9月5日在11:13