How to collect results of recursive backtracking? - python

I'm teaching myself recursive backtracking. For a dice summing problem I can't figure out how to elegantly collect the results.
For reference here's my code that just prints any dice roll which meets the criteria. Ideally I want to change it so instead of printing the output, I can build up a list of those chosen dice and return it.
Below here is code that does not do what I want
def dice_sum(num_dice: int, target_sum: int) -> None:
dice_sum_helper(num_dice, target_sum, [])
def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> None:
if num_dice == 0 and sum(chosen) == target_sum:
print(chosen)
elif num_dice == 0:
pass
else:
for i in range(1, 7):
chosen.append(i)
dice_sum_helper(num_dice - 1, target_sum, chosen)
chosen.pop()
Instead I want it to do something like this
from typing import List
DiceResult = List[List[int]]
def dice_sum(num_dice: int, target_sum: int) -> DiceResult:
return dice_sum_helper(num_dice, target_sum, [])
def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> DiceResult:
if num_dice == 0 and sum(chosen) == target_sum:
# Return the value that meets the constraints
return chosen
elif num_dice == 0:
pass
else:
for i in range(1, 7):
chosen.append(i)
# Return the result of my recursive call and build the list of lists?
result = dice_sum_helper(num_dice - 1, target_sum, chosen)
return result.append(result)
# End of that logic
chosen.pop()
I'm looking more for the theory or pattern to use than the exact code. I can't quite get the code to collect and append each result without using an external list working.

You can pass a 'results' list in which to store the results:
from typing import List
DiceResult = List[List[int]]
def dice_sum(num_dice: int, target_sum: int) -> DiceResult:
results = []
dice_sum_helper(num_dice, target_sum, [], results)
return results
def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int], results: DiceResult):
if num_dice == 0 and sum(chosen) == target_sum:
# Store the value that meets the constraints
results.append(chosen.copy())
elif num_dice == 0:
pass
else:
for i in range(1, 7):
chosen.append(i)
dice_sum_helper(num_dice - 1, target_sum, chosen, results)
chosen.pop()
Note this will be returning many duplicates, if ordering does not matter. You may want to investigate changing this to calculate a smaller target sum each recursion, and memoizing that in some way so it save on a lot of work. See also e.g. Calculate the number of ways to roll a certain number

You could utilize yield and yield from to return results from your functions:
from typing import List
def dice_sum(num_dice: int, target_sum: int) -> None:
yield from dice_sum_helper(num_dice, target_sum, [])
def dice_sum_helper(num_dice: int, target_sum: int, chosen: List[int]) -> None:
if num_dice == 0 and sum(chosen) == target_sum:
yield chosen[:]
elif num_dice == 0:
pass
else:
for i in range(1, 7):
chosen.append(i)
yield from dice_sum_helper(num_dice - 1, target_sum, chosen)
chosen.pop()
# you can store the results e.g. to list:
# results = list(dice_sum(3, 12))
for dices in dice_sum(3, 12):
for d in dices:
print('{: ^4}'.format(d), end='|')
print()
Prints:
1 | 5 | 6 |
1 | 6 | 5 |
2 | 4 | 6 |
2 | 5 | 5 |
2 | 6 | 4 |
3 | 3 | 6 |
3 | 4 | 5 |
3 | 5 | 4 |
3 | 6 | 3 |
4 | 2 | 6 |
4 | 3 | 5 |
4 | 4 | 4 |
4 | 5 | 3 |
4 | 6 | 2 |
5 | 1 | 6 |
5 | 2 | 5 |
5 | 3 | 4 |
5 | 4 | 3 |
5 | 5 | 2 |
5 | 6 | 1 |
6 | 1 | 5 |
6 | 2 | 4 |
6 | 3 | 3 |
6 | 4 | 2 |
6 | 5 | 1 |

