Python 3, any better/cleaner way to write these functions that use for loops? - python-3.x

I'm trying to write code in the most simplest and cleanest way possible. I've found a few ways to shorten and simplify my code through functions that I've never seen before or through using other methods. I'd like to expand my knowledge on writing code using various (but simple) methods, and also expand my function 'vocabulary'.
Here are the functions:
1. Perfect number:
If a number's divisors' sum is equal to the number itself, it is a perfect number. We dont count the number itself as a divisor. E.g. 6's divisors are 1, 2, 3. The sum of the divisors is 6. Therefore 6 is a perfect number.
def perfect_number(num):
if type(num) != int or num < 0:
return None
divisors = []
total = 0
for x in range(num):
if num % (x+1) == 0:
if num != x+1:
divisors += [x+1]
for x in divisors:
total += x
if total == num:
return True
return False
2. Pattern:
A function that takes a positive integer and prints a pattern as follows:
pattern(1): '#-'
pattern(2): '#-#--'
pattern(5): '#-#--#---#----#-----'
def pattern(num):
if type(num) != int or num < 0:
return None
output = ''
for x in range(num):
output += '#'+('-'*(x+1))
return output
3. Reversed Numbers:
A function that takes 2 integers. It goes through every number in the range between those 2 numbers, if one of those numbers is a palindrome (the same thing backwards e.g. 151 is a 'palindrome'), it will increase a variable by 1. That variable is then returned.
invert_number(num) returns the opposite of num as an integer.
def reversed_numbers(low, high):
output = 0
for x in range(low,high+1):
if invert_number(x) == x:
output += 1
return output
It is assumed that low is lower than high.
If I broke a rule or if this doesnt fit here, please tell me where I can post it/how I can improve. Thanks :)

Related

Different results from for-loop and while-loop for prime factorization in Python 3

I have written a function that returns the prime factors of a given integer. For the cases I tested, it seems to work alright. Here is the original function.
def prime_factors_2(num: int) -> set:
factors = []
while num % 2 == 0:
factors.append(2)
num //= 2
i = 3
while i <= int(sqrt(num)) + 1:
if num % i == 0:
factors.append(i)
num //= i
else:
i += 2
if num != 1:
factors.append(num)
return set(factors)
# num = 867844
# Output = {601, 2, 19}
While messing around with the code, I tried to implement the same but with a for loop instead of a while loop (as I prefer to use a for loop when counting is involved). This is the code for the second function.
def prime_factors_1(num: int) -> set:
factors = []
while num % 2 == 0:
factors.append(2)
num //= 2
for i in range(3, int(sqrt(num)) + 1, 2):
if num % i == 0:
factors.append(i)
num //= i
print(num)
if num != 1:
factors.append(num)
return set(factors)
# num = 867844
# Output = {2, 19, 11419}
For some reason, it no longer factors the 11419 into 601 and 19. Are both the loops not equivalent? Or am I making some mistake while translating the while loop to a for loop? I know that there is no practical difference between the two loops in this case, but I want to know this out of pure curiosity.
Problem is that in your "while loop", you are incrementing the value of "i" by 2 only when the "if" condition is not satisfied, but in the case of "for loop", i is getting incremented by 2 in each iteration.
So if you'll do something like:
while i <= int(sqrt(num)) + 1:
if num % i == 0:
factors.append(i)
num //= i
i += 2
Then the prime_factors_2 function would also result in the same answer as the prime_factors_1 function
There is a slight difference between the two functions.
If you look closely at prime_factors_2, in the while loop, the counter doesn't get incremented when the if condition is satisfied.
On the other hand, in prime_factors_1, in the for loop, the counter gets incremented in every iteration regardless of the if condition.
There is no way to control the counter from within a for loop, so the while loop implementation is the only correct approach.

recursive solution for coin change problem

