Find minimal operations such that N will become 1 with 3 operations - python

We are given N as a natural number and we can do these 3 operations with him.
1)Subtract 1 from N
2)If it is even, divide by 2
3)If it divides 3, then divide by 3
We stop this when N becomes 1. We need to find minimal operations such that N will become 1
#!/bin/python3
import math
import os
import random
import re
import sys
#
# Complete the 'count' function below.
#
# The function is expected to return an INTEGER.
# The function accepts INTEGER num as parameter.
#
def count(num):
count = 0
while num != 1:
if (num % 3 ==0 ):
count += 1
num = num / 3
elif((num -1) % 3 == 0):
count += 2
num = (num - 1) / 3
elif (num % 2 ==0):
count += 1
num = num / 2
else:
num = num - 1
count += 1
return count
This is my code. From 11 test cases it passed 9 and gave 2 wrong answers. I don't know for which test cases my code gives wrong answers. Can you help to understand where is problem?

You make an assumption that it is better to subtract 1 and divide by 3 first if you can, but that isn't always true.
Consider 16
Your solution would be 16-15-5-4-3-1 = 5 steps
Better solution:
16-8-4-2-1 = 4 steps

Probably not the most efficient solution but:
def count(num):
count1, count2 = float('inf'), float('inf')
if num == 1:
return 0
if num % 3 == 0:
count1 = 1 + count(num // 3)
if num % 2 == 0:
count2 = 1 + count(num // 2)
count3 = 1 + count(num - 1)
return min(count1, count2, count3)

These types of problems usually have a small set of rules that produce the optimal answer.
Your fundamental problem, though, is that you are assuming that your rule set is optimal with no evidence whatsoever. You really have no reason to believe that it will produce the right count.
Unfortunately, answers to these kinds of questions on SO sometimes provide the correct rule set, but always seem to omit any kind of proof that the rule set is optimal.
You have to do something that is guaranteed to work, so unless you have that kind of proof, you should fall back on an A* search or similar.

You may use the non-recursive breadth-first strategy.
Say there is a queue holding lists of numbers. Initially, enter a list holding just N into the queue. Then repeat the following until the queue is empty:
Pick the first list in the queue.
Repeat 3 for all three operations.
Select an operation. If possible, apply it to the last list number and add the result to the end of the list. If the list is a solution, store it somewhere. If not, add it to the end of the queue.
This strategy is exhaustive and finds all solutions. Since only the minimum is of interest, we introduce a limiting bound. Once a solution list is available, we do not enter lists longer than that into the queue, nor do we consider them if they are in the queue already.

Related

How to make a while loop faster in python?

I am trying to make a script that figures out the least number divisible by numbers from 1 to 20 without any remainder. Apparently it works well with finding the least number that can be divided from 1 to 10 which is 2520, but for the former problem it takes a lot of time to actually find it because the number is much bigger than 2520 (232792560). So is there anyway to make that process faster, I am totally new to Python by the way. Here is the code I used:
num = 1
oper = 1
while oper <= 20:
y = num % oper
if y == 0:
oper += 1
else:
num += 1
oper = 1
print(num)
from math import gcd
a = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
lcm = a[0]
for i in a:
lcm = lcm*i//gcd(lcm, i)
print(lcm)
Your algorithm is quite slow and will not work for quite large numbers efficiently.
This will find the lcm for each number and multiply it to the previous number.
you can do it using numpy:
import numpy as np
arr = np.arange(1,21)
t = np.lcm.reduce(arr)
also see these examples

Printing prime factors of a number using a "points system"

We've been given a challenge to write a program that prints prime factors of an input number. We're given an outline: the program checks for factors of the original number; each such factor gets one point. Then, if that factor is prime, it gets another point. Each factor with two points gets printed.
Here's what I have so far.
print('Please enter a number')
x = int(input('Number:'))
a = int(0)
b = int(0)
c = int(1)
d = int(0)
counter = int(0)
while (a < x-1):
a = a + 1
counter = 0
if (x / a % 1 == 0):
b = a
counter = counter + 1
while(c < b - 1):
c = c + 1
if((b / c) % 1 != 0):
d = b
counter = counter + 1
if(counter == 2):
print(d)
With input of 15, I get 3 and 5, but also 15 (wrong). With 24, I get 3, 4, 6, 8, and 12; several of these are not prime.
I believe the issue lies somewhere in the innermost loop, but I can't pinpoint the problem.
At first the issue was I wasn't resetting the "counter", so future factors would exceed 2 "points" and nothing would print. I fixed that by resetting it before it enters the innermost loop, but sadly, that didn't fix the problem.
I've also tried getting rid of 'b' and 'd' and just using two variables to see if I was somehow overcomplicating it, but that didn't help either.
EDIT: Edited slightly for clarity.
Your first sentence was enough of a clue for me to unravel the rest of your posting. Your main problems are loop handling and logic of determining a prime factor.
In general, I see your program design as
Input target number
Find each factor of the number
For each factor, determine whether the factor is prime. If so, print it.
Now, let's look at the innermost loop:
while(c < b - 1):
c = c + 1
if((b / c) % 1 != 0):
d = b
counter = counter + 1
if(counter == 2):
print(d)
As a styling note, please explain why you are building for logic from while loops; this makes your program harder to read. Variable d does nothing for you. Also, your handling of counter is an extra complication: any time you determine that b is a prime number, then simply print it.
The main problem is your logical decision point: you look for factors of b. However, rather than waiting until you know b is prime, you print it as soon as you discover any smaller number that doesn't divide b. Since b-1 is rarely a factor of b (only when b=2), then you will erroneously identify any b value as a prime number.
Instead, you have to wait until you have tried all possible factors. Something like this:
is_prime = True
for c in range(2, b):
if b % c == 0:
is_prime = False
break
if is_prime:
print(b)
There are more "Pythonic" ways to do this, but I think that this is more within your current programming sophistication. If it's necessary to fulfill your assignment, you can add a point only when you've found no factors, and then check the count after you leave the for loop.
Can you take it from there?
I'm not entirely sure I follow you're problem description, but here is my attempt at interpreting it as best I can:
import math
def is_factor(n,m):
return m % n == 0
def is_prime(n):
if n % 2 == 0 and n > 2:
return False
return all(n % i for i in range(3, int(math.sqrt(n)) + 1, 2))
def assign_points(n):
for i in range(1,n+1):
if is_factor(i,n) and is_prime(i):
print('two points for: ', i)
elif is_factor(i,n):
print('one point for: ', i)
print(assign_points(15))

Hofstadter equation related code in python

There was this question in a university assignment related to Hofstadter sequence. It basically say that it is a integer sequence blah blah and there are two values for a given index n. A male value [M(n)] and a female value [F(n)].
They are defined as:
M(0)=0
F(0)=1
M(n)=n-F(M(n-1))
F(n)=n-M(F(n-1))
And we were asked to write a program in python to find the Male and Female values of the sequence of a given integer.
So the code I wrote was:
def female(num):
if num == 0:
return 1
elif num >0:
return num - male(female(num-1))
def male(num):
if num==0:
return 0
elif num >0:
return num - female(male(num-1))
And when executed with a command like
print male(5)
It works without a fuss. But when I try to find the value of n = 300, the program gives no output.
So I added a print method inside one of the functions to find out what happens to the num value
[ elif num>0:
print num ...]
And it shows that the num value is decreasing until 1 and keeps hopping between 1 and 2 at times reaching values like 6.
I can’t figure out why it happens. Any insight would be nice. Also what should I do to find the values relevant to bigger integers. Thanks in advance. Cheers.
The code is theoretically fine. What you underestimate is the complexity of the computation. Formula
M(n)=n-F(M(n-1))
actually means
tmp = M(n-1)
M(n) = n - F(tmp)
So if you represent this calculation as a tree of necessary calls, you might see that its a binary tree and you should go through all its nodes to calculate the results. Given that M(n) and F(n) are about n/2 I'd estimate the total number of the nodes to be of order 2^(n/2) which becomes a huge number once you put n = 300 there. So the code works but it just will take a very very long time to finish.
The one way to work this around is to employ caching in a form of memoization. A code like this:
femCache = dict()
def female(num):
#print("female " + str(num))
global femCache
if num in femCache:
return femCache[num];
else:
res = 1 # value for num = 0
if num > 0:
res = num - male(female(num-1))
femCache[num] = res
return res
maleCache = dict()
def male(num):
#print("male " + str(num))
global maleCache
if num in maleCache:
return maleCache[num];
else:
res = 0 # value for num = 0
if num > 0:
res = num - female(male(num-1))
maleCache[num] = res
return res
print(male(300))
should be able to compute male(300) in no time.

Python: ProjectEuler Q.5

I'm trying to solve the following question: What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20? The problem is that I get a MemoryError. Is there a better way to do this, using the same logic?
for n in range(1,1000000000):
count = 0
for i in range(1,21):
if n%i == 0:
count = count + 1
if count == 20:
print n
Thanks
Assuming you're using Python 2, range allocates a list.
Utilize Python's lazy abilities with generators* , and use xrange instead.
for n in xrange(1,1000000000):
count = 0
for i in xrange(1,21):
if n%i == 0:
count = count + 1
if count == 20:
print n
That should solve your memory error. That said, there is a much faster solution to that particular problem (however, you asked for 'using the same logic').
* actually, a sequence object

Using Python for quasi randomization

Here's the problem: I try to randomize n times a choice between two elements (let's say [0,1] -> 0 or 1), and my final list will have n/2 [0] + n/2 [1]. I tend to have this kind of result: [0 1 0 0 0 1 0 1 1 1 1 1 1 0 0, until n]: the problem is that I don't want to have serially 4 or 5 times the same number so often. I know that I could use a quasi randomisation procedure, but I don't know how to do so (I'm using Python).
To guarantee that there will be the same number of zeros and ones you can generate a list containing n/2 zeros and n/2 ones and shuffle it with random.shuffle.
For small n, if you aren't happy that the result passes your acceptance criteria (e.g. not too many consecutive equal numbers), shuffle again. Be aware that doing this reduces the randomness of the result, not increases it.
For larger n it will take too long to find a result that passes your criteria using this method (because most results will fail). Instead you could generate elements one at a time with these rules:
If you already generated 4 ones in a row the next number must be zero and vice versa.
Otherwise, if you need to generate x more ones and y more zeros, the chance of the next number being one is x/(x+y).
You can use random.shuffle to randomize a list.
import random
n = 100
seq = [0]*(n/2) + [1]*(n-n/2)
random.shuffle(seq)
Now you can run through the list and whenever you see a run that's too long, swap an element to break up the sequence. I don't have any code for that part yet.
Having 6 1's in a row isn't particularly improbable -- are you sure you're not getting what you want?
There's a simple Python interface for a uniformly distributed random number, is that what you're looking for?
Here's my take on it. The first two functions are the actual implementation and the last function is for testing it.
The key is the first function which looks at the last N elements of the list where N+1 is the limit of how many times you want a number to appear in a row. It counts the number of ones that occur and then returns 1 with (1 - N/n) probability where n is the amount of ones already present. Note that this probability is 0 in the case of N consecutive ones and 1 in the case of N consecutive zeros.
Like a true random selection, there is no guarantee that the ratio of ones and zeros will be the 1 but averaged out over thousands of runs, it does produce as many ones as zeros.
For longer lists, this will be better than repeatedly calling shuffle and checking that it satisfies your requirements.
import random
def next_value(selected):
# Mathematically, this isn't necessary but it accounts for
# potential problems with floating point numbers.
if selected.count(0) == 0:
return 0
elif selected.count(1) == 0:
return 1
N = len(selected)
selector = float(selected.count(1)) / N
if random.uniform(0, 1) > selector:
return 1
else:
return 0
def get_sequence(N, max_run):
lim = min(N, max_run - 1)
seq = [random.choice((1, 0)) for _ in xrange(lim)]
for _ in xrange(N - lim):
seq.append(next_value(seq[-max_run+1:]))
return seq
def test(N, max_run, test_count):
ones = 0.0
zeros = 0.0
for _ in xrange(test_count):
seq = get_sequence(N, max_run)
# Keep track of how many ones and zeros we're generating
zeros += seq.count(0)
ones += seq.count(1)
# Make sure that the max_run isn't violated.
counts = [0, 0]
for i in seq:
counts[i] += 1
counts[not i] = 0
if max_run in counts:
print seq
return
# Print the ratio of zeros to ones. This should be around 1.
print zeros/ones
test(200, 5, 10000)
Probably not the smartest way, but it works for "no sequential runs", while not generating the same number of 0s and 1s. See below for version that fits all requirements.
from random import choice
CHOICES = (1, 0)
def quasirandom(n, longest=3):
serial = 0
latest = 0
result = []
rappend = result.append
for i in xrange(n):
val = choice(CHOICES)
if latest == val:
serial += 1
else:
serial = 0
if serial >= longest:
val = CHOICES[val]
rappend(val)
latest = val
return result
print quasirandom(10)
print quasirandom(100)
This one below corrects the filtering shuffle idea and works correctly AFAICT, with the caveat that the very last numbers might form a run. Pass debug=True to check that the requirements are met.
from random import random
from itertools import groupby # For testing the result
try: xrange
except: xrange = range
def generate_quasirandom(values, n, longest=3, debug=False):
# Sanity check
if len(values) < 2 or longest < 1:
raise ValueError
# Create a list with n * [val]
source = []
sourcelen = len(values) * n
for val in values:
source += [val] * n
# For breaking runs
serial = 0
latest = None
for i in xrange(sourcelen):
# Pick something from source[:i]
j = int(random() * (sourcelen - i)) + i
if source[j] == latest:
serial += 1
if serial >= longest:
serial = 0
guard = 0
# We got a serial run, break it
while source[j] == latest:
j = int(random() * (sourcelen - i)) + i
guard += 1
# We just hit an infinit loop: there is no way to avoid a serial run
if guard > 10:
print("Unable to avoid serial run, disabling asserts.")
debug = False
break
else:
serial = 0
latest = source[j]
# Move the picked value to source[i:]
source[i], source[j] = source[j], source[i]
# More sanity checks
check_quasirandom(source, values, n, longest, debug)
return source
def check_quasirandom(shuffled, values, n, longest, debug):
counts = []
# We skip the last entries because breaking runs in them get too hairy
for val, count in groupby(shuffled):
counts.append(len(list(count)))
highest = max(counts)
print('Longest run: %d\nMax run lenght:%d' % (highest, longest))
# Invariants
assert len(shuffled) == len(values) * n
for val in values:
assert shuffled.count(val) == n
if debug:
# Only checked if we were able to avoid a sequential run >= longest
assert highest <= longest
for x in xrange(10, 1000):
generate_quasirandom((0, 1, 2, 3), 1000, x//10, debug=True)

Categories

Resources