For the stated goal of elegance, I would go with a simpler design without a helper function nor passing extra arguments:
def dice_sum(num_dice, target_sum):
solutions = []
for i in range(1, 7):
if target_sum < i:
continue
if target_sum == i:
if num_dice == 1:
solutions.append([i])
continue
for solution in dice_sum(num_dice - 1, target_sum - i):
solutions.append([i] + solution)
return solutions
print(dice_sum(3, 5))
OUTPUT
> python3 test.py
[[1, 1, 3], [1, 2, 2], [1, 3, 1], [2, 1, 2], [2, 2, 1], [3, 1, 1]]
>
For added efficiency, you could add:
if target_sum - i < num_dice - 1:
continue
before the inner for loop to avoid the recursion if there's no hope.

A recursive generator with early abort conditions, so you don't get duplicate solutions, with regards to the order of die rolls. I.e. I assume that your dice are not identifiable and therefor the order of the numbers shouldn't matter.
def dice_sum (num_dice, target_sum, start=1):
if num_dice == 1 and 0 < target_sum < 7:
yield (target_sum, )
return
if num_dice * 6 < target_sum or num_dice > target_sum:
return
for i in xrange(start, 7):
for option in dice_sum(num_dice - 1, target_sum - i, i):
if i > option[0]:
return
yield (i, ) + option
>>> tuple(dice_sum(3, 12))
((1, 5, 6), (2, 4, 6), (2, 5, 5), (3, 3, 6), (3, 4, 5), (4, 4, 4))

Related

How should I solve logic error in timestamp using Python?

I have written a code to calculate a, b, and c. They were initialized at 0.
This is my input file
-------------------------------------------------------------
| Line | Time | Command | Data |
-------------------------------------------------------------
| 1 | 0015 | ACTIVE | |
| 2 | 0030 | WRITING | |
| 3 | 0100 | WRITING_A | |
| 4 | 0115 | PRECHARGE | |
| 5 | 0120 | REFRESH | |
| 6 | 0150 | ACTIVE | |
| 7 | 0200 | WRITING | |
| 8 | 0314 | PRECHARGE | |
| 9 | 0318 | ACTIVE | |
| 10 | 0345 | WRITING_A | |
| 11 | 0430 | WRITING_A | |
| 12 | 0447 | WRITING | |
| 13 | 0503 | WRITING | |
and the timestamps and commands are used to process the calculation for a, b, and c.
import re
count = {}
timestamps = {}
with open ("page_stats.txt", "r") as f:
for line in f:
m = re.split(r"\s*\|\s*", line)
if len(m) > 3 and re.match(r"\d+", m[1]):
count[m[3]] = count[m[3]] + 1 if m[3] in count else 1
#print(m[2])
if m[3] in timestamps:
timestamps[m[3]].append(m[2])
#print(m[3], m[2])
else:
timestamps[m[3]] = [m[2]]
#print(m[3], m[2])
a = b = c = 0
for key in count:
print("%-10s: %2d, %s" % (key, count[key], timestamps[key]))
if timestamps["ACTIVE"] > timestamps["PRECHARGE"]: #line causing logic error
a = a + 1
print(a)
Before getting into the calculation, I assign the timestamps with respect to the commands. This is the output for this section.
ACTIVE : 3, ['0015', '0150', '0318']
WRITING : 4, ['0030', '0200', '0447', '0503']
WRITING_A : 3, ['0100', '0345', '0430']
PRECHARGE : 2, ['0115', '0314']
REFRESH : 1, ['0120']
To get a, the timestamps of ACTIVE must be greater than PRECHARGE and WRITING must be greater than ACTIVE. (Line 4, 6, 7 will contribute to the first a and Line 8, 9, and 12 contributes to the second a)
To get b, the timestamps of WRITING must be greater than ACTIVE. For the lines that contribute to a such as Line 4, 6, 7, 8, 9, and 12, they cannot be used to calculate b. So, Line 1 and 2 contribute to b.
To get c, the rest of the unused lines containing WRITING will contribute to c.
The expected output:
a = 2
b = 1
c = 1
However, in my code, when I print a, it displays 0, which shows the logic has some error. Any suggestion to amend my code to achieve the goal? I have tried for a few days and the problem is not solved yet.
I made a function that will return the commands in order that match a pattern with gaps allowed.
I also made a more compact version of your file reading.
There is probably a better version to divide the list into two parts, the problem was to only allow elements in that match the whole pattern. In this one I iterate over the elements twice.
import re
commands = list()
with open ("page_stats.txt", "r") as f:
for line in f:
m = re.split(r"\s*\|\s*", line)
if len(m) > 3 and re.match(r"\d+", m[1]):
_, line, time, command, data, _ = m
commands.append((line,time,command))
def search_pattern(pattern, iterable, key=None):
iter = 0
count = 0
length = len(pattern)
results = []
sentinel = object()
for elem in iterable:
original_elem = elem
if key is not None:
elem = key(elem)
if elem == pattern[iter]:
iter += 1
results.append((original_elem,sentinel))
if iter >= length:
iter = iter % length
count += length
else:
results.append((sentinel,original_elem))
matching = []
nonmatching = []
for res in results:
first,second = res
if count > 0:
if second is sentinel:
matching.append(first)
count -= 1
elif first is sentinel:
nonmatching.append(second)
else:
value = first if second is sentinel else second
nonmatching.append(value)
return matching, nonmatching
pattern_a = ['PRECHARGE','ACTIVE','WRITING']
pattern_b = ['ACTIVE','WRITING']
pattern_c = ['WRITING']
matching, nonmatching = search_pattern(pattern_a, commands, key=lambda t: t[2])
a = len(matching)//len(pattern_a)
matching, nonmatching = search_pattern(pattern_b, nonmatching, key=lambda t: t[2])
b = len(matching)//len(pattern_b)
matching, nonmatching = search_pattern(pattern_c, nonmatching, key=lambda t: t[2])
c = len(matching)//len(pattern_c)
print(f'{a=}')
print(f'{b=}')
print(f'{c=}')
Output:
a=2
b=1
c=1