i need to write an algorithm that receives a number and a list of numbers and returns the number of possible combinations of numbers from the list that can create the sum number. for example: def coin(5,[1,2,5,6] should return the number 4 because there are 4 possible combinations from the list that can create together the number 5. below there is the code that i tried but it does not work. the output is 6 instead of 4 and i would love some help in understanding why.
def coin(n,lst):
if n<=1:
return 1
else:
total=0
for i in range(len(lst)):
change=lst[i]
if change>n:
continue
else:
result=coin(n-change,lst[i:])
if result>0:
total+=result
return total
print(coin(5,[1,2,5,6]))
The mistake is in the base case:
if n<=1:
return 1
This is only valid if 1 is one of the allowed coins. But in your recursive case, you slice the list at lst[i:], so not all coins will be allowed every time. Sometimes, 1 isn't one of the allowed coins, in which case there are zero ways to make a total of 1, not one way. To fix it, write something like:
if n <= 1:
if n == 0 or n in lst:
return 1
else:
return 0
This is correct because you can make change for n in the base case either if n is 0 (we can always make 0), or if n is 1 and 1 is one of the allowed coins.
That said, it would be simpler to let the recursive case handle 1, so the base case only needs to handle 0; this is also correct:
if n == 0:
return 1
int coinCount( int C[], int m, int n )
{
// if n is 0 return 1
if (n == 0)
return 1;
// If n is less than 0 then no solution exists
if (n < 0)
return 0;
// If there are no coins and n is greater than 0, then no solution exists
if (m <=0 && n > 0)
return 0;
return coinCount( C, m - 1, n ) + coinCount( C, m, n-C[m-1] );
}

find the first occurrence of a number greater than k in a sorted array

For the given sorted list,the program should return the index of the number in the list which is greater than the number which is given as input.
Now when i run code and check if it is working i am getting 2 outputs. One is the value and other output is None.
If say i gave a input of 3 for the below code.The expected output is index of 20 i.e., 1 instead i am getting 1 followed by None.
If i give any value that is greater than the one present in the list i am getting correct output i.e., "The entered number is greater than the numbers in the list"
num_to_find = int(input("Enter the number to be found"))
a=[2,20,30]
def occur1(a,num_to_find):
j = i = 0
while j==0:
if a[len(a)-1] > num_to_find:
if num_to_find < a[i]:
j=1
print(i)
break
else:
i = i + 1
else:
ret_state = "The entered number is greater than the numbers in the list"
return ret_state
print(occur1(a,num_to_find))
This code is difficult to reason about due to extra variables, poor variable names (j is typically used as an index, not a bool flag), usage of break, nested conditionals and side effect. It's also inefficient because it needs to visit each element in the list in the worst case scenario and fails to take advantage of the sorted nature of the list to the fullest. However, it appears working.
Your first misunderstanding is likely that print(i) is printing the index of the next largest element rather than the element itself. In your example call of occur1([2, 20, 30], 3)), 1 is where 20 lives in the array.
Secondly, once the found element is printed, the function returns None after it breaks from the loop, and print dutifully prints None. Hopefully this explains your output--you can use return a[i] in place of break to fix your immediate problem and meet your expectations.
Having said that, Python has a builtin module for this: bisect. Here's an example:
from bisect import bisect_right
a = [1, 2, 5, 6, 8, 9, 15]
index_of_next_largest = bisect_right(a, 6)
print(a[index_of_next_largest]) # => 8
If the next number greater than k is out of bounds, you can try/except that or use a conditional to report the failure as you see fit. This function takes advantage of the fact that the list is sorted using a binary search algorithm, which cuts the search space in half on every step. The time complexity is O(log(n)), which is very fast.
If you do wish to stick with a linear algorithm similar to your solution, you can simplify your logic to:
def occur1(a, num_to_find):
for n in a:
if n > num_to_find:
return n
# test it...
a = [2, 5, 10]
for i in range(11):
print(i, " -> ", occur1(a, i))
Output:
0 -> 2
1 -> 2
2 -> 5
3 -> 5
4 -> 5
5 -> 10
6 -> 10
7 -> 10
8 -> 10
9 -> 10
10 -> None
Or, if you want the index of the next largest number:
def occur1(a, num_to_find):
for i, n in enumerate(a):
if n > num_to_find:
return i
But I want to stress that the binary search is, by every measure, far superior to the linear search. For a list of a billion elements, the binary search will make about 20 comparisons in the worst case where the linear version will make a billion comparisons. The only reason not to use it is if the list can't be guaranteed to be pre-sorted, which isn't the case here.
To make this more concrete, you can play with this program (but use the builtin module in practice):
import random
def bisect_right(a, target, lo=0, hi=None, cmps=0):
if hi is None:
hi = len(a)
mid = (hi - lo) // 2 + lo
cmps += 1
if lo <= hi and mid < len(a):
if a[mid] < target:
return bisect_right(a, target, mid + 1, hi, cmps)
elif a[mid] > target:
return bisect_right(a, target, lo, mid - 1, cmps)
else:
return cmps, mid + 1
return cmps, mid + 1
def linear_search(a, target, cmps=0):
for i, n in enumerate(a):
cmps += 1
if n > target:
return cmps, i
return cmps, i
if __name__ == "__main__":
random.seed(42)
trials = 10**3
list_size = 10**4
binary_search_cmps = 0
linear_search_cmps = 0
for n in range(trials):
test_list = sorted([random.randint(0, list_size) for _ in range(list_size)])
test_target = random.randint(0, list_size)
res = bisect_right(test_list, test_target)[0]
binary_search_cmps += res
linear_search_cmps += linear_search(test_list, test_target)[0]
binary_search_avg = binary_search_cmps / trials
linear_search_avg = linear_search_cmps / trials
s = "%s search made %d comparisons across \n%d searches on random lists of %d elements\n(found the element in an average of %d comparisons\nper search)\n"
print(s % ("binary", binary_search_cmps, trials, list_size, binary_search_avg))
print(s % ("linear", linear_search_cmps, trials, list_size, linear_search_avg))
Output:
binary search made 12820 comparisons across
1000 searches on random lists of 10000 elements
(found the element in an average of 12 comparisons
per search)
linear search made 5013525 comparisons across
1000 searches on random lists of 10000 elements
(found the element in an average of 5013 comparisons
per search)
The more elements you add, the worse the situation looks for the linear search.
I would do something along the lines of:
num_to_find = int(input("Enter the number to be found"))
a=[2,20,30]
def occur1(a, num_to_find):
for i in a:
if not i <= num_to_find:
return a.index(i)
return "The entered number is greater than the numbers in the list"
print(occur1(a, num_to_find))
Which gives the output of 1 (when inputting 3).
The reason yours gives you 2 outputs, is because you have 2 print statements inside your code.

