I am working on a project where I generate every possible tic-tac-toe array. As a proof of concept, I am working on code to fill an array with 9 subarrays. Each subarray will have two values, the first one being 0 or 1 (for x and o respectively), and the second one being from 1 to 9 (representing when it was placed).
An example of an array I would like to get out would look like:
[[0, 0], [1, 1], [0, 2], [1, 3], [0, 4], [1, 5], [0, 6], [1, 7], [0, 8]]
I have already written code, using 9 for loops, each one nested in the one above, which gives me the desired results (every possible array, and each one unique). But I am trying to write code, using recursion, and avoiding writing tons of nested loops.
When I run the code below, it is only able to generate the array above, and cannot create other combinations. My code is below:
print("running...")
allGames = []
checkCurrentGame = [5, 5, 5, 5, 5, 5, 5, 5, 5]
stepsDown = 0
def cleanGame(move, currentGame):
for j in range(9):
if (currentGame[j][1] >= move):
currentGame[j] = [5, 0]
def completeMove(moveNumber, currentGame):
global stepsDown
stepsDown = stepsDown + 1
for i in range(9):
cleanGame(moveNumber, currentGame)
if (currentGame[i][0] == 5):
currentGame[i][0] = i % 2
currentGame[i][1] = moveNumber
allGames.append(currentGame)
break
if (stepsDown < 9):
generateGame(currentGame)
def generateGame(currentGame):
for i in range(9):
completeMove(i, currentGame)
generateGame([[5, 0], [5, 0], [5, 0], [5, 0], [5, 0], [5, 0], [5, 0], [5, 0], [5, 0]])
for x in range(len(allGames)):
print(allGames[x])
If I understand your question correctly this should do, however this is not recursion -
import itertools
[zip(p, range(0, 9)) for p in itertools.product([0, 1], repeat=9)]
The code first generates a board (9 0's or 1's) -
itertools.product([0, 1], repeat=9)
and then add the index data to it.
I recommend having a look in itertools
This is fun. Here's a method that plays the game, branching off each possible move (or at any rate, that's the intention).
Although my preference is usually to concat and return a pure result, to allow for an early exit and inspection, this recursion accumulates in a global variable.
import copy
import json
import time
s = set()
rs = []
def f():
"""
111
111000
111000000
1001001
10010010
100100100
1010100
100010001
"""
wins = [0007,0070,0700,0111,0222,0444,0124,0421]
def winner(board):
# check xs or os individual board for a win
for i in wins:
if not (i ^ (board & i)):
return True
return False
# xs board, os board, move-number, game
def g(xs, os, move, result):
# allow for early exit
global rs
if (len(rs) > 20000):
return
# base case, win or draw
if winner(xs) or winner(os) or move == 9:
#print "{0:b}".format(xs)
#print "{0:b}".format(os)
#print (result, xs, os)
#print
# confirm we're not duplicating results
enc = json.dumps(result)
if enc in s:
print "Duplicate!"
print result
s.add(enc)
# accumulate results
rs.append((result, xs, os))
return
board = xs | os
for i in xrange(9):
# candidate move
m = 1 << i
# valid move
if not (m & board):
_result = copy.deepcopy(result)
# 'O' plays on odd numbered moves
if move & 1:
_result[i] = [1, move]
g(xs, os | m, move + 1, _result)
# 'X' plays on even numbered moves
else:
_result[i] = [0, move]
g(xs | m, os, move + 1, _result)
# start the recursion
g(0, 0, 0, [[-1, -1]] * 9)
start_time = time.time()
f()
print("--- %s seconds ---" % (time.time() - start_time))
Output:
"""
rs[2002]
=> ([[0, 0], [1, 1], [-1, -1],
[-1, -1], [1, 5], [0, 2],
[0, 4], [1, 3], [-1, -1]], 97, 146)
x--
---
---
xo-
---
---
xo-
--x
---
xo-
--x
-o-
xo-
--x
xo-
xo-
-ox
xo-
"""
I recommend you watch this video on youtube. The professor teaches really nicely about recursion. I think it will benefit for you more, then getting a working code. He uses sudoku as an example, but both games are just 2D arrays.
If you watched the video, you will know how to modify this example to better suit your needs.
A pseudo code:
def place(who, when, where, table):
if (None, None) not in table: # the table is full
print(table)
return
if table[where] not (None, None): # this cell is already marked
return
table[where] (who, when) # can place our mark on the cell
# make the recursive calls
for i in range(9):
place(who.opponent, when+1, i, table)
for cell in range(9):
empty = [(None, None) for _ in range(9)]
place(firstplayer, 0, cell, empty)
Related
My input data is
mylist = [[[1820, 57, 1920, 237], [2, 3, 3]], [[1830, 90, 1920, 240], [2, 3, 4]], [[1450, 0, 1667, 197], [1, 3, 6]], [[1835, 90, 1920, 240], [0, 5, 7]],[[1452, 0, 1667, 197], [9, 9, 9]]]
I have a group of rectangle data with the first sublist containing its coordinates and the second one containing rgb data. I would like to group the second sublist if they overlap. Below is the code to check whether rectangle overlap.
def isRectangleOverlap(self, R1, R2):
if (R1[0] >= R2[2]) or (R1[2] <= R2[0]) or (R1[3] <= R2[1]) or (R1[1] >= R2[3]):
return False
else:
return True
And I have the following code:
for i in range(len(mylist)):
for j in range(i + 1, len(mylist)):
if isRectangleOverlap(mylist[i][0], mylist[j][0]):
....
if they pass the condition, the second sublist will be grouped together.
My expected output:
[[[2, 3, 3], [2, 3, 4], [0, 5, 7]], [[1, 3, 6], [9, 9, 9]]]
Do you have any suggestions?
UPDATE: I solved the problem with answers below but I have another question that is it possible group RGB data of retangles that cluster in a small area like this ?
file1
file2
It is an interesting question, because there are a few curve balls. For one, you are not looking for overlapping rectangles, but for clusters of rectangles. Your desired result implies that. I tried to be explicit in the code below:
mylist = [[[1820, 57, 1920, 237], [2, 3, 3]], [[1830, 90, 1920, 240], [2, 3, 4]], [[1450, 0, 1667, 197], [1, 3, 6]], [[1835, 90, 1920, 240], [0, 5, 7]],[[1452, 0, 1667, 197], [9, 9, 9]]]
# R= [Ly, Lx, Ry, Rx] = (LowerLeft-Y, LowerLeft-X, UpperRight-Y, UpperRight-X)
clusters = {}
#will hold end results, clustering overlapping rectangels together by key in mylist
#{
# 1: [1,2]
#}
def isRectangleOverlap(R1: list, R2: list):
if (R1[0] >= R2[2]) or (R1[2] <= R2[0]) or (R1[3] <= R2[1]) or (R1[1] >= R2[3]):
return False
else:
return True
def hasAlreadyOverlapped(key: int):
for k, v in clusters.items():
if key in v:
return k
return None
#itterate over every set of Rectangle,RGB combination
for i in range(len(mylist)):
cur_rect = mylist[i][0]
current_cluster = None
#check if current rectangle has already overlapped with previous rectangles,
#and if so, any new overlappings with current rectangle will be added to that cluster
#otherwise a new cluster will be created with current index i
current_cluster = hasAlreadyOverlapped(i) or i+1
#if this is a new cluster, then initiate it with current rectange in it
if not (current_cluster in clusters):
clusters[current_cluster] = [i]
#check remaining rectangles in mylist for overlapping, and add them to current cluster
#unless of course, if this is the last item.
if i < len(mylist)-1:
for j in range(i+1, len(mylist)):
if isRectangleOverlap(cur_rect, mylist[j][0]):
#this rectangle could already overlapped with a previouws processed rectangle, so we
#need to check for that.
if not (j in clusters[current_cluster]):
clusters[current_cluster].append(j)
print(clusters)
#> {1: [0, 1, 3], 3: [2, 4]}
result = []
for _, rectangles in clusters.items():
c = []
for i in rectangles:
c.append(mylist[i][1])
result.append(c)
print(result)
#> [[[2, 3, 3], [2, 3, 4], [0, 5, 7]], [[1, 3, 6], [9, 9, 9]]]
Note that there is one problem here that I haven't accounted for. Let's say, rect0 and rect8 overlap, and rect3 and rect4 overlap. The above code would make clusters. But what if both clusters overlap with rect9 (rect9 stretching both over rect8 and rect3)?
I will have another thought about it later today, fun question!
UPDATE
I had another moment to think about it and I came up with a better solution, using recursion. The above solution, as explained, iterates over rectangles and sees if there is any overlap (creating clusters). However, two independent clusters cannot be merged later on when encountering a bridging rectangle. To counter this effect, the logic would be that you explore a cluster when you find one, and after you have exhausted the current cluster of overlapping rectangles, you move on to new rectangles that form new clusters.
See the code below, I have amended a bit (created a class for example for Rectangles) for readability.
class Rectangle():
"""
Making the objects in your list a bit more readable
"""
def __init__(self, lleft_y: int, lleft_x: int, uright_y:int, uright_x:int, rgb: list) -> None:
self.lleft_y = lleft_y
self.lleft_x = lleft_x
self.uright_y = uright_y
self.uright_x = uright_x
self.rgb = rgb
mylist = [
Rectangle(1820, 57, 1920, 237, [2, 3, 3]),
Rectangle(1830, 90, 1920, 240, [2, 3, 4]),
Rectangle(1450, 0, 1667, 197, [1, 3, 6]),
Rectangle(1835, 90, 1920, 240, [0, 5, 7]),
Rectangle(1452, 0, 1667, 197, [9, 9, 9])]
# This list will hold all clusters of overlapping rectangles by their index
clusters: list[list[int]] = []
# This list will keep track of all already processed rectangles by their index
processed_rectangles: list[int] = []
def doRectanglesOverlap(R1: Rectangle, R2: Rectangle):
# if (R1 above of R2) or (R1 below of R2) or (R1 left of R2) or (R1 right of R2)
if (R1.lleft_y >= R2.uright_y) or (R1.uright_y <= R2.lleft_y) or (R1.uright_x <= R2.lleft_x) or (R1.lleft_x >= R2.uright_x):
return False
else:
return True
def find_overlapping(index: int) -> list[int]:
# First find all rectangles directly overlapping with current rectangle
result: list[int] = []
for j in range(len(mylist)):
# If this rectangle was already processed because it was part of another cluster, just skip it
if j in processed_rectangles:
continue
if doRectanglesOverlap(mylist[index], mylist[j]):
processed_rectangles.append(j)
result.append(j)
# Now, for each found overlapping rectangle, we want to find the overlapping rectangles as well,
# effectively exploring the cluster outward. Recursive functions can be handy!
for r in result:
result += find_overlapping(r)
return result
# Looping over all rectangles
for i in range(len(mylist)):
# If this rectangle was already processed because it was part of another cluster, just skip it
if i in processed_rectangles:
continue
processed_rectangles.append(i)
# If it was not part of another earlier processed cluster, it is a new cluster. Let's add it.
current_cluster = [i]
# Now, we are going to loop over all remaining rectangles that haven't been processed yet, and add it to the current cluster
other_rectangles = find_overlapping(i)
# Due to the recursion, `other_rectangles` now hold the entire rest of the cluster.
current_cluster = current_cluster + (other_rectangles)
clusters.append(current_cluster)
print(clusters)
#> [[0, 1, 3], [2, 4]]
result = []
for ix, cluster in enumerate(clusters):
result.append([])
for i in cluster:
result[ix].append(mylist[i].rgb)
print(result)
#> [[[2, 3, 3], [2, 3, 4], [0, 5, 7]], [[1, 3, 6], [9, 9, 9]]]
A simple version that sets found items to an empty list. This is not very efficient since the list to compare to doesn't get shorter.
import copy
mylist = [[[1820, 57, 1920, 237], [2, 3, 3]], [[1830, 90, 1920, 240], [2, 3, 4]], [[1450, 0, 1667, 197], [1, 3, 6]], [[1835, 90, 1920, 240], [0, 5, 7]],[[1452, 0, 1667, 197], [9, 9, 9]]]
def isRectangleOverlap(R1, R2):
if (not R1) or (not R2):
return False
if (R1[0] >= R2[2]) or (R1[2] <= R2[0]) or (R1[3] <= R2[1]) or (R1[1] >= R2[3]):
return False
else:
return True
mylist2 = copy.deepcopy(mylist)
grouped = []
for i in range(len(mylist)):
grouped.append([])
for j in range(i + 1, len(mylist)):
if isRectangleOverlap(mylist[i][0], mylist2[j][0]):
if not grouped[i]: # include the i'th sublist
grouped[i].append(mylist[i][1])
grouped[i].append(mylist[j][1])
mylist2[j] = [[], []]
grouped = [sublst for sublst in grouped if sublst]
print(grouped)
A bit more complicated way that removes the items from the list so it doesn't check them again:
mylist2 = copy.deepcopy(mylist)
grouped = []
for i in range(len(mylist)):
grouped.append([])
inds_2_remove = []
for j in range(len(mylist2)):
if mylist[i] == mylist2[j]:
inds_2_remove.append(j)
continue
if isRectangleOverlap(mylist[i][0], mylist2[j][0]):
if not grouped[i]:
grouped[i].append(mylist[i][1])
grouped[i].append(mylist2[j][1])
inds_2_remove.append(j)
for ind_2_remove in reversed(inds_2_remove):
print(ind_2_remove)
del mylist2[ind_2_remove]
grouped = [sublst for sublst in grouped if sublst]
print(grouped)
I am currently taking a computer science course called Intro to Computer Science where I am learning Python (Python 3) and I doing an assignment having to do with flipping 2D arrays. I have written some code for my assignment already and I already have it running sequentially as it tells me to in the instructions but I am having some problems flipping it each time. What I want to know is: How do I call my printArray function in both my flipHorizontal and flipVertical functions in order to flip my code each time it is run? I have the assignment instructions and an instructor's comment on my assignment.
Here are the instructions:
Here is my code:
def printArray(N):
for r in N:
for c in r:
print(c,end = " ")
print()
print()
def flipHorizontal(N):
Array = []
for i in range(0,len(N)):
pos = []
for j in range(0,len(N[0])):
pos.append(N[i][len(N[0])-j-1])
Array.append(pos)
return Array
def flipVertical(N):
newArray = []
for i in range(0,len(N)):
newArray.append(N[len(N)-i-1])
return newArray
Array1 = [[0, 2, 0, 0,0], [0, 2, 0, 0,0], [0, 2, 2, 0,0], [0, 2, 0, 2,0],[0, 2, 0, 0,2]]
printArray(Array1)
flipedHor = flipHorizontal(Array1)
printArray(flipedHor)
flipedVer=flipVertical(Array1)
printArray(flipedVer)
Here is the comment:
If I am reading correct , you just need to add print statement in your defined functions.
def printArray(N):
for r in N:
for c in r:
print(c,end = " ")
print()
print()
def flipHorizontal(N):
Array = []
for i in range(0,len(N)):
pos = []
for j in range(0,len(N[0])):
pos.append(N[i][len(N[0])-j-1])
Array.append(pos)
printArray(Array)
return Array
def flipVertical(N):
newArray = []
for i in range(0,len(N)):
newArray.append(N[len(N)-i-1])
printArray(newArray)
return newArray
Array1 = [[0, 2, 0, 0,0], [0, 2, 0, 0,0], [0, 2, 2, 0,0], [0, 2, 0, 2,0],[0, 2, 0, 0,2]]
printArray(Array1)
flipedHor = flipHorizontal(Array1)
flipedVer=flipVertical(Array1)
I need a vector that stores the median values of the medians of the main list "v". I have tried something with the following code but I am only able to write some values in the correct way.
v=[1,2,3,4,5,6,7,8,9,10]
final=[]
nfac=0
for j in range (0,4):
nfac=j+1
for k in range (0,nfac):
if k%2==0:
final.append(v[10/2**(nfac)-1])
else:
final.append(v[9-10/2**(nfac)])
The first median in v=[1,2,3,4,5,6,7,8,9,10] is 5
Then I want the medians of the remaining sublists [1,2,3,4] and [6,7,8,9,10]. I.e. 2 and 8 respectively. And so on.
The list "final" must be in the following form:
final=[5,2,8,1,3,6,9,4,7,10]
Please take a note that the task as you defined it is basically equivalent to constructing a binary heap from an array.
Definitely start by defining a helper function for finding the median:
def split_by_median(l):
median_ind = (len(l)-1) // 2
median = l[median_ind]
left = l[:median_ind]
right = l[median_ind+1:] if len(l) > 1 else []
return median, left, right
Following the example you give, you want to process the resulting sublists in a breadth-first manner, so we need a queue to remember the following tasks:
from collections import deque
def construct_heap(v):
lists_to_process = deque([sorted(v)])
nodes = []
while lists_to_process:
head = lists_to_process.popleft()
if len(head) == 0:
continue
median, left, right = split_by_median(head)
nodes.append(median)
lists_to_process.append(left)
lists_to_process.append(right)
return nodes
So calling the function finally:
print(construct_heap([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) # [5, 2, 8, 1, 3, 6, 9, 4, 7, 10]
print(construct_heap([5, 1, 2])) # [2, 1, 5]
print(construct_heap([1, 0, 0.5, -1])) # [0, -1, 0.5, 1]
print(construct_heap([])) # []
I'm trying to generate all possible combinations of a distribution of sorts.
For example, say you have 5 points to spend on 4 categories, but you can only spend a maximum of 2 points on any given category.
In this instance, all possible solutions would be as follows:
[0, 1, 2, 2]
[0, 2, 1, 2]
[0, 2, 2, 1]
[1, 0, 2, 2]
[1, 1, 1, 2]
[1, 1, 2, 1]
[1, 2, 0, 2]
[1, 2, 1, 1]
[1, 2, 2, 0]
[2, 0, 1, 2]
[2, 0, 2, 1]
[2, 1, 0, 2]
[2, 1, 1, 1]
[2, 1, 2, 0]
[2, 2, 0, 1]
[2, 2, 1, 0]
I have successfully been able to make a recursive function that accomplishes this, but for larger numbers of categories it takes extremely long to generate. I have attempted making an iterative function instead in hopes of speeding it up, but I can't seem to get it to account for the category maximums.
Here is my recursive function (count = points, dist = zero-filled array w/ same size as max_allo)
def distribute_recursive(count, max_allo, dist, depth=0):
for ration in range(max(count - sum(max_allo[depth + 1:]), 0), min(count, max_allo[depth]) + 1):
dist[depth] = ration
count -= ration
if depth + 1 < len(dist):
distribute_recursive(count, max_allo, dist, depth + 1)
else:
print(dist)
count += ration
recursion isn't slow
Recursion isn't what's making it slow; consider a better algorithm
def dist (count, limit, points, acc = []):
if count is 0:
if sum (acc) is points:
yield acc
else:
for x in range (limit + 1):
yield from dist (count - 1, limit, points, acc + [x])
You can collect the generated results in a list
print (list (dist (count = 4, limit = 2, points = 5)))
pruning invalid combinations
Above, we use a fixed range of limit + 1, but watch what happens if we're generating a combination with a (eg) limit = 2 and points = 5 ...
[ 2, ... ] # 3 points remaining
[ 2, 2, ... ] # 1 point remaining
At this point, using a fixed range of limit + 1 ([ 0, 1, 2 ]) is silly because we know we only have 1 point remaining to spend. The only remaining options here are 0 or 1...
[ 2, 2, 1 ... ] # 0 points remaining
Above we know we can use an empty range of [ 0 ] because there's no points left to spend. This will prevent us from attempting to validate combinations like
[ 2, 2, 2, ... ] # -1 points remaining
[ 2, 2, 2, 0, ... ] # -1 points remaining
[ 2, 2, 2, 1, ... ] # -2 points remaining
[ 2, 2, 2, 2, ... ] # -3 points remaining
If count was significantly large, this could rule out a huge amount of invalid combinations
[ 2, 2, 2, 2, 2, 2, 2, 2, 2, ... ] # -15 points remaining
To implement this optimization, we could add yet another parameter to our dist function, but at 5 parameters, it would start to look messy. Instead we introduce an auxiliary function to control the loop. Adding our optimization, we trade the fixed range for a dynamic range of min (limit, remaining) + 1. And finally, since we know how many points have been allocated, we no longer need to test the sum of each combination; yet another expensive operation removed from our algorithm
# revision: prune invalid combinations
def dist (count, limit, points):
def loop (count, remaining, acc):
if count is 0:
if remaining is 0:
yield acc
else:
for x in range (min (limit, remaining) + 1):
yield from loop (count - 1, remaining - x, acc + [x])
yield from loop (count, points, [])
benchmarks
In the benchmarks below, the first version of our program is renamed to dist1 and the faster program using a dynamic range dist2. We setup three tests, small, medium, and large
def small (prg):
return list (prg (count = 4, limit = 2, points = 5))
def medium (prg):
return list (prg (count = 8, limit = 3, points = 7))
def large (prg):
return list (prg (count = 16, limit = 5, points = 10))
And now we run the tests, passing each program as an argument. Note for the large test, only 1 pass is done as dist1 takes awhile to generate the result
print (timeit ('small (dist1)', number = 10000, globals = globals ()))
print (timeit ('small (dist2)', number = 10000, globals = globals ()))
print (timeit ('medium (dist1)', number = 100, globals = globals ()))
print (timeit ('medium (dist2)', number = 100, globals = globals ()))
print (timeit ('large (dist1)', number = 1, globals = globals ()))
print (timeit ('large (dist2)', number = 1, globals = globals ()))
The results for the small test show that pruning invalid combinations doesn't make much of a difference. However in the medium and large cases, the difference is dramatic. Our old program takes over 30 minutes for the large set, but just over 1 second using the new program!
dist1 small 0.8512216459494084
dist2 small 0.8610155049245805 (0.98x speed-up)
dist1 medium 6.142372329952195
dist2 medium 0.9355670949444175 (6.57x speed-up)
dist1 large 1933.0877765258774
dist2 large 1.4107366011012346 (1370.26x speed-up)
For frame of reference, the size of each result is printed below
print (len (small (dist2))) # 16 (this is the example in your question)
print (len (medium (dist2))) # 2472
print (len (large (dist2))) # 336336
checking our understanding
In the large benchmark with count = 12 and limit = 5, using our unoptimized program we were iterating through 512, or 244,140,625 possible combinations. Using our optimized program, we skip all invalid combinations resulting in 336,336 valid answers. By analyzing combination count alone, we see a staggering 99.86% of possible combinations are invalid. If analysis of each combination costs an equal amount of time, we can expect our optimized program to perform at a minimum of 725.88x better, due to invalid combination pruning.
In the large benchmark, measured at 1370.26x faster, the optimized program meets our expectations and even goes beyond. The additional speed-up is likely owed to the fact we eliminated the call to sum
huuuuge
To show this technique works for extremely large data sets, consider the huge benchmark. Our program finds 17,321,844 valid combinations amongst 716, or 33,232,930,569,601 possibilities.
In this test, our optimized program prunes 99.99479% of the invalid combinations. Correlating these numbers to the previous data set, we estimate the optimized program runs 1,918,556.16x faster than the unoptimized version.
The theoretical running time of this benchmark using the unoptimized program is 117.60 years. The optimized program finds the answer in just over 1 minute.
def huge (prg):
return list (prg (count = 16, limit = 7, points = 12))
print (timeit ('huge (dist2)', number = 1, globals = globals ()))
# 68.06868170504458
print (len (huge (dist2)))
# 17321844
You can use a generator function for the recursion, while applying additional logic to cut down on the number of recursive calls needed:
def listings(_cat, points, _max, current = []):
if len(current) == _cat:
yield current
else:
for i in range(_max+1):
if sum(current+[i]) <= points:
if sum(current+[i]) == points or len(current+[i]) < _cat:
yield from listings(_cat, points, _max, current+[i])
print(list(listings(4, 5, 2)))
Output:
[[0, 1, 2, 2], [0, 2, 1, 2], [0, 2, 2, 1], [1, 0, 2, 2], [1, 1, 1, 2], [1, 1, 2, 1], [1, 2, 0, 2], [1, 2, 1, 1], [1, 2, 2, 0], [2, 0, 1, 2], [2, 0, 2, 1], [2, 1, 0, 2], [2, 1, 1, 1], [2, 1, 2, 0], [2, 2, 0, 1], [2, 2, 1, 0]]
While it is unclear at around what category size your solution drastically slows down, this solution will run under one second for category sizes up to 24, searching for a total of five points with a maximum slot value of two. Note that for large point and slot values, the number of possible category sizes computed under a second increases:
import time
def timeit(f):
def wrapper(*args):
c = time.time()
_ = f(*args)
return time.time() - c
return wrapper
#timeit
def wrap_calls(category_size:int) -> float:
_ = list(listings(category_size, 5, 2))
benchmark = 0
category_size = 1
while benchmark < 1:
benchmark = wrap_calls(category_size)
category_size += 1
print(category_size)
Output:
24
This code, if you give it a number N, it generates 1234...N or N...4321, but produces the output not as a string, but rather as a number. I wanted to convert the loop in the middle into a list comprehension but ran into trouble trying to do it.
Here is the code that works (written in Python 2.7:
def findTens(N):
# counts number of tens in a number: examples:
# * 100 & 113 would both return 2
# * 9 returns 0
# * 14 returns 1
incrementer = 1
while True:
if N - 10**incrementer < 0:
break
else:
incrementer += 1
if incrementer == 100:
break # debugging condition
return incrementer - 1
def create_seqNum(N, reverse=False, showWork=False, returnDescr=False, divLength=100):
'''create_seqNum() --> iput N, and get back a number built from the sequence of 1234...N
Arguments: reverse=True to get the sequence in revers, showWork=True to see numbers that add up to final,
returnDescr=True to print the answer in a sentence as well as returning it as a number.'''
num = 0
tensIncr = 0
answer = 0
Ntens = findTens(N)
modifier = 0 # modifies counter when increment of 10 occurs
if reverse == True: # create range builder inputs
rstart = 1
rend = N+1
rinc = 1
else:
rstart = N
rend = 0
rinc = -1
for i in range(rstart, rend, rinc):
itens = findTens(i)
num = i * 10**tensIncr
tensIncr += 1 + itens
pad = (Ntens - itens)
if showWork == True:
print(("For %d" + " "*pad + " Add: %d") %(i, num))
answer += num
if showWork == True:
print("#"*divLength)
if showWork == True or returnDescr == True:
print("Answer: %d" %answer)
print("#"*divLength)
return answer
These test cases all work with the above code:
for i in [1, 5, 9, 10, 11, 13, 98, 99, 100, 101, 102, 107, 1012]:
create_seqNum(i, reverse=True, returnDescr=True)
create_seqNum(i, returnDescr=True)
Note too that my attempt to create working list comprehensions broke down both for retiring the loop in the calculation and for making it possible to get the list of numbers it added up as well as the final sum (the "showWork" option). But if no one cracks them both, then the best solution to the calculation that passes all tests will be accepted.
In case it helps - Here is my attempt to convert it to list comprehensions that failed. If anyone can figure this out, looking to learn from your answer and thought others might find the puzzle of this interesting (at least I hope):
def create_seqNum_v2(N, reverse=False, showWork=False, returnDescr=False, divLength=100):
'''create_seqNum() --> iput N, and get back a number built from the sequence of 1234...N
Arguments: reverse=True to get the sequence in revers, showWork=True to see numbers that add up to final,
returnDescr=True to print the answer in a sentence as well as returning it as a number.'''
num = 0
tensIncr = 0
answer = 0
Ntens = findTens(N)
modifier = 0 # modifies counter when increment of 10 occurs
if reverse == True: # create range builder inputs
rstart = 1
rend = N+1
rinc = 1
else:
rstart = N
rend = 0
rinc = -1
workList = [i * 10**((i-1) + findTens(i)) for i in range(rstart, rend, rinc)]
answer = sum(workList)
if showWork == True:
# [ print(("For %d" + " "*(Ntens - findTens(i)) + " Add: %s") %(workList[i], i)) for i in workList ]
# [x for ind, x in enumerate(list1) if 4 > ind > 0]
[print(("%d" + " "*(Ntens-findTens(x)) + ": %s") %(ind, x)) for ind, x in enumerate(workList)]
if showWork == True:
print("#"*divLength)
if showWork == True or returnDescr == True:
print("Answer: %d" %answer)
print("#"*divLength)
return answer
Source and background of this Challenge:
On HackerRank, they have you solve the N = 123...N by simply not using strings and they accept a simple one line print format statement as the answer. While using python 3.x print arguments in connection with unpacking a list is a lot more efficient to solve this, I became interested in whether you could build the number, as a number without doing any string conversions at all to do it. Since print() I think converts to string under the covers before it outputs content, I felt this was a more interesting approach, from a purely academic standpoint.
This can be done in a single line (minus importing), in no more than O(n) steps, without writing separate functions or loops, just a standard map-reduce.
import math # to get math.log10
listofints = [1,2,3,10,12,19,99,100,101,50102030]
n = reduce(lambda x,y:[x[0]*(10**(y[1]+1))+y[0],0],map(lambda x:[x,int(math.log10(x))], listofints))[0]
print(n)
# the number 1231012199910010150102030
This uses a standard map-reduce method to adorn numbers using map, and then reduce them back to a number.
The first step:
map(lambda x:[x,int(math.log10(x))], ...
takes a list of integers like:
[1,2,3,10,12,19,99,100,101,50102030]
and converts them into a list of pairs that are the number plus it's base-10 logarithm:
[[1, 0], [2, 0], [3, 0], [10, 1], [12, 1], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
Then, the next step is to reduce that list down to a single number:
reduce(lambda x,y:[x[0]*(10**(y[1]+1))+y[0],0],...
In order to do that, you need to multiply it by enough powers of ten to make room to add the next number on. Luckily, the next number says how big it is. So multiply pair one's number (the first part) by 10 to (one plus) the power of the next pair's power (the second part), and then add in the second pair's number (the first part).
The reduction looks like this as it goes:
[[1, 0], [2, 0], [3, 0], [10, 1], [12, 1], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[12, 0], [3, 0], [10, 1], [12, 1], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[123, 0], [10, 1], [12, 1], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[12310, 0], [12, 1], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[1231012, 0], [19, 1], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[123101219, 0], [99, 1], [100, 2], [101, 2], [50102030, 7]]
[[12310121999, 0], [100, 2], [101, 2], [50102030, 7]]
[[12310121999100, 0], [101, 2], [50102030, 7]]
[[12310121999100101, 0], [50102030, 7]]
[[1231012199910010150102030, 0]]
Finally, the last item is [1231012199910010150102030, 0], so take it's first element, which is 1231012199910010150102030
UPDATE
While the whole map-reduce thing is a good process to get used to, it was overkill here. This can be done without map entirely, just using reduce:
n = reduce(lambda x,y:x*(10**(int(math.log10(y))+1))+y,listofints)
Uh, so, this looks like cancer but I think it works
def get_seq(n):
return sum([(m+1)*10**(m + [sum([k/10 for k in range(n)][:i+1]) for i in range(len([j/10 for j in range(n)]))][m]) for m in range(n)])
I'm not quite sure I'm understand your problem, but are you looking for something like this?
def create_seqNum(num, reverse=False):
final_num = 0
x = [x for x in range(1, num+1)]
y = [y for y in range(num, 0, -1)]
for i in range(0, num):
print(x[i], y[i])
final_num += x[i]* 10**(y[i]-1)
return final_num
I may have misunderstood but what about:
def msb(N):
return sum(((i+1)*10**i for i in xrange(N)))
and
def lsb(N):
return sum(((i+1)*10**(N-i-1) for i in xrange(N)))
This fails if N >= 10, though.
How about this:
def reverse(N):
return sum([(x + 1) * 10**x for x in range(N)])
def forwards(N):
return sum([(x+1) * 10**y for x, y in zip(range(N), range(N-1, -1, -1))])
This only holds for 1<=N<=9, but one can note that the square root of numbers of the form 1234...N...4321 are N ones.
def seq(n):
return reduce(lambda x,y: 10*x+y, [1]*n)**2/10**(n-1)