Python: Efficient lookup by interval

I have a large lookup table where the key is an interval:
| min | max | value |
|-----|-----|---------|
| 0 | 3 | "Hello" |
| 4 | 5 | "World" |
| 6 | 6 | "!" |
| ... | ... | ... |
The goal is to create a lookup structure my_lookup that returns a value for each integer, depending on the range the integer is in.
For example: 2 -> "Hello", 3 -> "Hello", 4 -> "World".
Here is an implementation that does what I want:
d = {
(0, 3): "Hello",
(4, 5): "World",
(6, 6): "!"
}
def my_lookup(i: int) -> str:
for key, value in d.items():
if key[0] <= i <= key[1]:
return value
But looping over all entries seems inefficient (the actual lookup table contains 400.000 lines). Is there a faster way?
If your intervals are sorted (in ascending order), you can use bisect module (doc). The search is O(log n) instead of O(n):
min_lst = [0, 4, 6]
max_lst = [3, 5, 6]
values = ['Hello', 'World', '!']
import bisect
val = 2
idx = bisect.bisect_left(max_lst, val)
if idx < len(max_lst) and min_lst[idx] <= val <= max_lst[idx]:
print('Value found ->', values[idx])
else:
print('Value not found')
Prints:
Value found -> Hello

python Reverse Collatz Conjecture

