Python - Random sample from a range whilst avoiding certain values - python

I have been reading up about the random.sample() function in the random module and have not seen anything that solves my problem.
I know that using random.sample(range(1,100),5) would give me 5 unique samples from the 'population'...
I would like to get a random number in range(0,999). I could use random.sample(range(0,999),1) but why then am I thinking about using random.sample() ?
I need the random number in that range to not match any number in a separate array (Say, [443,122,738])
Is there a relatively easy way I could go about doing this?
Also, I am pretty new to python and am definitely a beginner -- If you would like me to update the question with any information I may have missed then I will.
EDIT:
Accidentally said random.range() once. Whoops.

One way you can accomplish that is by simply checking the number and then appending it to a list where you can then use the numbers.
import random
non_match = [443, 122, 738]
match = []
while len(match) < 6: # Where 6 can be replaced with how many numbers you want minus 1
x = random.sample(range(0,999),1)
if x not in non_match:
match.append(x)

There are two main ways:
import random
def method1(lower, upper, exclude):
choices = set(range(lower, upper + 1)) - set(exclude)
return random.choice(list(choices))
def method2(lower, upper, exclude):
exclude = set(exclude)
while True:
val = random.randint(lower, upper)
if val not in exclude:
return val
Example usage:
for method in method1, method2:
for i in range(10):
print(method(1, 5, [2, 4]))
print('----')
Output:
1
1
5
3
1
1
3
5
5
1
----
5
3
5
1
5
3
5
3
1
3
----
The first is better for a smaller range or a larger list exclude (so the choices list won't be too big), the second is better for the opposite (so it doesn't loop too many times looking for an appropriate option).

Related

How can I count sequences that meet these constraints?

