0

I'm trying to find all prime divisors of a number. I'm trying to do it without passing an empty list as a parameter.

I'm able to call function podz without empty list parameter, but the result is incorrect - that's the 1st question - why? I get the correct result in podzb function, but it is necessary to provide the empty list parameter.

The code:

def podz(x, lista = ()):
    # xa = []
    lis = list(lista)
    # lis = list(lista) # czemu to psuje wynik? jak odpalić krok po kroku?
    # print(lis)
    for i in range(2, x + 1):
        # print(f'x: {x}')
        # print(f'i: {i}')
        # print(f'lis: {lis}')
        if x % i == 0:
            lis.append(i)
            # print(f'lis2: {lis}')
            podz(int(x / i), lis)
            break
    return(lis)

def podzb(x, lista = ()):
    # xa = []
    lis = lista
    # lis = list(lista) # czemu to psuje wynik? jak odpalić krok po kroku?
    # print(lis)
    for i in range(2, x + 1):
        # print(f'x: {x}')
        # print(f'i: {i}')
        # print(f'lis: {lis}')
        if x % i == 0:
            lis.append(i)
            # print(f'lis2: {lis}')
            podzb(int(x / i), lis)
            break
    return(lis)


a = podz(50, [])
print(f'a: {a}')
aa = podz(50)
print(f'aa: {aa}')

a2 = podzb(50, [])
print(f'a2: {a2}')
# aa = podzb(50)        #this causes: AttributeError: 'tuple' object has no attribute 'append'
# print(f'aa: {aa}')

5 Answers5

1

The cause of the AttributeError is that the default argument is an empty tuple (), not an empty list []. Now, you're right not to use an empty list as a default argument for various reasons, but since you want to mutate this object, you do need a list.

The solution is to do something like

def podzb(x, lista=None):
    if lista is None:
        lista = []
    ... # rest of function
FHTMitchell
  • 11,793
  • 2
  • 35
  • 47
  • This seems to work well. It doesn't require empty list parameter in a call and doesn't require extending the list with what comes from recursive calls. The final working code is (though I can't make it multiline): `def podznone(x, lista = None): if lista is None: lista = [] for i in range(2, x + 1): if x % i == 0: lista.append(i) podz(int(x / i), lista) break return(lista)` – Jakub Zbudniewek Jul 10 '18 at 13:38
  • @JakubZbudniewek What's the point of returning from the recursive function when you discard the result anyway? Why would you consider it advantageous *not* to reuse the recursive call result? That's what recursion is all about. Otherwise it's an iterative solution, unnecessarily wrapped in a recursive structure. Using recursion you don't even need *any* other argument than `x` as you can see [here](https://stackoverflow.com/a/51264959/3767239). It's just `def podz(x): ...`. Using `lista=None` may solve your problem but it's not clean. – a_guest Jul 10 '18 at 15:20
1

when you do :

lis = list(lista)

you copy your list. So you don't modify it.

madjaoue
  • 5,104
  • 2
  • 19
  • 31
1

In podz, here:

lis = list(lista)

you create a new list from the content of lista. Then you pass this new list when making the recursive call here:

   if x % i == 0:
        lis.append(i)
        # print(f'lis2: {lis}')
        podz(int(x / i), lis)

So in the recursive call you create a new list once again and append to it, and then discard this new list when the recursive call returns, and since the recursive call worked on a copy of the first list, the first list is not updated either.

To make this work, you'd have to add the result of the recursive call to the first list:

   if x % i == 0:
        lis.append(i)
        # print(f'lis2: {lis}')
        lis.extend(podz(int(x / i), lis))

The second version (podzb) doesn't make a copy of the list so it's the same list being updated by all recursive calls.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • somehow adding lis.extend() doesn't return the correct result for 50: [2,5,5]. Instead it gives [2, 2, 5, 2, 5, 5, 2, 5, 5], so it looks like some elements are being added twice. – Jakub Zbudniewek Jul 10 '18 at 13:43
  • @JakubZbudniewek possibly yes, I didn't bother checking the whole implementation, I just focused on your question. – bruno desthuilliers Jul 10 '18 at 13:53
0

This is because you never update the list of divisors, you just pass it to the subsequent function call where a new list is created (and then returned) but you never capture the return value. Actually you don't need a tuple / list argument at all, you can simply compute and return the divisors within the function, such as:

def podz(x):
    div = []
    for i in range(2, x + 1):
        if x % i == 0:
            div.append(i)
            div.extend(podz(int(x / i)))
            break
    return div
a_guest
  • 34,165
  • 12
  • 64
  • 118
  • Seems reasonable. Unfortunately print(podz01(50)) outputs [2,5] instead of [2,5,5] – Jakub Zbudniewek Jul 10 '18 at 13:24
  • @JakubZbudniewek I don't know what the function `podz01` is supposed to be but the above defined function `podz` will output `[2, 5, 5]` which you can easily verify yourself. – a_guest Jul 10 '18 at 15:12
0

Your code is bit messy. Here is cleaner code for you:

def divisors(num):
    divisors = []
    for divisor in range(2, num + 1):
        if num % divisor == 0 and isPrime(divisor):
            divisors.append(divisor)
    return divisors

def isPrime(num):
    for divisor in range(2, num):
        if num % divisor == 0: return False
    return True

a = divisors(50)
print(f'a: {a}')
Dusan Gligoric
  • 582
  • 3
  • 21
  • This one won't make it, because in case of divisors(4) I want to get output of [2,2], not [2,4] nor [2] – Jakub Zbudniewek Jul 10 '18 at 13:21
  • For what you wrote "I'm trying to find all prime divisors of a number.", output for divisors(4) should be only [2], [2,2] brings more questions, like why do you need those doubled? @JakubZbudniewek – Dusan Gligoric Jul 10 '18 at 13:26
  • This is for calculating least common multiple (LCM), so it does matter how many time is a number divisible by a prime number. Doubles are welcome – Jakub Zbudniewek Jul 10 '18 at 13:51