如何以递归方式递增二进制数(以列表形式)而不转换为整数?

时间:2018-03-06 21:06:45

标签: python list recursion binary increment

例如,给定列表[1, 0, 1],代码将返回[1,1,0]。其他例子:

[1,1,1] -- > [1,0,0,0]
[1,0,0,1] --> [1,0,1,0]

我很难理解递归的基本情况,以及如何实现(n-1)情况。

def increment_helper(number):
    newNum = []
    if len(number) ==1:
        if number[0] == 1:
            carry = 1
            newNum.append(0)

        else:
            carry = 0
            newNum.append(1)
    else:
        return increment_helper(number-1)
    return newNum

所以我确定这里有很多错误,特别是我如何调用我的递归,因为我不知道如何在存储以某种方式删除的数字时在列表中递归。 else return语句显然是不正确的,但我将其用作占位符。我不确定使用什么条件作为增量的基本情况。我想我应该使用一个随身携带变量来跟踪我是否携带了一个,但除此之外,我仍然坚持如何继续。

10 个答案:

答案 0 :(得分:0)

这个"棘手"因为你在每一步都要做两件事:

  1. 我目前看的电话号码是多少? (二进制问题0/1)
  2. 应该递增吗? (我们有携带?)(二进制问题是/否)
  3. 这导致了4个案例。

    还有额外的"案例"我们甚至得到了一份清单,但它并不那么有趣。

    所以我将其描述如下:

    if not carry:
        # technically meaningless so just return the entire list immediately
        return list
    
    # we have "carry", things will change
    if not list:
        # assumes [] == [0]
        return [1]
    
    if list[0]:
        # step on `1` (make it 0 and carry)
        return [0] + increment(rest_of_list)
    
    # step on `0` (make it 1 and no more carry)
    return [1] + rest_of_list
    

    我强烈建议将列表更改为元组并使用它们。

    另请注意,递归位于反转列表中,因此请按以下方式应用:

    def increment_helper(lst, carry):
        if not carry:
            return lst
    
        if not lst:
            return [1]
    
        if lst[0]:
            return [0] + increment_helper(lst[1:], True)
    
        return [1] + lst[1:]
    
    
    def increment(lst):
        # long but really just reverse input and output
        return increment_helper(lst[::-1], True)[::-1]
    

    我通过立即返回列表(短路)使用了一些快捷方式,但是你可以使它更多"纯粹"通过进行递归甚至没有携带。

答案 1 :(得分:0)

你可以通过从尾到头遍历数字来递归地处理(二进制)数字(就像加法一样)。

在执行递归之前,您必须检查两个特殊情况:

  • 不执行当前数字的增量。然后只返回未修改的数字。
  • 剩下一个数字(你在数字的前面)。然后你可能需要将溢出附加到头部。

对于其余情况,您可以递增当前数字并以递归方式处理当前数字之前的所有数字。

def bin_incr(n, incr=True):
    if not incr:
        return n
    if len(n) == 1:
        return [1, 0] if n[0] == 1 else [1]
    return (
        # `n[-1] == 1` denotes an overflow to the next digit.
        bin_incr(n[:-1], n[-1] == 1)
        + [(n[-1] + 1) % 2]
    )

答案 2 :(得分:0)

啊,哈!好的,你已经了解了自己在做什么。基本大纲是

基本情况:我怎么知道我什么时候完成?

当你用完数字时你已经完成了。 number应该是个别数字的列表;检查其长度以确定何时重复。

递归案例:下一步是什么?

递归的一般概念是"做一些简单的事情,少量减少问题,并用较小的问题重复出现。"你在这部分的工作是做一位数的加法。如果你需要坚持下去(这个数字是否有一个进位?),那么就重复一遍。否则,您将获得完成所需的所有信息。

具体应用

您的递归步骤将涉及调用increment_helper少一位数:不是number - 1,而是number[:-1]

从每次递归返回后,您然后想要追加刚刚完成的数字。例如,如果您正在递增1101,则您的第一个电话会看到右侧的电话会增加一个进位。新数字为0,您必须重复。暂时抓住0,用110给自己打电话,然后获得该通话的结果。将保存的0附加到该页面,然后返回主程序。

这会让你感动吗?

答案 3 :(得分:0)

