Combination of all possible numbers following digit rules in python - python

Looking for a "pythonish" and simple way to achieve the generation of all possible numbers following different ranges of digits.
Example: Generate strings (representing number) with digits '01234567' ranging from [0-4] occurrences and '8' ranging from [0-10] occurrences and '9' from [0-100]
Example of numbers generated: {'11337899999999', '33567899999999', '245678999999999999999',...}
(all generated numbers should be with sequential digits... so '88119' isn't valid)
So far I came up with a very simple and clean code but that doesn't do exactly what I need:
from itertools import combinations_with_replacement
length = 50
for x in combinations_with_replacement('0123456789', length):
print("".join(x), end=', ')
This will generate the numbers I need but as well a bunch of unnecessary ones, which will delay significantly the execution.
Thought on generating the digits one by one according to the rules and concatenating... but believe this will be a "dirty" solution and as well inefficient.
Anyone knows a nice clean way of doing this? (Itertools, or any other library is welcome)

I am not sure I fully undersand you problem, but i think i have a solution :
import random
def generate(string = ""):
for i in range(9):
i += 1
if i <= 7:
occurrence = 4
elif i == 8:
occurrence = 10
else:
occurrence = 100
string = string + str(i)* random.randint(0, occurrence)
return string
a = generate()
print(a)
if you want it to be lenght 50 use:
a = a[:50]

So, I ended up picking the itertools.combinations_with_replacement and alter it to accept an array containing maximum usage of digits...
Pretty simple, but it's working.
If anyone has any hints on how to look at it differently or adapt anything to speed up, I will be happy with it.
def combinations_with_replacement2(iterable, m_digits, r):
pool = tuple(iterable)
n = len(pool)
if not n and r: return
count = [0] * n
indices = [0] * r
d = 0
for i in range(r):
if count[d] == m_digits[d]: d += 1
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != n - 1: break
else: return
count = [0] * n
d = indices[i] + 1
for f in range(i, r):
indices[f] = d
count[d] += 1
if count[d] == m_digits[d]: d += 1
yield tuple(pool[i] for i in indices)

Related

All possible way of adding up number in a sequence so that it becomes a given number

I was given range n and number k. Count the possible ways so that two (not identical) number in that range add up to number k. And can this be done without nested loops?
Here's my approach, the only thing is I'm using a nested loop, which takes times and not computer-friendly. Opposite pairs like (A, B) and (B, A) still count as 1.
n, k = int(input()), int(input())
cnt = 0
for i in range(1, n+1):
for s in range(1, n+1):
if i == 1 and s == 1 or i == n+1 and s==n+1:
pass
else:
if i+s==k:
cnt += 1
print(int(cnt/2))
example inputs (first line is n, second is k)
8
5
explanation(1, 4 and 2, 3), so I should be printing 2
You only need a single loop for this:
n = int(input('N: ')) # range 1 to n
k = int(input('K: '))
r = set(range(1, n+1))
c = 0
while r:
if k - r.pop() in r:
c += 1
print(c)
If I understood you well it's gonna be just a single while loop counting up to k:
counter = 0
while counter<min(n,k)/2:
if counter+(k-counter) == k: # This is always true actually...
print(counter,k-counter)
counter+=1
Starting from 0 up to k those pairs are gonna be counter and k - counter (complement to k, so result of subtracting the counter from k)
We should can count up to smaller of the two n and k, cause numbers bigger than k are not gonna add up to k
Actually we should count up to a half of that, cause we're gonna get symmetric results after that.
So considering you don't want to print each pair it's actually:
count = int(min(n,k)//2)
why are you iterating and checking number combinations when you can mathematically derive the count of valid pairs using n and k itself?
depending on whether n or k being larger the number of pairs can be calculated directly
Every number i within n range has a matching pair k-i
and depending on whether n or k which greater we need to validate whether k-i and i both are within the range n and not equal.
for n>=k case the valid range is from 1 to k-1
and for the other case the valid range is from k-n to n
and the count of a range a to b is b-a+1
since in both conditions the pairs are symmetrical these range count should be halved.
so the entire code becomes
n= int(input())
k=int(input())
if n>=k:print(int((k-1)/2))
if n<k:print(int((2*n-(k-1))/2))
A problem of combinatorics. The following code uses python's built-in library to generate all possible combinations
from itertools import combinations
n = 10
k = 5
n_range = [i for i in range(1, n+1)]
result = []
for i in n_range:
n_comb = combinations(n_range, i)
for comb in n_comb:
if sum(comb) == k:
result.append(comb)
print(result)

How many numbers have n as their smallest prime factor within 10^6?

for _ in range(int(input())):
n=int(input())
least_prime = [0] * (1000001)
count=0
for i in range(2, int(1000001**0.5 + 1)):
if least_prime[i] == 0:
least_prime[i] = i
for j in range(i * i, 1000000, 2*i) :
if (least_prime[j] == 0) :
least_prime[j] = i
for i in range(2, 1000001) :
if least_prime[i] == n:
count+=1
print(count)
Can anyone reduce the time complexity of the problem? tried for an hour maybe but can't simplify more than this.
First for loop is for the number of test cases. And the question is regarding how many numbers have n as their smallest prime factor within 10^6?
there are 3 problems with your code:
you repeat the same task however many test case you have, which is a waste of time and resources
you leave unchanged/ignore the numbers/primes above int(1000001**0.5 + 1)
and by doing a step of 2*i in your j range you mislabel a bunch of number, like for example 6, which should have 2 marked as its least prime but it get marked as itself because it get skipped, 6=4+2 but you never get there with your j range because you make a step of 4 so you go 4,8,12,... instead of 4,6,8,...
How to fix it? simple, make the sieve first and only once, no need to repeat the exact same thing 10**6 times or however many test case you have, two or more is two too many times ( if n is always prime that is 78498 which is the number of primes less than 10**6), and the other 2 points are simple fix
I would put it inside its own function, which make it more reusable and easy to play with, and for the counting part, there is no problem with how you do it, but is more convenient with a Counter which make all the counting at once
from collections import Counter
def make_sieve(MAX):
MAX += 1
least_prime = [0] * MAX
for i in range(2, MAX):
if least_prime[i] == 0:
least_prime[i] = i
for j in range(i * i, MAX, i) :
if (least_prime[j] == 0) :
least_prime[j] = i
return least_prime
result = Counter(make_sieve(10**6))
print("n=2->",result[2])# n=2-> 500000
print("n=3->",result[3])# n=3-> 166667
so now your test can be as simple as
for _ in range(int(input())):
n = int(input())
print(result[n])
And just for completeness, here is how you can do it without the Counter
least_prime = make_sieve(10**6)
for _ in range(int(input())):
n = int(input())
count = 0
for p in least_prime:
if p==n:
count+=1
print(count)
but that is also too long, a list already do that for us with .count
least_prime = make_sieve(10**6)
for _ in range(int(input())):
n = int(input())
count = least_prime.count(n)
print(count)
The counter is still better, because in just one go you get all the answers, and if needed you can make your own with a regular dictionary, but I leave that as exercise to the reader.

Using a loop to describe multiple conditions

In the following code, I am trying to extract numbers from a list in which all digits are divisible by 2. The following code works.
l = range(100,401)
n=[]
for i in l:
s =str(i)
if all([int(s[0])%2==0,int(s[1])%2==0,int(s[2])%2==0]):
n.append(s)
print(",".join(n))
I was trying to insert a for loop to avoid writing all three conditions explicitly.
l = range(100,401)
n=[]
ss=[]
for i in l:
s =str(i)
ss.append(s)
for element in ss:
for j in range(3):
if int(element[j])%2==0:
n.append(element)
print(n)
I can't get the desired output. Not only that, the elements of output list 'n' at even index are printed twice. I am unable to figure out WHY?
Thanks.
Generator expression checking if all() elements evaluate to True comes to your rescue:
l = range(100,401)
n=[]
for i in l:
s = str(i)
if all(int(ch) % 2 == 0 for ch in s):
n.append(s)
print(",".join(n))
Now it also works even if you work with more digits.
Thanks for #jpp's advice on generator expression!
And here a faster alternative where you evaluate if any() is not divisable with 2.
l = range(100,401)
n=[]
for i in l:
s = str(i)
if any(int(ch) % 2 != 0 for ch in s):
continue
else:
n.append(s)
print(",".join(n))
You can do this:
l = range(100, 401)
n = []
for i in l:
v = 0
for j in str(i):
if int(j) % 2 == 0:
v += 1
if v == len(str(i)):
n.append(str(i))
print(",".join(n))
Or with some list comprehension:
l = range(100, 401)
n = []
for i in l:
if all(int(j) % 2 == 0 for j in str(i)):
n.append(str(i))
print(",".join(n))
Or with even more list comprehension:
l = range(100, 401)
n = [str(i) for i in l if all(int(j) % 2 == 0 for j in str(i))]
print(",".join(n))
Or with a ridiculous minimizing:
print(",".join([str(i) for i in range(100, 401) if all(int(j) % 2 == 0 for j in str(i))]))
Explaining
OP asked me to explain why his code doesn't work. I'll make it in some steps, also optimizing it:
l = range(100,401)
n = []
ss = []
for i in l: # You don't need this loop, you are just making a new list with string values instead of integers. You could make that on the fly.
s = str(i)
ss.append(s)
for element in ss:
for j in range(3):
if int(element[j]) % 2 == 0: # This only check if the current digit is pair and it append the whole number to the list. You have to check if the 3 numbers are pair AND then append it.
n.append(element)
print(n)
Your code check each digit and if that is true, the number is appended to the result list (n). But you don't want that, you want to check if the 3 digits that make the number are pair, so you have to check the whole group.
For example you could do this:
for element in l:
pairs = 0
for j in range(3):
if int(str(element)[j]) % 2 == 0:
pairs += 1 # Each time a digit of the number is pair, `pairs` variable increase in one
if pairs == 3: # If the 3 digits are true it append your number
n.append(str(element))
That is my first idea of how to improve your code, but instead of element and pairs I use j and v, (also I don't use range(3), I just iterate over the stringed number).
If you are looking for something "better" you could try to use a list comprehension like all(int(j) % 2 == 0 for j in str(i)). That iterate over all the digits to check if the are pair, if all the checks are true (like 222, or 284) it returns true.
Let me know if I should explain something more.
Try this method. You don't need to check all the numbers.
You just need to change the range statement from range(100, 401) to range (100, 401, 2) and add some checks as the Numbers which have first digit as Odd you can skip all the 100 numbers and then in next 10 series you can skip 10 if the tenth digit is odd. It reduces the complexity and decreases your processing time.
l = range(100, 401, 2)
n = []
for i in l:
s = str(i)
if int(s[0]) % 2 == 1:
remainder = i % 100
i = i - remainder + 100 - 1
continue
elif int(s[1])%2 == 1:
remainder = i % 10
i = i - remainder + 10 - 1
continue
n.append(s)
print(",".join(n))

Need better logic in finding the count of palindrome numbers in the range

I have two numbers say A = 10 and B =20.
Now I need to count the palindrome numbers in Range (A,B)
I tried this:
s = list(map(int,raw_input().split()))
a = s[0]
b = s[1]
l = range(s[0],s[1]+1)
# print "list : ",l
def isNumberPalindrome(n):
return str(n) == str(n)[::-1]
x = filter(isNumberPalindrome, l)
# print " All Palindorme numbers : ",x
count = len(x)
print count
I have problem of memory exceeding if A and B are in range of 10^18.
Can somebody suggest me how to solve this.
Thanks in Advance
Use a generator instead of calling range().
from __future__ import print_function
def isNumberPalindrome(n):
return str(n) == str(n)[::-1]
a = pow(10, 18)
b = pow(10, 19) + 1
def gen_range(start, end):
i = long(start)
while i < end:
yield i
i = i + 1
count = 0
for l in gen_range(a, b):
count += isNumberPalindrome(l)
print(count)
It is not the whole answer. But consider this:
For range 10^n to 10^n+1 you can find the number of palindromes in constant time. It is 10^ceil(n/2) - 10^ceil(n/2)-1. Because (for example) for n = 6 and range from 10^6 to 10^7 (1 000 000 - 10 000 000) number of palindromes is the number of possible numbers from 1000 to 10000 (that represent the first half of original numbers. 4315 is the first half of 4315134 and so on).
So you don't filter numbers but find how many palindromes you can generate in such range.

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