I am trying to count permutations of a sequence of I and O symbols, representing e.g. people entering (I for "in") and leaving (O for "out") a room. For a given n many I symbols, there should be exactly as many O symbols, giving a total length of 2*n for the sequence. Also, at any point in a valid permutation, the number of O symbols must be less than or equal to the number of I symbols (since it is not possible for someone to leave the room when it is empty).
Additionally, I have some initial prefix of I and O symbols, representing people who previously entered or left the room. The output should only count sequences starting with that prefix.
For example, for n=1 and an initial state of '', the result should be 1 since the only valid sequence is IO; for n=3 and an initial state of II, the possible permutations are
IIIOOO
IIOIOO
IIOOIO
for a result of 3. (There are five ways for three people to enter and leave the room, but the other two involve the first person leaving immediately.)
I'm guessing the simplest way to solve this is using itertools.permutations. This is my code so far:
n=int(input()) ##actual length will be 2*n
string=input()
I_COUNT=string.count("I")
O_COUNT=string.count("O")
if string[0]!="I":
sys.exit()
if O_COUNT>I_COUNT:
sys.exit()
perms = [''.join(p) for p in permutations(string)]
print(perms)
the goal is to get the permutation for whatever is left out of the string and append it to the user's input, so how can I append user's input to the remaining length of the string and get the count for permutation?
#cache
def count_permutations(ins: int, outs: int):
# ins and outs are the remaining number of ins and outs to process
assert outs >= ins
if ins == 0 :
# Can do nothing but output "outs"
return 1
elif outs == ins:
# Your next output needs to be an I else you become unbalanced
return count_permutations(ins - 1, outs)
else:
# Your. next output can either be an I or an O
return count_permutations(ins - 1, outs) + count_permutations(ins, outs - 1)
If, say you have a total of 5 Is and 5 Os, and you've already output one I, then you want: count_permutations(4, 5).
I'm guessing the simplest way to solve this is using itertools.permutations
Sadly, this will not be very helpful. The problem is that itertools.permutations does not care about the value of the elements it's permuting; it treats them as all distinct regardless. So if you have 6 input elements, and ask for length-6 permutations, you will get 720 results, even if all the inputs are the same.
itertools.combinations has the opposite issue; it doesn't distinguish any elements. When it selects some elements, it only puts those elements in the order they initially appeared. So if you have 6 input elements and ask for length-6 combinations, you will get 1 result - the original sequence.
Presumably what you wanted to do is generate all the distinct ways of arranging the Is and Os, then take out the invalid ones, then count what remains. This is possible, and the itertools library can help with the first step, but it is not straightforward.
It will be simpler to use a recursive algorithm directly. The general approach is as follows:
At any given time, we care about how many people are in the room and how many people must still enter. To handle the prefix, we simply count how many people are in the room right now, and subtract that from the total number of people in order to determine how many must still enter. I leave the input handling as an exercise.
To determine that count, we count up the ways that involve the next action being I (someone comes in), plus the ways that involve the next action being O (someone leaves).
If everyone has entered, there is only one way forward: everyone must leave, one at a time. This is a base case.
Otherwise, it is definitely possible for someone to come in. We recursively count the ways for everyone else to enter after that; in the recursive call, there is one more person in the room, and one fewer person who must still enter.
If there are still people who have to enter, and there is also someone in the room right now, then it is also possible for someone to leave first. We recursively count the ways for others to enter after that; in the recursive call, there is one fewer person in the room, and the same number who must still enter.
This translates into code fairly directly:
def ways_to_enter(currently_in, waiting):
if waiting == 0:
return 1
result = ways_to_enter(currently_in + 1, waiting - 1)
if currently_in > 0:
result += ways_to_enter(currently_in - 1, waiting)
return result
Some testing:
>>> ways_to_enter(0, 1) # n = 1, prefix = ''
1
>>> ways_to_enter(2, 1) # n = 3, prefix = 'II'; OR e.g. n = 4, prefix = 'IIOI'
3
>>> ways_to_enter(0, 3) # n = 3, prefix = ''
5
>>> ways_to_enter(0, 14) # takes less than a second on my machine
2674440
We can improve the performance for larger values by decorating the function with functools.cache (lru_cache prior to 3.9), which will memoize results of the previous recursive calls. The more purpose-built approach is to use dynamic programming techniques: in this case, we would initialize 2-dimensional storage for the results of ways_to_enter(x, y), and compute those values one at a time, in such a way that the values needed for the "recursive calls" have already been done earlier in the process.
That direct approach would look something like:
def ways_to_enter(currently_in, waiting):
# initialize storage
results = [[0] * currently_in for _ in waiting]
# We will iterate with `waiting` as the major axis.
for w, row in enumerate(results):
for c, column in enumerate(currently_in):
if w == 0:
value = 1
else:
value = results[w - 1][c + 1]
if c > 0:
value += results[w][c - 1]
results[w][c] = value
return results[-1][-1]
The product() function from itertools will allow you to generate all the possible sequences of 'I' and 'O' for a given length.
From that list, you can filter by the sequences that start with the user-supplied start_seq.
From that list, you can filter by the sequences that are valid, given your rules of the number and order of the 'I's and 'O's:
from itertools import product
def is_valid(seq):
'''Evaluates a sequence I's and O's following the rules that:
- there cannot be more outs than ins
- the ins and outs must be balanced
'''
_in, _out = 0, 0
for x in seq:
if x == 'I':
_in += 1
else:
_out += 1
if (_out > _in) or (_in > len(seq)/2):
return False
return True
# User inputs...
start_seq = 'II'
assert start_seq[0] != 'O', 'Starting sequence cannot start with an OUT.'
n = 3
total_len = n*2
assert len(start_seq) < total_len, 'Starting sequence is at least as big as total number, nothing to iterate.'
# Calculate all possible sequences that are total_len long, as tuples of 'I' and 'O'
seq_tuples = product('IO', repeat=total_len)
# Convert tuples to strings, e.g., `('I', 'O', 'I')` to `'IOI'`
sequences = [''.join(seq_tpl) for seq_tpl in seq_tuples]
# Filter for sequences that start correctly
sequences = [seq for seq in sequences if seq.startswith(start_seq)]
# Filter for valid sequences
sequences = [seq for seq in sequences if is_valid(seq)]
print(sequences)
and I get:
['IIIOOO', 'IIOIOO', 'IIOOIO']
Not very elegant perhaps but this certainly seems to fulfil the brief:
from itertools import permutations
def isvalid(start, p):
for c1, c2 in zip(start, p):
if c1 != c2:
return 0
n = 0
for c in p:
if c == 'O':
if (n := n - 1) < 0:
return 0
else:
n += 1
return 1
def calc(n, i):
s = i + 'I' * (n - i.count('I'))
s += 'O' * (n * 2 - len(s))
return sum(isvalid(i, p) for p in set(permutations(s)))
print(calc(3, 'II'))
print(calc(3, 'IO'))
print(calc(3, 'I'))
print(calc(3, ''))
Output:
3
2
5
5
def solve(string,n):
countI =string.count('I')
if countI==n:
return 1
countO=string.count('O')
if countO > countI:
return 0
k= solve(string + 'O',n)
h= solve(string + 'I',n)
return k+h
n= int(input())
string=input()
print(solve(string,n))
This is a dynamic programming problem.
Given the number of in and out operations remaining, we do one of the following:
If we're out of either ins or outs, we can only use operations of the other type. There is only one possible assignment.
If we have an equal number of ins or outs, we must use an in operation according to the constraints of the problem.
Finally, if we have more ins than outs, we can perform either operation. The answer, then, is the sum of the number of sequences if we choose to use an in operation plus the number of sequences if we choose to use an out operation.
This runs in O(n^2) time, although in practice the following code snippet can be made faster using a 2D-list rather than the cache annotation (I've used #cache in this case to make the recurrence easier to understand).
from functools import cache
#cache
def find_permutation_count(in_remaining, out_remaining):
if in_remaining == 0 or out_remaining == 0:
return 1
elif in_remaining == out_remaining:
return find_permutation_count(in_remaining - 1, out_remaining)
else:
return find_permutation_count(in_remaining - 1, out_remaining) + find_permutation_count(in_remaining, out_remaining - 1)
print(find_permutation_count(3, 3)) # prints 5
The number of such permutations of length 2n is given by the n'th Catalan number. Wikipedia gives a formula for Catalan numbers in terms of central binomial coefficients:
from math import comb
def count_permutations(n):
return comb(2*n,n) // (n+1)
for i in range(1,10):
print(i, count_permutations(i))
# 1 1
# 2 2
# 3 5
# 4 14
# 5 42
# 6 132
# 7 429
# 8 1430
# 9 4862

Python 3: Executing a For loop x number of times by using a variable

Edit: Fixed the screwed up brackets, sorry. Should have caught this. Let's see if this clarifies what I'm trying to do.
I want to do a basic program that takes the variable "run" and runs the following code that number of times by using a For loop.
run = 3
def program(run):
for i in range(5):
print("The number is",i)
program(run)
What I want is to get is:
the number is 0
the number is 1
the number is 2
the number is 3
the number is 4
the number is 0
the number is 1
the number is 2
the number is 3
the number is 4
the number is 0
the number is 1
the number is 2
the number is 3
the number is 4
i.e. the "program" loops however many times I set "run" to equal.
Let's break the code and the error message down a bit:
for i in range[5]:
TypeError: 'type' object is not subscriptable
"Subscripting" is the way you access a particular element in a container, like a list or a dict. For example, to get the first (0th) element of a list called foo, you'd say foo[0], and to get the value associated with the key "bar" in a dictionary called foo, you'd say foo["bar"].
Note that when you just use the [] symbols by themselves (instead of putting them after some other identifier), they serve a different purpose: they construct a list. The expression [5] is a list that contains a single element (the number 5). To represent more elements, you comma-separate them, e.g. [0, 1, 2, 3, 4].
So what the error is telling you is that a type object can't be subscripted. Does that mean that range is a type? Yes! In Python, a range is a type of iterable object that represents a range of numbers. On its own, the word range in a Python program refers to the entire class of range objects (that is, a type of object), not a particular range object.
You construct a range the same way you construct most objects, by calling the type as a constructor function, with parameters in parentheses. For example, to construct a range with 5 numbers in it (0 to 4, similar to the list [0, 1, 2, 3, 4]) you can simply say range(5).
So to print all the numbers from 0 to 4 you can do:
for i in range(5):
print("The number is", i)
In your code, the run variable serves no purpose and can be removed completely.
Functions can take any number of parameters, or none at all, and since in this case your function doesn't do anything with the parameter you pass in, it can be simply:
def program():
for i in range(5):
print("The number is", i)
program()
If you wanted to make the length of the range variable, that would make sense as a parameter -- you just need to pass the parameter along to your range construction:
def program(num):
for i in range(num):
print("The number is", i)
runs = 3
for _ in range(runs):
program(5)
You'd use range, but you would use parenthesis to call the function instead of braces. I.e. range(5) instead of range[5]. Or you could use your run variable like range(run)
In short you are seeing the error because you are using square brackets and not parentheses range() is a function. Change it to
def program(run):
for i in range(5):
print("The number is",i)
program(run)
The number is 0
The number is 1
The number is 2
The number is 3
The number is 4
and it works fine.
If you want to the program to loop the same number of times as specified in run then you can do this:
run = 5
def program(run):
for i in range(run):
print("the number is",i)
program(run)
def program(n):
for i in range(0,n+1):
print("The number is",i)
program(5)
ouput
The number is 0
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
Give this a try. Hopefully understood the assignment:
run = 3
def program(run):
nums = [f'The number is: {n}' for r in range(run) for n in range(5)]
print('\n'.join(nums))
program(run)
This uses list comprehension to loop twice which translates to:
run = 3
def program(run):
for r in range(run):
for n in range(5):
print(f'The number is: {n}')
program(run)
Or:
run = 3
def program():
for n in range(5):
print(f'The number is: {n}')
for r in range(run):
program()
Output:
The number is: 0
The number is: 1
The number is: 2
The number is: 3
The number is: 4
The number is: 0
The number is: 1
The number is: 2
The number is: 3
The number is: 4
The number is: 0
The number is: 1
The number is: 2
The number is: 3
The number is: 4
Whelp, I finally figured it out - the reason I couldn't seem to find a solution anywhere to "could this be done and how would I go about it" is that in Python at least, a For statement cannot do this kind of loop, only a While can.
So my experiment of trying to do this via a For is a moot point, it's not something that can be coded without using While.
Thanks for the help.

Augmented Assignment

I am new to the world of Python and programming in general, and today I have faced a problem with augmented assignment. I unfortunately do not understand the code, and what for i in range(multiplier) and answer *= number does. I tried understanding it but I still don't really get the logic behind it. Can somebody please explain?
number = 5
multiplier = 8
answer = 0
for i in range(multiplier):
answer *= number
print(answer)
range([start], stop[, step])
range is a function that takes a number and return a list of number from 0 ... right through to the number you gave it as an argument.
BUT THE KEY TO NOTICE IS THAT IT WILL NEVER INCLUDE THE NUMBER YOU
TOLD IT TO COUNT TO
. Example :
This is an example of giving the function range 1 argument:
>>> # One parameter
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
Here is an example of giving it two arguments where the first argument is telling the function what to start the list it returns at. The second argument is where it should end:
>>> # Two parameters
>>> for i in range(3, 6):
... print(i)
...
3
4
5
Here is an even cooler example where we use the third argument as well. This argument tells the function to count from whatever number you told it to start at, right through to whatever number you told it to stop at (just like the above to examples)... only now, the third argument tells it in what steps to count:
Like count from 2 to 12 but count in 2's:
>>> # Three parameters
>>> for i in range(2, 12, 2):
... print(i)
...
2
4
6
8
10
SO....
the for loop is just iterating through that list of numbers that is given back by the function range
so lets break that for loop in to pseudo code.
***loop***
for i in range(multiplier):
answer *= number
***Pseudo code***
Give me a list of numbers
(not giving it a start value but only a value to end the count at).
The numbers has to be from 0 to multiplier (8).
so range(multiplier) -> range(8) -> [0, 1, 2, 3, 4, 5, 6, 7]
now you have a list
now you ask the compiler to go through that list.
you say : go through that list and everytime you at a new number, give it to me in the for of a variable called i.
then when you have i, you don't use it because i was just trying to loop 8 (multiplier) times... but now take the answer and add to it this (answer * number) ... this will happen 8 times because you loop 8 times

To find the minimum(least) number of Unique numbers in a given list after 'n' reductions

Problem Statement: To find the minimum(least) number of Unique numbers in a given list after 'n' reductions
Input:
N and an Array(or list)
Where 0 < N < len(Array)
N is the number of reductions possible and the input for the array needs to be separated by commas(,)
Example 1:
N = 2
Array = 1, 2, 3, 3, 4, 4
Output:
To find the Least or minimum number of unique elements after deleting N number of elements in the Array
In the above example,
After deleting N = 2 number of elements from the Array
In the above example 1, 2 should be deleted from the array
3, 3, 4, 4 will be remaining
So, 2 unique elements remaining after deleting 2 elements from the array
So, the output should be 2
Example 2:
N = 2 [ number of reductions possible]
Input Array : 1,3,4,1,2,4,2,2
Output: 3 [least number of unique elements]
Explanation :[1,1,2,2,4,4] will be the resultant array when [2,3] are removed
Supposed to be coded in Python exclusively but solutions in any language will be appreciated.
Finding the minimum number of unique elements is equivalent to finding the maximum number of duplicates.
The driving idea here would be to use your reductions to take out the elements that appear the fewest number of times first. In order to do that, you'd want to count the number of occurrences of each element in the list, sort them by number of occurrences, and remove them from least to most until you run out of deletions. The only tricky part is the first part, and that's only if you have to code it in pure python (#DerekLangley's answer gives a good example of how you might do that).
If you're allowed to import other parts of the standard library, then collections.Counter makes quick work of this problem. Here's a sample implementation that doesn't account for anything that could go wrong (such as an empty list, or N being larger than len(lst) - these are things that the interviewer would expect you to mention and know how to handle, so work on that).
import collections
...
def min_uniques(N, lst):
# use collections.Counter to get a sorted list of unique elements and their frequencies
most_common = collections.Counter(lst).most_common()
# returns [(most_frequent, num_occurrences), ...], so we pull from the back to get fewest occurrences.
# We could reverse the list and pull from the front but that would be less efficient
while N >= most_common[-1][1]:
# remove the element with lowest count and subtract its count from N, all at once
N -= most_common.pop()[1]
# return the number of unique elements left, after we can no longer remove enough to decrease that count
return len(most_common)
min_uniques(2, [1, 2, 3, 3, 4, 4])
# 2
min_uniques(2, [1, 3, 4, 1, 2, 4, 2, 2])
# 3
My comments on that code represent how I would talk through the problem with the interviewer as I was writing it. This is a four-line python function, but I'm pretty sure you could also do it in two - the interviewer might ask for how you can improve this code, and if you can put in that as an example (maybe say "I think it would use mechanism X or mechanism Y, but I'd have to look at the documentation and do some tinkering first).
I don't especially see how Dynamic Programming is relevant here, though I kind of feel like Dynamic Programming is a bit of a buzzword anyway.
Since you do not show any code of your own, I will just give some ideas for an algorithm. If you want more details, please show some of your own code.
The Counter object in the collections module in Python's standard library can count the number of occurrences of each number in the array. Use Counter to do that for your array. The size of the resulting Counter object is the number of unique items in the array.
Then use Counter's most_common method to sort that information from the most popular number to the least popular. Now look at that result from the least popular end. Use your value of N to "remove" the least popular values in the array. You don't need to actually do the removal--just do it conceptually. When you have done that removal (conceptually or actually), the size of the Counter object is then your answer.
Of course, there are ways to do this without Counter but the code will be more lengthy. Again, show some more effort of your own then I will be glad to give more details.
Here's a (probably) inefficient way of doing it.
ls = [1,3,4,1,2,4,2,2]
d = {}
for i in ls:
if i not in d:
d[i] = 1
else:
d[i] += 1
def min_num(n):
counter = n
while counter > 0:
del d[min(d, key = d.get)]
counter -= 1
return len(d.keys())
min_num(2)

How to determine the sum of a group of integers without using recursion

This is my first post on Stack Overflow, and I'm hoping that it'll be a good one.
This is a problem that I thought up myself, and now I'm a bit embarrassed to say, but it's beating the living daylights out of me. Please note that this is not a homework exercise, scout's honor.
Basically, the program takes (as input) a string made up of integers from 0 to 9.
strInput = '2415043'
Then you need to break up that string of numbers into smaller groups of numbers, until eventually, the sum of those groups give you a pre-defined total.
In the case of the above string, the target is 289.
iTarget = 289
For this example, there are two correct answers (but most likely only one will be displayed, since the program stops once the target has been reached):
Answer 1 = 241, 5, 043 (241 + 5 + 043 = 289)
Answer 2 = 241, 5, 0, 43 (241 + 5 + 0 + 43 = 289)
Note that the integers do not change position. They are still in the same order that they were in the original string.
Now, I know how to solve this problem using recursion. But the frustrating part is that I'm NOT ALLOWED to use recursion.
This needs to be solved using only 'while' and 'for' loops. And obviously lists and functions are okay as well.
Below is some of the code that I have so far:
My Code:
#Pre-defined input values, for the sake of simplicity
lstInput = ['2','4','1','5','0','4','3'] #This is the kind of list the user will input
sJoinedList = "".join(lstInput) #sJoinedList = '2415043'
lstWorkingList = [] #All further calculuations are performed on lstWorkingList
lstWorkingList.append(sJoinedList) #lstWorkingList = ['2415043']
iTarget = 289 #Target is pre-defined
-
def SumAll(_lst): #Adds up all the elements in a list
iAnswer = 0 #E.g. lstEg = [2,41,82]
for r in _lst: # SumAll(lstEg) = 125
iAnswer += int(r)
return(iAnswer)
-
def AddComma(_lst):
#Adds 1 more comma to a list and resets all commas to start of list
#E.g. lstEg = [5,1001,300] (Note only 3 groups / 2 commas)
# AddComma(lstEg)
# [5,1,0,001300] (Now 4 groups / 3 commas)
iNoOfCommas = len(_lst) - 1 #Current number of commas in list
sResetString = "".join(_lst) #Make a string with all the elements in the list
lstTemporaryList = []
sTemp = ""
i = 0
while i < iNoOfCommas +1:
sTemp += sResetString[i]+',' #Add a comma after every element
i += 1
sTemp += sResetString[i:]
lstTemporaryList = sTemp.split(',') #Split sTemp into a list, using ',' as a separator
#Returns list in format ['2', '415043'] or ['2', '4', '15043']
return(lstTemporaryList)
return(iAnswer)
So basically, the Pseudo-code will look something like this:
Pseudo-Code:
while SumAll(lstWorkingList) != iTarget: #While Sum != 289
if(len(lstWorkingList[0]) == iMaxLength): #If max possible length of first element is reached
AddComma(lstWorkingList) #then add a new comma / group and
Reset(lstWorkingList) #reset all the commas to the beginning of the list to start again
else:
ShiftGroups() #Keep shifting the comma's until all possible combinations
#for this number of comma's have been tried
#Otherwise, Add another comma and repeat the whole process
Phew! That was quite a mouthfull .
I have worked through the process that the program will follow on a piece of paper, so below is the expected output:
OUTPUT:
[2415043] #Element 0 has reached maximum size, so add another group
#AddComma()
#Reset()
[2, 415043] #ShiftGroups()
[24, 15043] #ShiftGroups()
[241, 5043] #ShiftGroups()
#...etc...etc...
[241504, 3] #Element 0 has reached maximum size, so add another group
#AddComma()
#Reset()
[2, 4, 15043] #ShiftGroups()
[2, 41, 5043] #ShiftGroups()
#etc...etc...
[2, 41504, 3] #Tricky part
Now here is the tricky part.
In the next step, the first element must become 24, and the other two must reset.
#Increase Element 0
#All other elements Reset()
[24, 1, 5043] #ShiftGroups()
[24, 15, 043] #ShiftGroups()
#...etc...etc
[24, 1504, 3]
#Increase Element 0
#All other elements Reset()
[241, 5, 043] #BINGO!!!!
Okay. That is the basic flow of the program logic. Now the only thing I need to figure out, is how to get it to work without recursion.
For those of you that have been reading up to this point, I sincerely thank you and hope that you still have the energy left to help me solve this problem.
If anything is unclear, please ask and I'll clarify (probably in excruciating detail X-D).
Thanks again!
Edit: 1 Sept 2011
Thank you everyone for responding and for your answers. They are all very good, and definitely more elegant than the route I was following.
However, my students have never worked with 'import' or any data-structures more advanced than lists. They do, however, know quite a few list functions.
I should also point out that the students are quite gifted mathematically, many of them have competed and placed in international math olympiads. So this assignment is not beyond the scope of
their intelligence, perhaps only beyond the scope of their python knowledge.
Last night I had a Eureka! moment. I have not implemented it yet, but will do so over the course of the weekend and then post my results here. It may be somewhat crude, but I think it will get the job done.
Sorry it took me this long to respond, my internet cap was reached and I had to wait until the 1st for it to reset. Which reminds me, happy Spring everyone (for those of you in the Southern Hempisphere).
Thanks again for your contributions. I will choose the top answer after the weekend.
Regards!
A program that finds all solutions can be expressed elegantly in functional style.
Partitions
First, write a function that partitions your string in every possible way. (The following implementation is based on http://code.activestate.com/recipes/576795/.) Example:
def partitions(iterable):
'Returns a list of all partitions of the parameter.'
from itertools import chain, combinations
s = iterable if hasattr(iterable, '__getslice__') else tuple(iterable)
n = len(s)
first, middle, last = [0], range(1, n), [n]
return [map(s.__getslice__, chain(first, div), chain(div, last))
for i in range(n) for div in combinations(middle, i)]
Predicate
Now, you'll need to filter the list to find those partitions that add to the desired value. So write a little function to test whether a partition satisfies this criterion:
def pred(target):
'Returns a function that returns True iff the numbers in the partition sum to iTarget.'
return lambda partition: target == sum(map(int, partition))
Main program
Finally, write your main program:
strInput = '2415043'
iTarget = 289
# Run through the list of partitions and find partitions that satisfy pred
print filter(pred(iTarget), partitions(strInput))
Note that the result is calculated in a single line of code.
Result: [['241', '5', '043'], ['241', '5', '0', '43']]
Recursion isn't the best tool for the job anyways. itertools.product is.
Here's how I search it:
Imagine the search space as all the binary strings of length l, where l is the length of your string minus one.
Take one of these binary strings
Write the numbers in the binary string in between the numbers of your search string.
2 4 1 5 0 4 3
1 0 1 0 1 0
Turn the 1's into commas and the 0's into nothing.
2,4 1,5 0,4 3
Add it all up.
2,4 1,5 0,4 3 = 136
Is it 289? Nope. Try again with a different binary string.
2 4 1 5 0 4 3
1 0 1 0 1 1
You get the idea.
Onto the code!
import itertools
strInput = '2415043'
intInput = map(int,strInput)
correctOutput = 289
# Somewhat inelegant, but what the heck
JOIN = 0
COMMA = 1
for combo in itertools.product((JOIN, COMMA), repeat = len(strInput) - 1):
solution = []
# The first element is ALWAYS a new one.
for command, character in zip((COMMA,) + combo, intInput):
if command == JOIN:
# Append the new digit to the end of the most recent entry
newValue = (solution[-1] * 10) + character
solution[-1] = newValue
elif command == COMMA:
# Create a new entry
solution.append(character)
else:
# Should never happen
raise Exception("Invalid command code: " + command)
if sum(solution) == correctOutput:
print solution
EDIT:
agf posted another version of the code. It concatenates the string instead of my somewhat hacky multiply by 10 and add approach. Also, it uses true and false instead of my JOIN and COMMA constants. I'd say the two approaches are equally good, but of course I am biased. :)
import itertools
strInput = '2415043'
correctOutput = 289
for combo in itertools.product((True, False), repeat = len(strInput) - 1):
solution = []
for command, character in zip((False,) + combo, strInput):
if command:
solution[-1] += character
else:
solution.append(character)
solution = [int(x) for x in solution]
if sum(solution) == correctOutput:
print solution
To expand on pst's hint, instead of just using the call stack as recursion does, you can create an explicit stack and use it to implement a recursive algorithm without actually calling anything recursively. The details are left as an exercise for the student ;)

Categories