最好使用两个函数:一个用于检查最后一个位置的简单增量是否足够,另一个用于在前一次尝试失败时执行递归:

vals = [[1, 0, 1], [1,1,1], [1,0,0,1]]
def update_full(d):
   if all(i in [1, 0] for i in d):
      return d
   start = [i for i, a in enumerate(d) if a > 1]
   return update_full([1]+[0 if i > 1 else i for i in d] if not start[0] else [a+1 if i == start[0] -1 else 0 if i == start[0] else a for i, a in enumerate(d)])

def increment(d):
    if not d[-1]:
       return d[:-1]+[1]
    return update_full(d[:-1]+[2])

print(list(map(increment, vals)))

输出:

[[1, 1, 0], [1, 0, 0, 0], [1, 0, 1, 0]]

答案 4 :(得分:0)

试试这个:

def add1(listNum):

    if listNum.count(0):
        oneArr = [[0] * (len(listNum) - 1)] + [1]
        sumArr = []
        for i in range(len(listNum)):
            sumArr.append(sum(listNum[i], oneArr[i]))
        newArr = []
        for j in range(len(sumArr) - 1):
            if sumArr[len(sumArr) - 1 - j] < 2:
                newArr.insert(0, sumArr[len(sumArr) - 1 - j])
            else:
                newArr.insert(0, 1)
                sumArr[len(sumArr) - 1 - j] += 1
        return sumArr

    else:
        return [1] + [[0] * len(listNum)]

对于像这样简单的程序使用递归没有多少理由,这就是我选择提供非递归解决方案的原因。

如果你感兴趣的话,我已经计算了这个函数的时间复杂度,它是O(n)。

答案 5 :(得分:0)

另一种递归方法。

  • 当前输入是否为空列表?

    • 如果是,则返回[1]
    • 如果不是,请继续
  • 列表中最后一个元素的总和(value)和1是否大于1?

    • 如果是这样递归调用列表中的函数而没有最后一个元素(number_list[:-1])并将[0]附加到结果中。
    • 如果不是,请将列表的最后一个元素设置为总和。
  • 返回number_list

<强>代码

def increment_helper(number_list):
    if not number_list:
        return [1]

    value = number_list[-1] + 1

    if value > 1:
        number_list = increment_helper(number_list[:-1]) + [0]
    else:
        number_list[-1] = value

    return number_list

示例输出

numbers = [[1, 0, 1], [1,1,1], [1,0,0,1]]
for n in numbers:
    print("%r ---> %r" % (n, increment_helper(n)))
#[1, 0, 1] ---> [1, 1, 0]
#[1, 1, 1] ---> [1, 0, 0, 0]
#[1, 0, 0, 1] ---> [1, 0, 1, 0]

答案 6 :(得分:0)

我知道你不想使用十进制加法,但是如果你必须使用big endian bit order,那么首先转换回十进制可能是最实用的。否则,您最终会遇到不必要的reverse调用或尴尬的负数组索引

def binary (dec): # big endian bit order
  if dec < 2:
    return [ dec ]
  else:
    return binary (dec >> 1) + [ dec & 1 ]

def decimal (bin, acc = 0):
  if not bin:
    return acc
  else:
    return decimal (bin[1:], (acc << 1) + bin[0])

def increment (bin):
  # sneaky cheat
  return binary (decimal (bin) + 1)

for x in range (10):
  print (x, binary (x), increment (binary (x)))

# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]

但是,如果您可以用 little endian位顺序表示二进制数,则情况会发生变化。而不是转换回十进制,increment可以直接定义为一个漂亮的递归函数

def binary (dec): # little endian bit order
  if dec < 2:
    return [ dec ]
  else:
    return [ dec & 1 ] + binary (dec >> 1) 

def increment (bin):
  if not bin:
    return [1]
  elif bin[0] == 0:
    return [1] + bin[1:]
  else:
    return [0] + increment(bin[1:])

for x in range (10):
  print (x, binary (x), increment (binary (x)))

# 0 [0] [1]
# 1 [1] [0, 1]
# 2 [0, 1] [1, 1]
# 3 [1, 1] [0, 0, 1]
# 4 [0, 0, 1] [1, 0, 1]
# 5 [1, 0, 1] [0, 1, 1]
# 6 [0, 1, 1] [1, 1, 1]
# 7 [1, 1, 1] [0, 0, 0, 1]
# 8 [0, 0, 0, 1] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [0, 1, 0, 1]