2^n question (it didn't work on large numbers)

a power of two is a number of the form 2n where n is an integer, i.e. the result of exponentiation with number two as the base and integer n as the exponent.
i want to test a power of two number.For example if i input 128 program input should be True because 128=2^7
so i wrote this code:
import math
def power_of_two(x):
if(x==0):
print ("False")
return False
else:
n = math.log2(x)
if n%1 > 0:
return False
else:
return True
but in this code if i try for example 4096 it works well but if i try larger numbers,for example 4722366482869645213702 it didn't work it output True(should be False)
how can i fix this problem??
def power_of_two(x):
return x > 0 and (x & (x-1)) == 0
Explanation.
Powers of 2 look like
10000000000000000000
After subtraction of 1 they look like
01111111111111111111
Bitwise and of these numbers is 0. For other positive numbers it's false.
A simple solution is to convert the number to its binary form, using bin then test if the first digit is a 1 and all others are 0.
def is_a_power_of_two(x):
if x == 1:
return True
b = bin(x)[2:]
return int(b[0]) == 1 and int(b[1:]) == 0
That's not very fast, but if performances are not an issue for you then it's just fine.
You can test that there are no false negatives using this:
for i in range(100):
assert is_a_power_of_two(2**i)

Generating sequence of numbers with recursion python

The goal is to generate catalan numbers ! my code works up to n = 30 (i tried the same algorithm in JAVA and it's totally correct,but, then something strange happens with python , it gives wrong numbers back after n=30. I'm totally sure that there is an issue about rounding or maybe formats, but can't figure it out by myself!
def catalan(n):
if n < 0:
return -1
else:
if n == 0:
return 1
else:
c_n = (4*n-2)/(n+1)*catalan(n-1)
return int(c_n)
By using /(n+1) you produce a floating point number, which by its nature has a limited precision. This precision is not accurate enough for the larger numbers that appear with n > 30.
So instead, use a formula that sticks with integer numbers: first multiply and only then perform the division, an integer division:
c_n = (4*n-2)*catalan(n-1)//(n+1)
The cast to int is then also unnecessary, and you can just do:
return c_n
Side note: you don't need else when you return in the if part of the statement. So you can write:
def catalan(n):
if n < 0:
return -1
if n == 0:
return 1
return (4*n-2)*catalan(n-1)//(n+1)

Resources