What the program should do is take steps and a number and than output you how many unique sequences there are with exactly x steps to create number.
Does someone know how I can save some memory - as I should make this work for pretty huge numbers within a 4 second limit.
def IsaacRule(steps, number):
if number in IsaacRule.numbers:
return 0
else:
IsaacRule.numbers.add(number)
if steps == 0:
return 1
counter = 0
if ((number - 1) / 3) % 2 == 1:
counter += IsaacRule(steps-1, (number - 1) / 3)
if (number * 2) % 2 == 0:
counter += IsaacRule(steps-1, number*2)
return counter
IsaacRule.numbers = set()
print(IsaacRule(6, 2))
If someone knows a version with memoization I would be thankful, right now it works, but there is still room for improvement.
Baseline: IsaacRule(50, 2) takes 6.96s
0) Use the LRU Cache
This made the code take longer, and gave a different final result
1) Eliminate the if condition: (number * 2) % 2 == 0 to True
IsaacRule(50, 2) takes 0.679s. Thanks Pm2Ring for this one.
2) Simplify ((number - 1) / 3) % 2 == 1 to number % 6 == 4 and use floor division where possible:
IsaacRule(50, 2) takes 0.499s
Truth table:
| n | n-1 | (n-1)/3 | (n-1)/3 % 2 | ((n-1)/3)%2 == 1 |
|---|-----|---------|-------------|------------------|
| 1 | 0 | 0.00 | 0.00 | FALSE |
| 2 | 1 | 0.33 | 0.33 | FALSE |
| 3 | 2 | 0.67 | 0.67 | FALSE |
| 4 | 3 | 1.00 | 1.00 | TRUE |
| 5 | 4 | 1.33 | 1.33 | FALSE |
| 6 | 5 | 1.67 | 1.67 | FALSE |
| 7 | 6 | 2.00 | 0.00 | FALSE |
Code:
def IsaacRule(steps, number):
if number in IsaacRule.numbers:
return 0
else:
IsaacRule.numbers.add(number)
if steps == 0:
return 1
counter = 0
if number % 6 == 4:
counter += IsaacRule(steps-1, (number - 1) // 3)
counter += IsaacRule(steps-1, number*2)
return counter
3) Rewrite code using sets
IsaacRule(50, 2) takes 0.381s
This lets us take advantage of any optimizations made for sets. Basically I do a breadth first search here.
4) Break the cycle so we can skip keeping track of previous states.
IsaacRule(50, 2) takes 0.256s
We just need to add a check that number != 1 to break the only known cycle. This gives a speed up, but you need to add a special case if you start from 1. Thanks Paul for suggesting this!
START = 2
STEPS = 50
# Special case since we broke the cycle
if START == 1:
START = 2
STEPS -= 1
current_candidates = {START} # set of states that can be reached in `step` steps
for step in range(STEPS):
# Get all states that can be reached from current_candidates
next_candidates = set(number * 2 for number in current_candidates if number != 1) | set((number - 1) // 3 for number in current_candidates if number % 6 == 4)
# Next step of BFS
current_candidates = next_candidates
print(len(next_candidates))

Summarize data in a list in python

In python i need to summarize data in count_list this way (like a histogram):
"""
number | occurence
0 | *
1 | **
2 | ***
3 | **
4 | **
5 | *
6 | *
7 | **
8 | ***
9 | *
10 | **
"""
But instead I get this wrong output:
"""
number | occurence
0 |
1 | **
2 |
3 |
4 |
5 |
6 | **
7 |
8 |
9 |
10 | **
"""
Here is my code:
import random
random_list = []
list_length = 20
while len(random_list) < list_length:
random_list.append(random.randint(0,10))
count_list = [0] * 11
index = 0
while index < len(random_list):
number = random_list[index]
count_list[number] = count_list[number] + 1
index = index + 1
def summerizer():
index = 0
print count_list
print '"'*3
print 'number | occurrence'
while index < len(count_list):
print '%s' %' '*(7),
print index,#the problem is here
print ' | ',#and here
print '%s' %'*'*(count_list[index])
index += 1
print '%s'%'"'*3
summerizer()
This method uses collections.Counter:
from collections import Counter
import random
random_list = []
list_length = 20
while len(random_list) < list_length:
random_list.append(random.randint(0,10))
c = Counter(random_list)
print('number | occurrence')
def summerizer(dic):
for v,d in dic.items():
print(v, '|', '%s'%'*'*c[v])
summerizer(dic)
Yes i have found the problem
It is from the ide itself !!!
This was a quiz in a course on UDACITY android application and the embedded compiler inside it make this wrong answer..
Same code i tried now from pydroid application on Android also made the answer that i need without any change
Thanks for trying to help all of you
`import random
random_list = []
list_length = 20
while len(random_list) < list_length:
random_list.append(random.randint(0,10))
count_list = [0] * 11
index = 0
while index < len(random_list):
number = random_list[index]
count_list[number] = count_list[number] + 1
index = index + 1
def summerizer():
index = 0
print count_list
print '"'*3
print 'number | occurrence'
while index < len(count_list):
print '%s' %' '*(7),
print index,
print ' | ',
print '%s' %'*'*(count_list[index])
index += 1
print '%s'%'"'*3
summerizer()`
Try this
import random
random_list = []
list_length = 20
while len(random_list) < list_length:
random_list.append(random.randint(0,10))
dic={}
for i in random_list:
dic[i]=dic.get(i,0)+1
print 'number | occurrence'
for i in range(0,11):
if(i not in dic):
print i,"|",'%s' %'*'*(0)
else:
print i,"|",'%s' %'*'*(dic[i])
Out put
[9, 8, 4, 2, 5, 4, 8, 3, 5, 6, 9, 5, 3, 8, 6, 2, 10, 10, 8, 9]
number | occurrence
0 |
1 |
2 | **
3 | **
4 | **
5 | ***
6 | **
7 |
8 | ****
9 | ***
10 | **

Counting choice streaks in Python

I have a dataset that looks like the following:
Subject | Session | Trial | Choice
--------+---------+-------+-------
1 | 1 | 1 | A
1 | 1 | 2 | B
1 | 1 | 3 | B
1 | 1 | 4 | B
1 | 1 | 5 | B
1 | 1 | 6 | A
2 | 1 | 1 | A
2 | 1 | 2 | A
2 | 1 | 3 | A
I would like to use a Python script to generate the following table:
Subject | Session | streak_count
--------+---------+-------------
1 | 1 | 3
2 | 1 | 1
Where streak_count is a count of the total number of choice streaks made by a given subject during a given session, and a streak is any number of choices of one particular item in a row (>0).
I've tried using some of the suggestions to similar questions here, but I'm having trouble figuring out how to count these instances, rather than measure their length, etc., which seem to be more common queries.
def count():
love = []
love1 = []
streak = -1
k = 0
session = 1
subject = raw_input("What is your subject? ")
trials = raw_input("How many trials do you wish to do? ")
trial = 0
for i in range(int(trials)):
choice = raw_input("What was the choice? ")
love.append(choice)
love1.append(choice)
trial += 1
print subject, trial, choice
if love[i] == love1[i-1]:
streak += 1
print subject, session, streak
This may be what you want it takes in how many trials you wish to do and whatever your subject is and if there is a streak it adds one. The reason streak starts at -1 is because when you put your first answer it adds one because of the negative index going back to its self.
I think this is what you are asking for;
import itertools
data = [
[1, 1, 1, 'A'],
[1, 1, 2, 'B'],
[1, 1, 3, 'B'],
[1, 1, 4, 'B'],
[1, 1, 5, 'B'],
[1, 1, 6, 'A'],
[2, 1, 1, 'A'],
[2, 1, 2, 'A'],
[2, 1, 3, 'A']
]
grouped = itertools.groupby(data, lambda x: x[0])
results = dict()
this, last = None, None
for key, group in grouped:
results[key] = 0
for c, d in enumerate(group):
this = d
streak = c == 0 or this[3] != last[3]]
if streak:
results[key] += 1
last = this
print results
This yields;
{1: 3, 2: 1}

Categories