除此之外:将小端表示转换回十进制有点不同。我提供这个来表明递归的用例存在于任何地方

def decimal (bin, power = 0):
  if not bin:
    return 0
  else:
    return (bin[0] << power) + decimal (bin[1:], power + 1)

答案的这一部分为你提供了蛋糕,并允许你吃它。你得到大端序列顺序和一个递归increment,它按照从左到右的顺序逐步执行 - 你应该使用上面的任何一个实现有很多原因,但是这个目的是为了告诉你,即使你的问题很复杂,它仍然可以递归地思考它。在制作此功能时,没有reversearr[::-1]被滥用。

def binary (dec): # big endian bit order
  if dec < 2:
    return [ dec ]
  else:
    return binary (dec >> 1) + [ dec & 1 ]

def increment (bin, cont = lambda b, carry: [1] + b if carry else b):
  if bin == [0]:
    return cont ([1], 0)
  elif bin == [1]:
    return cont ([0], 1)
  else:
    n, *rest = bin
    return increment (rest, lambda b, carry:
                              cont ([n ^ carry] + b, n & carry))

for x in range (10):
  print (x, binary (x), increment (binary (x)))

# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]

我们首先将问题分解为更小的部分; n是第一个问题,rest是其他问题。但是,继续思考的关键(如上面的cont)就是要大胆思考。

在此特定问题中,n会根据rest是否更新而更新。所以我们立即重复rest并传递一个将继续接收子问题结果的延续。我们的延续会收到子问题b的答案,以及该子问题是否会产生carry

...
  else:
    n, *rest = bin
    return increment (rest, lambda b, carry:
                              cont ([n ^ carry] + b, n & carry))

n ^ carryn & carry表达式决定了这个子问题的答案是什么以及下一个进位将是什么。以下真值表显示^&分别对answernext_carry进行编码。例如,如果n为0且carry为1,则可以使用进位。 answer将是[1] +子问题的答案,下一个进位将为0。

n   carry   (answer, next_carry)   n ^ carry   n & carry
0   0       ([0] + b, 0)           0           0
0   1       ([1] + b, 0)           1           0
1   0       ([1] + b, 0)           1           0
1   1       ([0] + b, 1)           0           1

基本案例很简单。如果子问题为[0],则答案为[1],且不会携带0。如果子问题为[1],那么答案为[0],并带有1

...
  if bin == [0]:
    return cont ([1], 0)
  elif bin == [1]:
    return cont ([0], 1)

最后,设计默认延续 - 如果问题b的答案导致进位,只需将[1]添加到答案中,否则只返回答案。

cont = lambda b, carry: [1] + b if carry else b

答案 7 :(得分:0)

您要求生成序列序列的增量/后继/下一个函数。由于其他人已经给出了代码,我将给出开发这些函数的一般方法。

首先,开发一个多次递归(2次或更多次递归调用),用于计算长度为N的所有序列。对于大端序列的二进制序列(bs),从N 0到N 1,基本情况bs(0)表达式是[[]],该序列包含一个没有二进制数字的序列。 bs(n)在bs(n-1)方面的双递归是([0]连接到bs(n-1)的所有成员(按顺序))加([1]接受bs的所有成员( N-1))。

接下来,重点关注相邻递归调用返回的子序列之间的转换。这里只有一个:0,1,...,1到1,0,...,0。要跨越这个边界,我们需要将0后跟0或更多1s替换为1后跟相同的数字0。正如其他人所示,我们通过从右侧扫描前0来找到这样的中断。

事实证明,每个增量越过相邻bs(k)之间的边界调用k的某个值,也就是说,在双递归产生的调用树的某个级别。

到目前为止,据我所知,同样的想法适用于设计格雷码序列的增量函数,组合序列(一次取k个事物)和排列序列。

注1:1-> 0转换可以一次完成1个或一次完成。

注2:二进制位测试和翻转是用于计数+1的图灵机算法。当我记得时,Stephen Wolfram,一种新的科学,在TM章节中提出了3种不同的实现。

注3(在编辑中添加):如果一个从先加0开始先切换到前加1,或从前加到后加,或两者兼而有之,则得到3个其他序列序列,其他3个递增函数。

答案 8 :(得分:0)

在这种情况下,您不需要递归。让我们从最简单的实现开始:

  1. 实现将在位上运行的完整加法器。
  2. 使用完整的加法器实现纹波加法器,该加法器将在2个位列表上运行。

完整的加法器实现是直接的。

def full_adder(a,b,cin):
    sum_ = a^(b^cin)
    cout = (a&b) | (a|b)&cin
    return sum_, cout

进行测试以确保完整的加法器符合规范:

>>> zero_one = (0,1)
>>> [full_adder(*x) for x in [(a,b,c) for a in zero_one for b in zero_one for c in zero_one]]
[(0, 0), (1, 0), (1, 0), (0, 1), (1, 0), (0, 1), (0, 1), (1, 1)]

由于波纹加法器的参数是列表,因此我们需要确保列表长度在加法之前匹配。这是通过将较短的列表填充前导零来完成的。

def ripple_adder(xs,ys):
    x, y = map(len, (xs, ys))
    alen = max(x, y)
    ax, by = map(lambda f: f if len(f) == alen else [0]*(alen-len(f)) + f, (xs, ys))
    cout = 0
    res = [0]*(alen)
    for i in range(alen-1, -1, -1):
        a, b, cin = ax[i], by[i], cout
        s, cout = full_adder(a, b, cin)
        res[i] = s
    if cout:
        res = [1] + res
    return res

最后,我们根据波纹加法器定义bin_inc二进制增量函数

def bin_inc(bin_lst):
    return ripple_adder(bin_lst, [1])

>>> bin_inc([1,1,1])
[1, 0, 0, 0]
>>> bin_inc([1,0,0,1])
[1, 0, 1, 0]

现在需要一个更简单但需要一点洞察力的解决方案。考虑以下 输入xs的长度L和输出ys的观测值

  1. 如果xs全部为1,则ys的长度为L+1ys的第一个元素为1,其余的为为零。

  2. 如果xs的元素为零,则使pxs中最后一个零元素的索引。 然后,ys将是由xs的前p个元素,后跟一个1和后跟长度为L - p的零组成的列表。那是 ys = xs[:p] + [1] + [0]*(L-p)

我们可以轻松地将其翻译为python代码。我们使用python的list.index方法通过处理列表的反面来找到最后出现的零,并适当地调整算法:

def bin_inc_2(xs):
    if all(xs):
        return [1] + [0]*len(xs)
    p = xs[::-1].index(0)
    return xs[:-p-1] + [1] + [0]*p

这比基于ripple_adder的实现有效且简单。您可能会注意到的一个小缺点是,当xs的元素为零时,我们遍历该元素以检查其是否全部为1,然后再次遍历以找到第一个出现的零。

通过使用try except块,我们可以简化实现并同时使其更具有Python风格:

def bin_inc_3(bin_lst):
    try:
        p = bin_lst[::-1].index(0)
        return bin_lst[:-p-1] + [1] + [0]*p
    except ValueError: 
        return [1] + [0]*len(bin_lst)

就源文本和惯用python而言,此实现很简单。现在,我们针对基于涟漪_adder的加法器对其进行测试,以确保其正常工作。

>>> z_n = (0,1)
>>> xs = [[a,b,c,d,e,f,g,h] for a in z_n for b in z_n for c in z_n for d in z_n
                            for e in z_n for f in z_n for g in z_n for h in z_n ]

>>> print(all(ripple_adder(x, [1]) == bin_inc_3(x) for x in xs))
True

很棒,它可以按预期工作,并且可以正确地处理测试所证明的前导零(每个数字从0255递增)。

答案 9 :(得分:0)

有进位时只需递归:

def f(n):
  # Base cases
  if not n:
    return [1]
  if n == [1]:
    return [1, 0]
  if n[-1] == 0:
    return n[:-1] + [1]
  if n[-2:] == [0, 1]:
    return n[:-2] + [1, 0]

  # Recurse
  return f(n[:-2]) + [0, 0]

numbers = [[1, 0, 1], [1,1,1], [1,0,0,1], [1, 0, 1, 1]]
for n in numbers:
  print n, f(n)