├── logic_puzzle.py ├── Archive ├── logic_puzzle.py ├── bowling.py ├── portmanteau.py ├── parking_lot_search.py ├── parking_lot_search_draft.py ├── polynomials.py └── darts_probability.py ├── bowling.py ├── portmanteau.py ├── polynomials.py ├── parking_lot_search.py └── darts_probability.py /logic_puzzle.py: -------------------------------------------------------------------------------- 1 | """ 2 | Logic Puzzle 3 | ------------ 4 | 5 | You will write code to solve the following logic puzzle: 6 | 7 | 1. The person who arrived on Wednesday bought the laptop. 8 | 2. The programmer is not Wilkes. 9 | 3. Of the programmer and the person who bought the droid, 10 | one is Wilkes and the other is Hamming. 11 | 4. The writer is not Minsky. 12 | 5. Neither Knuth nor the person who bought the tablet is the manager. 13 | 6. Knuth arrived the day after Simon. 14 | 7. The person who arrived on Thursday is not the designer. 15 | 8. The person who arrived on Friday didn't buy the tablet. 16 | 9. The designer didn't buy the droid. 17 | 10. Knuth arrived the day after the manager. 18 | 11. Of the person who bought the laptop and Wilkes, 19 | one arrived on Monday and the other is the writer. 20 | 12. Either the person who bought the iphone or the person who bought the tablet 21 | arrived on Tuesday. 22 | 23 | You will write the function logic_puzzle(), which should return a list of the 24 | names of the people in the order in which they arrive. For example, if they 25 | happen to arrive in alphabetical order, Hamming on Monday, Knuth on Tuesday, etc., 26 | then you would return: 27 | 28 | ['Hamming', 'Knuth', 'Minsky', 'Simon', 'Wilkes'] 29 | 30 | (You can assume that the days mentioned are all in the same week.) 31 | """ 32 | import itertools 33 | 34 | PERSONS = ['Wilkes','Hamming','Knuth','Minsky','Simon'] 35 | 36 | def logic_puzzle(): 37 | wilkes,hamming,knuth,minsky,simon = PERSONS 38 | ordering = list(itertools.permutations(PERSONS)) 39 | return next([mon,tue,wed,thu,fri] for (mon,tue,wed,thu,fri) in ordering 40 | for (laptop,droid,tablet,iphone,_) in ordering 41 | for (programmer,writer,manager,designer,_) in ordering 42 | if(wed == laptop and programmer != wilkes and \ 43 | (programmer == hamming and droid == wilkes) and \ 44 | (writer != minsky) and \ 45 | (knuth != manager and tablet != manager) and \ 46 | ((mon == simon and tue == knuth) or (tue == simon and wed == knuth) or (wed == simon and thu == knuth) or (thu == simon and fri == knuth)) and \ 47 | (thu != designer) and (fri != tablet) and (designer != droid) and (manager == simon) 48 | and ((laptop == mon and wilkes == writer) or (laptop == writer and wilkes == mon)) and \ 49 | ((iphone == tue) or (tablet == tue))) 50 | ) 51 | 52 | def test_unit(): 53 | assert ['Wilkes', 'Simon', 'Knuth', 'Hamming', 'Minsky'] == logic_puzzle() 54 | print 'test pass' 55 | 56 | if __name__ == '__main__': 57 | test_unit() 58 | 59 | -------------------------------------------------------------------------------- /Archive/logic_puzzle.py: -------------------------------------------------------------------------------- 1 | """ 2 | UNIT 2: Logic Puzzle 3 | 4 | You will write code to solve the following logic puzzle: 5 | 6 | 1. The person who arrived on Wednesday bought the laptop. 7 | 2. The programmer is not Wilkes. 8 | 3. Of the programmer and the person who bought the droid, 9 | one is Wilkes and the other is Hamming. 10 | 4. The writer is not Minsky. 11 | 5. Neither Knuth nor the person who bought the tablet is the manager. 12 | 6. Knuth arrived the day after Simon. 13 | 7. The person who arrived on Thursday is not the designer. 14 | 8. The person who arrived on Friday didn't buy the tablet. 15 | 9. The designer didn't buy the droid. 16 | 10. Knuth arrived the day after the manager. 17 | 11. Of the person who bought the laptop and Wilkes, 18 | one arrived on Monday and the other is the writer. 19 | 12. Either the person who bought the iphone or the person who bought the tablet 20 | arrived on Tuesday. 21 | 22 | You will write the function logic_puzzle(), which should return a list of the 23 | names of the people in the order in which they arrive. For example, if they 24 | happen to arrive in alphabetical order, Hamming on Monday, Knuth on Tuesday, etc., 25 | then you would return: 26 | 27 | ['Hamming', 'Knuth', 'Minsky', 'Simon', 'Wilkes'] 28 | 29 | (You can assume that the days mentioned are all in the same week.) 30 | """ 31 | import itertools 32 | 33 | PERSONS = ['Wilkes','Hamming','Knuth','Minsky','Simon'] 34 | 35 | def logic_puzzle(): 36 | days = Monday,Tuesday,Wednesday,Thursday,Friday = [1,2,3,4,5] 37 | ordering = list(itertools.permutations(days)) 38 | return next(transformed(wilkes,hamming,knuth,minsky,simon) 39 | for (wilkes,hamming,knuth,minsky,simon) in ordering 40 | for (laptop,droid,tablet,iphone,_) in ordering 41 | for(programmer,writer,manager,designer,_) in ordering 42 | if(Wednesday == laptop and programmer != wilkes and \ 43 | (programmer == hamming and droid == wilkes) and \ 44 | (writer != minsky) and \ 45 | (knuth != manager and tablet != manager) and \ 46 | (knuth == simon + 1) and \ 47 | (Thursday != designer) and (Friday != tablet) and (designer != droid) and (manager == simon) 48 | and ((laptop == Monday and wilkes == writer) or (laptop == writer and wilkes == Monday)) and \ 49 | ((iphone == Tuesday) or (tablet == Tuesday))) 50 | ) 51 | #Another implementation 52 | '''for (wilkes,hamming,knuth,minsky,simon) in ordering: 53 | for (laptop,droid,tablet,iphone,_) in ordering: 54 | for(programmer,writer,manager,designer,_) in ordering: 55 | if(Wednesday == laptop and programmer != wilkes and \ 56 | (programmer == hamming and droid == wilkes) and \ 57 | (writer != minsky) and \ 58 | (knuth != manager and tablet != manager) and \ 59 | (knuth == simon + 1) and \ 60 | (Thursday != designer) and (Friday != tablet) and (designer != droid) and (manager == simon) 61 | and ((laptop == Monday and wilkes == writer) or (laptop == writer and wilkes == Monday)) and \ 62 | ((iphone == Tuesday) or (tablet == Tuesday))): 63 | return transformed(wilkes,hamming,knuth,minsky,simon) 64 | return 'no result found''''' 65 | 66 | def transformed(*positions): 67 | transformed_list = list(['dummy']*len(positions)) 68 | for index,position in enumerate(positions): 69 | transformed_list[position-1] = PERSONS[index] 70 | return transformed_list 71 | 72 | 73 | 74 | def test_unit(): 75 | assert ['Wilkes', 'Simon', 'Knuth', 'Hamming', 'Minsky'] == logic_puzzle() 76 | print 'test pass' 77 | 78 | if __name__ == '__main__': 79 | test_unit() 80 | 81 | -------------------------------------------------------------------------------- /bowling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Bowling score counter: 3 | ---------------------- 4 | 5 | In a perfect bowling game: 6 | 7 | >>> bowling([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]) 8 | 300 9 | 10 | But in theory, the rules bowling are as follows: 11 | 12 | (1) A game consists of 10 frames. In each frame you roll one or two balls, 13 | except for the tenth frame, where you roll one, two, or three. Your total 14 | score is the sum of your scores for the ten frames. 15 | (2) If you knock down fewer than ten pins with your two balls in the frame, 16 | you score the total knocked down. For example, bowling([8, 1, 7, ...]) means 17 | that you knocked down a total of 9 pins in the first frame. You score 9 point 18 | for the frame, and you used up two balls in the frame. The second frame will 19 | start with the 7. 20 | (3) If you knock down all ten pins on your second ball it is called a 'spare' 21 | and you score 10 points plus a bonus: whatever you roll with your next ball. 22 | The next ball will also count in the next frame, so the next ball counts twice 23 | (except in the tenth frame, in which case the bonus ball counts only once). 24 | For example, bowling([8, 2, 7, ...]) means you get a spare in the first frame. 25 | You score 10 + 7 for the frame; the second frame starts with the 7. 26 | (4) If you knock down all ten pins on your first ball it is called a 'strike' 27 | and you score 10 points plus a bonus of your score on the next two balls. 28 | (The next two balls also count in the next frame, except in the tenth frame.) 29 | For example, bowling([10, 7, 3, ...]) means that you get a strike, you score 30 | 10 + 7 + 3 = 20 in the first frame; the second frame starts with the 7. 31 | 32 | """ 33 | FRAMES = 10 34 | # Main() : Compute the total score for a player's game of bowling. 35 | def bowling(balls,debug=False): 36 | score = 0 37 | index = 0 # points to the start of next frame 38 | frame = 1 39 | while(frame <= FRAMES): 40 | frame_score,increment = score_frame(index,balls) 41 | if debug: 42 | print 'frame_score:%s increment:%s' %(frame_score,increment) 43 | score += frame_score 44 | index += increment # increment returns a value for the number of balls in the previous frame. It could be '1' for a strike or '2' for a normal throw 45 | frame += 1 46 | return score 47 | 48 | def score_frame(index,balls): 49 | frame_score = 0 50 | increment = 2 51 | 52 | if isStrike(index,balls): 53 | for i in range(index,scoring_balls(index,balls,'3 if balls[index] == 10 else 4')): 54 | frame_score += balls[i] 55 | increment = 1 if balls[index] == 10 else 2 56 | elif isSpare(index,balls): 57 | for i in range(index,scoring_balls(index,balls,'3')): 58 | frame_score += balls[i] 59 | else: # normal throw 60 | for i in range(index,scoring_balls(index,balls,'2')): 61 | frame_score += balls[i] 62 | return frame_score,increment 63 | 64 | def scoring_balls(index,balls,expr):# returns the number of balls that should be scored.Say, may score additional 2 balls and spare may score additional 1 ball etc.. 65 | return min(index+eval(expr),len(balls)) 66 | 67 | def isStrike(index,balls): 68 | return balls[index] == 10 69 | 70 | def isSpare(index,balls): 71 | spare_score = 0 72 | for i in range(index,scoring_balls(index,balls,'2')): 73 | spare_score += balls[i] 74 | return spare_score == 10 75 | 76 | def test_bowling(): 77 | assert 0 == bowling([0] * 20) 78 | assert 20 == bowling([1] * 20) 79 | assert 80 == bowling([4] * 20) 80 | assert 190 == bowling([9,1] * 10 + [9]) 81 | assert 300 == bowling([10] * 12) 82 | assert 200 == bowling([10, 5,5] * 5 + [10]) 83 | assert 11 == bowling([0,0] * 9 + [10,1,0]) 84 | assert 12 == bowling([0,0] * 8 + [10, 1,0]) 85 | assert 168 == bowling([9, 1, 0, 10, 10, 10, 6, 2, 7, 3, 8, 2, 10, 9, 0, 9, 1, 10]) 86 | assert 60 == bowling([3]*20) 87 | assert 67 == bowling([8,2] + [3]*18) 88 | assert 70 == bowling([10] + [3]*18) 89 | assert 147 == bowling([8,2]*2 + [7]*16) 90 | assert 190 == bowling([9,1] * 10 + [9]) 91 | print 'tests pass' 92 | 93 | if __name__ == '__main__': 94 | test_bowling() 95 | -------------------------------------------------------------------------------- /Archive/bowling.py: -------------------------------------------------------------------------------- 1 | """ 2 | UNIT 1: Bowling: 3 | 4 | You will write the function bowling(balls), which returns an integer indicating 5 | the score of a ten-pin bowling game. balls is a list of integers indicating 6 | how many pins are knocked down with each ball. For example, a perfect game of 7 | bowling would be described with: 8 | 9 | >>> bowling([10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]) 10 | 300 11 | 12 | The rules of bowling are as follows: 13 | 14 | (1) A game consists of 10 frames. In each frame you roll one or two balls, 15 | except for the tenth frame, where you roll one, two, or three. Your total 16 | score is the sum of your scores for the ten frames. 17 | (2) If you knock down fewer than ten pins with your two balls in the frame, 18 | you score the total knocked down. For example, bowling([8, 1, 7, ...]) means 19 | that you knocked down a total of 9 pins in the first frame. You score 9 point 20 | for the frame, and you used up two balls in the frame. The second frame will 21 | start with the 7. 22 | (3) If you knock down all ten pins on your second ball it is called a 'spare' 23 | and you score 10 points plus a bonus: whatever you roll with your next ball. 24 | The next ball will also count in the next frame, so the next ball counts twice 25 | (except in the tenth frame, in which case the bonus ball counts only once). 26 | For example, bowling([8, 2, 7, ...]) means you get a spare in the first frame. 27 | You score 10 + 7 for the frame; the second frame starts with the 7. 28 | (4) If you knock down all ten pins on your first ball it is called a 'strike' 29 | and you score 10 points plus a bonus of your score on the next two balls. 30 | (The next two balls also count in the next frame, except in the tenth frame.) 31 | For example, bowling([10, 7, 3, ...]) means that you get a strike, you score 32 | 10 + 7 + 3 = 20 in the first frame; the second frame starts with the 7. 33 | 34 | """ 35 | FRAMES = 10 36 | 37 | def bowling(balls,debug=False): 38 | "Compute the total score for a player's game of bowling." 39 | score = 0 40 | index = 0 # tracks where does the next frame start 41 | frame = 1 42 | while(frame <= FRAMES): 43 | frame_score,increment = score_frame(index,balls) 44 | if debug: 45 | print 'frame_score:%s increment:%s' %(frame_score,increment) 46 | score += frame_score 47 | index += increment # increment returns a value for the number of balls in the previous frame. It could be '1' for a strike or '2' for a normal throw 48 | frame += 1 49 | return score 50 | 51 | def score_frame(index,balls): 52 | frame_score = 0 53 | increment = 2 54 | 55 | if isStrike(index,balls): 56 | #strike is tricky because strike can happen in a bowler's first attempt or second. The score could be [10] or [0,10]. Hence the ball counting logic and increment logic should vary. 57 | for i in range(index,scoring_balls(index,balls,'3 if balls[index] == 10 else 4')): 58 | frame_score += balls[i] 59 | increment = 1 if balls[index] == 10 else 2 60 | elif isSpare(index,balls): 61 | for i in range(index,scoring_balls(index,balls,'3')): 62 | frame_score += balls[i] 63 | else: # normal throw 64 | for i in range(index,scoring_balls(index,balls,'2')): 65 | frame_score += balls[i] 66 | return frame_score,increment 67 | 68 | def scoring_balls(index,balls,expr):# returns the number of balls that should be scored.Say, may score additional 2 balls and spare may score additional 1 ball etc.. 69 | return min(index+eval(expr),len(balls)) 70 | 71 | def isStrike(index,balls): 72 | return balls[index] == 10 73 | '''for i in range(index,scoring_balls(index,balls,'2')): 74 | if balls[i] == 10:#strike can happen in either one of the balls 75 | return True 76 | return False''' 77 | 78 | def isSpare(index,balls): 79 | spare_score = 0 80 | for i in range(index,scoring_balls(index,balls,'2')): 81 | spare_score += balls[i] 82 | return spare_score == 10 83 | 84 | def test_bowling(): 85 | assert 0 == bowling([0] * 20) 86 | assert 20 == bowling([1] * 20) 87 | assert 80 == bowling([4] * 20) 88 | assert 190 == bowling([9,1] * 10 + [9]) 89 | assert 300 == bowling([10] * 12) 90 | assert 200 == bowling([10, 5,5] * 5 + [10]) 91 | assert 11 == bowling([0,0] * 9 + [10,1,0]) 92 | assert 12 == bowling([0,0] * 8 + [10, 1,0]) 93 | assert 168 == bowling([9, 1, 0, 10, 10, 10, 6, 2, 7, 3, 8, 2, 10, 9, 0, 9, 1, 10]) 94 | print 'tests pass' 95 | 96 | def test_unit(): 97 | assert 60 == bowling([3]*20) 98 | assert 67 == bowling([8,2] + [3]*18) 99 | assert 70 == bowling([10] + [3]*18) 100 | assert 147 == bowling([8,2]*2 + [7]*16) 101 | assert 190 == bowling([9,1] * 10 + [9]) 102 | print 'unit tests pass' 103 | 104 | if __name__ == '__main__': 105 | #test_unit() 106 | test_bowling() 107 | -------------------------------------------------------------------------------- /Archive/portmanteau.py: -------------------------------------------------------------------------------- 1 | # Unit 6: Fun with Words 2 | 3 | """ 4 | A portmanteau word is a blend of two or more words, like 'mathelete', 5 | which comes from 'math' and 'athelete'. You will write a function to 6 | find the 'best' portmanteau word from a list of dictionary words. 7 | Because 'portmanteau' is so easy to misspell, we will call our 8 | function 'natalie' instead: 9 | 10 | natalie(['word', ...]) == 'portmanteauword' 11 | 12 | In this exercise the rules are: a portmanteau must be composed of 13 | three non-empty pieces, start+mid+end, where both start+mid and 14 | mid+end are among the list of words passed in. For example, 15 | 'adolescented' comes from 'adolescent' and 'scented', with 16 | start+mid+end='adole'+'scent'+'ed'. A portmanteau must be composed 17 | of two different words (not the same word twice). 18 | 19 | That defines an allowable combination, but which is best? Intuitively, 20 | a longer word is better, and a word is well-balanced if the mid is 21 | about half the total length while start and end are about 1/4 each. 22 | To make that specific, the score for a word w is the number of letters 23 | in w minus the difference between the actual and ideal lengths of 24 | start, mid, and end. (For the example word w='adole'+'scent'+'ed', the 25 | start,mid,end lengths are 5,5,2 and the total length is 12. The ideal 26 | start,mid,end lengths are 12/4,12/2,12/4 = 3,6,3. So the final score 27 | is 28 | 29 | 12 - abs(5-3) - abs(5-6) - abs(2-3) = 8. 30 | 31 | yielding a score of 12 - abs(5-(12/4)) - abs(5-(12/2)) - 32 | abs(2-(12/4)) = 8. 33 | 34 | The output of natalie(words) should be the best portmanteau, or None 35 | if there is none. 36 | 37 | Note (1): I got the idea for this question from 38 | Darius Bacon. Note (2): In real life, many portmanteaux omit letters, 39 | for example 'smoke' + 'fog' = 'smog'; we aren't considering those. 40 | Note (3): The word 'portmanteau' is itself a portmanteau; it comes 41 | from the French "porter" (to carry) + "manteau" (cloak), and in 42 | English meant "suitcase" in 1871 when Lewis Carroll used it in 43 | 'Through the Looking Glass' to mean two words packed into one. Note 44 | (4): the rules for 'best' are certainly subjective, and certainly 45 | should depend on more things than just letter length. In addition to 46 | programming the solution described here, you are welcome to explore 47 | your own definition of best, and use your own word lists to come up 48 | with interesting new results. Post your best ones in the discussion 49 | forum. Note (5) The test examples will involve no more than a dozen or so 50 | input words. But you could implement a method that is efficient with a 51 | larger list of words. 52 | """ 53 | import re 54 | def natalie(words): 55 | "Find the best Portmanteau word formed from any two of the list of words." 56 | possible_portmanteau = [] 57 | for word in words: 58 | i = 1 59 | while(i < len(word)): 60 | other_words = [w for w in words if w != word] 61 | anchor_word = word[i:] 62 | for ow in other_words: 63 | match = re.match('('+anchor_word +')' + r'.*',ow) 64 | if match: 65 | possible_portmanteau.append((i,len(match.group(1)),len(ow)-len(match.group(1)),word[0:i]+ow)) 66 | i = i+1 67 | 68 | scored_portmanteau = [] 69 | best_portmanteau = None 70 | best_score = None 71 | for portmanteau in possible_portmanteau: 72 | start,mid,end,word = portmanteau 73 | length = len(word) 74 | ideal_start,ideal_mid,ideal_end = length/4,length/2,length/4 75 | score = length - abs(start-ideal_start) - abs(mid-ideal_mid) - abs(end-ideal_end) 76 | if not best_score or best_score < score: 77 | best_score = score 78 | best_portmanteau = word 79 | return best_portmanteau 80 | 81 | def test_natalie(): 82 | "Some test cases for natalie" 83 | #print natalie(['adolescent', 'scented', 'centennial', 'always', 'ado']) 84 | assert natalie(['adolescent', 'scented', 'centennial', 'always', 'ado']) in ('adolescented','adolescentennial') 85 | assert natalie(['eskimo', 'escort', 'kimchee', 'kimono', 'cheese']) == 'eskimono' 86 | assert natalie(['kimono', 'kimchee', 'cheese', 'serious', 'us', 'usage']) == 'kimcheese' 87 | assert natalie(['circus', 'elephant', 'lion', 'opera', 'phantom']) == 'elephantom' 88 | assert natalie(['programmer', 'coder', 'partying', 'merrymaking']) == 'programmerrymaking' 89 | assert natalie(['int', 'intimate', 'hinter', 'hint', 'winter']) == 'hintimate' 90 | assert natalie(['morass', 'moral', 'assassination']) == 'morassassination' 91 | assert natalie(['entrepreneur', 'academic', 'doctor', 'neuropsychologist', 'neurotoxin', 'scientist', 'gist']) in ('entrepreneuropsychologist', 'entrepreneurotoxin') 92 | assert natalie(['perspicacity', 'cityslicker', 'capability', 'capable']) == 'perspicacityslicker' 93 | assert natalie(['backfire', 'fireproof', 'backflow', 'flowchart', 'background', 'groundhog']) == 'backgroundhog' 94 | assert natalie(['streaker', 'nudist', 'hippie', 'protestor', 'disturbance', 'cops']) == 'nudisturbance' 95 | assert natalie(['night', 'day']) == None 96 | assert natalie(['dog', 'dogs']) == None 97 | assert natalie(['test']) == None 98 | assert natalie(['']) == None 99 | assert natalie(['ABC', '123']) == None 100 | assert natalie([]) == None 101 | return 'tests pass' 102 | 103 | 104 | print test_natalie() 105 | 106 | 107 | -------------------------------------------------------------------------------- /portmanteau.py: -------------------------------------------------------------------------------- 1 | # Fun with Words 2 | 3 | """ 4 | A portmanteau word is a blend of two or more words, like 'mathelete', 5 | which comes from 'math' and 'athelete'. You will write a function to 6 | find the 'best' portmanteau word from a list of dictionary words. 7 | Because 'portmanteau' is so easy to misspell, we will call our 8 | function 'natalie' instead: 9 | 10 | natalie(['word', ...]) == 'portmanteauword' 11 | 12 | In this exercise the rules are: a portmanteau must be composed of 13 | three non-empty pieces, start+mid+end, where both start+mid and 14 | mid+end are among the list of words passed in. For example, 15 | 'adolescented' comes from 'adolescent' and 'scented', with 16 | start+mid+end='adole'+'scent'+'ed'. A portmanteau must be composed 17 | of two different words (not the same word twice). 18 | 19 | That defines an allowable combination, but which is best? Intuitively, 20 | a longer word is better, and a word is well-balanced if the mid is 21 | about half the total length while start and end are about 1/4 each. 22 | To make that specific, the score for a word w is the number of letters 23 | in w minus the difference between the actual and ideal lengths of 24 | start, mid, and end. (For the example word w='adole'+'scent'+'ed', the 25 | start,mid,end lengths are 5,5,2 and the total length is 12. The ideal 26 | start,mid,end lengths are 12/4,12/2,12/4 = 3,6,3. So the final score 27 | is 28 | 29 | 12 - abs(5-3) - abs(5-6) - abs(2-3) = 8. 30 | 31 | yielding a score of 12 - abs(5-(12/4)) - abs(5-(12/2)) - 32 | abs(2-(12/4)) = 8. 33 | 34 | The output of natalie(words) should be the best portmanteau, or None 35 | if there is none. 36 | 37 | Note (1): I got the idea for this question from 38 | Darius Bacon. Note (2): In real life, many portmanteaux omit letters, 39 | for example 'smoke' + 'fog' = 'smog'; we aren't considering those. 40 | Note (3): The word 'portmanteau' is itself a portmanteau; it comes 41 | from the French "porter" (to carry) + "manteau" (cloak), and in 42 | English meant "suitcase" in 1871 when Lewis Carroll used it in 43 | 'Through the Looking Glass' to mean two words packed into one. Note 44 | (4): the rules for 'best' are certainly subjective, and certainly 45 | should depend on more things than just letter length. In addition to 46 | programming the solution described here, you are welcome to explore 47 | your own definition of best, and use your own word lists to come up 48 | with interesting new results. Post your best ones in the discussion 49 | forum. Note (5) The test examples will involve no more than a dozen or so 50 | input words. But you could implement a method that is efficient with a 51 | larger list of words. 52 | """ 53 | import re 54 | from collections import deque 55 | 56 | 57 | def natalie(words): 58 | "Find the best Portmanteau word formed from any two of the list of words." 59 | possible_portmanteau = [] 60 | for word in words: 61 | i = 1 62 | while(i < len(word)): 63 | other_words = [w for w in words if w != word] # any word other than the root word. Better algorithm below 64 | anchor_word = word[i:] # anchor word is the common word. Key is to find the anchor word. Better alogorithm below 65 | for ow in other_words: 66 | match = re.match('('+anchor_word +')' + r'.*',ow) 67 | if match: 68 | possible_portmanteau.append((i,len(match.group(1)),len(ow)-len(match.group(1)),word[0:i]+ow)) 69 | i = i+1 70 | 71 | scored_portmanteau = [] 72 | best_portmanteau = None 73 | best_score = None 74 | for portmanteau in possible_portmanteau: 75 | start,mid,end,word = portmanteau 76 | length = len(word) 77 | ideal_start,ideal_mid,ideal_end = length/4,length/2,length/4 78 | score = length - abs(start-ideal_start) - abs(mid-ideal_mid) - abs(end-ideal_end) 79 | if not best_score or best_score < score: 80 | best_score = score 81 | best_portmanteau = word 82 | return best_portmanteau 83 | 84 | 85 | # More efficient algorithm to create portmanteau 86 | def natalie_efficient(words): 87 | possible_portmanteau = [] 88 | words = deque(words) 89 | i = 0 90 | while(i < len(words)): 91 | word = words.popleft() # use queues to prevent another loop. 92 | for ow in words: 93 | combined_word = word + ow 94 | match = re.sub(r'(\w{2,})\1',r'\1',combined_word) # check for repeated words 2 or more in length 95 | if len(match) != len(combined_word): 96 | possible_portmanteau.append(match) 97 | words.append(word) 98 | i += 1 99 | print possible_portmanteau 100 | 101 | def test_natalie(): 102 | "Some test cases for natalie" 103 | assert natalie(['adolescent', 'scented', 'centennial', 'always', 'ado']) in ('adolescented','adolescentennial') 104 | assert natalie(['eskimo', 'escort', 'kimchee', 'kimono', 'cheese']) == 'eskimono' 105 | assert natalie(['kimono', 'kimchee', 'cheese', 'serious', 'us', 'usage']) == 'kimcheese' 106 | assert natalie(['circus', 'elephant', 'lion', 'opera', 'phantom']) == 'elephantom' 107 | assert natalie(['programmer', 'coder', 'partying', 'merrymaking']) == 'programmerrymaking' 108 | assert natalie(['int', 'intimate', 'hinter', 'hint', 'winter']) == 'hintimate' 109 | assert natalie(['morass', 'moral', 'assassination']) == 'morassassination' 110 | assert natalie(['entrepreneur', 'academic', 'doctor', 'neuropsychologist', 'neurotoxin', 'scientist', 'gist']) in ('entrepreneuropsychologist', 'entrepreneurotoxin') 111 | assert natalie(['perspicacity', 'cityslicker', 'capability', 'capable']) == 'perspicacityslicker' 112 | assert natalie(['backfire', 'fireproof', 'backflow', 'flowchart', 'background', 'groundhog']) == 'backgroundhog' 113 | assert natalie(['streaker', 'nudist', 'hippie', 'protestor', 'disturbance', 'cops']) == 'nudisturbance' 114 | assert natalie(['night', 'day']) == None 115 | assert natalie(['dog', 'dogs']) == None 116 | assert natalie(['test']) == None 117 | assert natalie(['']) == None 118 | assert natalie(['ABC', '123']) == None 119 | assert natalie([]) == None 120 | return 'tests pass' 121 | 122 | if __name__ == '__main__': 123 | print test_natalie() 124 | 125 | 126 | -------------------------------------------------------------------------------- /polynomials.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Functions and APIs: Polynomials 4 | ------------------------------- 5 | 6 | A polynomial is a mathematical formula like: 7 | 8 | 30 * x**2 + 20 * x + 10 9 | 10 | More formally, it involves a single variable (here 'x'), and the sum of one 11 | or more terms, where each term is a real number multiplied by the variable 12 | raised to a non-negative integer power. (Remember that x**0 is 1 and x**1 is x, 13 | so 'x' is short for '1 * x**1' and '10' is short for '10 * x**0'.) 14 | 15 | We will represent a polynomial as a Python function which computes the formula 16 | when applied to a numeric value x. The function will be created with the call: 17 | 18 | p1 = poly((10, 20, 30)) 19 | 20 | where the nth element of the input tuple is the coefficient of the nth power of x. 21 | (Note the order of coefficients has the x**n coefficient neatly in position n of 22 | the list, but this is the reversed order from how we usually write polynomials.) 23 | poly returns a function, so we can now apply p1 to some value of x: 24 | 25 | p1(0) == 10 26 | 27 | Our representation of a polynomial is as a callable function, but in addition, 28 | we will store the coefficients in the .coefs attribute of the function, so we have: 29 | 30 | p1.coefs == (10, 20, 30) 31 | 32 | And finally, the name of the function will be the formula given above, so you should 33 | have something like this: 34 | 35 | >>> p1 36 | 37 | 38 | >>> p1.__name__ 39 | '30 * x**2 + 20 * x + 10' 40 | 41 | Make sure the formula used for function names is simplified properly. 42 | No '0 * x**n' terms; just drop these. Simplify '1 * x**n' to 'x**n'. 43 | Simplify '5 * x**0' to '5'. Similarly, simplify 'x**1' to 'x'. 44 | For negative coefficients, like -5, you can use '... + -5 * ...' or 45 | '... - 5 * ...'; your choice. I'd recommend no spaces around '**' 46 | and spaces around '+' and '*', but you are free to use your preferences. 47 | 48 | Your task is to write the function poly and the following additional functions: 49 | 50 | is_poly, add, sub, mul, power, deriv, integral 51 | 52 | They are described below; see the test_poly function for examples. 53 | """ 54 | import re 55 | 56 | SIMPLIFICATION_RULES = { 57 | r'(\d+)\s\*\sx\*{2}0':r'\1', # a * (x^0) = a 58 | r'(x)\*{2}1\b':r'\1',# x^1 = x 59 | r'\b1\s\*\s(x\*{2}\d)':r'\1', # 1 * (x^2) = x^2 60 | r'x\*{2}0':'1' # x^0 = 1 61 | } 62 | 63 | def simplify(expression): 64 | for pattern in SIMPLIFICATION_RULES: 65 | if(re.search(pattern,expression)): 66 | expression = re.sub(pattern,r'%s'%(SIMPLIFICATION_RULES[pattern]),expression) 67 | return expression 68 | 69 | # redefine enumerate that supports reverse 70 | def enumerate(sequence,reverse=False): 71 | n = len(sequence)-1 if reverse else 0 72 | for _ in range(len(sequence)): 73 | yield n,sequence[n] 74 | n = (n-1) if reverse else (n+1) 75 | 76 | # returns an uncompiled/canonical polynomial string (10,20,30) => '30 * x**2 + 20 * x + 10' 77 | def canonical_poly(coefs): 78 | terms = [simplify('{0} * x**{1}'.format(coef,position)) for position,coef in enumerate(coefs,reverse=True) if coef != 0] 79 | expression = ' + '.join(terms) 80 | return expression 81 | 82 | def poly(coefs): 83 | poly_st = canonical_poly(coefs) 84 | poly_fn = eval('lambda x: {0}'.format(poly_st)) 85 | def fn(x): 86 | return poly_fn(x) 87 | fn.__name__ = poly_st 88 | fn.coefs = coefs 89 | return fn 90 | 91 | def canonical_name(name): 92 | return name.replace(' ', '').replace('+-', '-') 93 | 94 | def same_name(name1, name2): 95 | """I define this function rather than doing name1 == name2 to allow for some 96 | variation in naming conventions.""" 97 | return canonical_name(name1) == canonical_name(name2) 98 | 99 | def is_poly(x): 100 | "Return true if x is a poly (polynomial)." 101 | if not (hasattr(x,'__call__')): # x should be a function 102 | return False 103 | if not (hasattr(x,'__name__')): # x should have name attr. 104 | return False 105 | poly = canonical_name(x.__name__) # strip and return in standard form 106 | poly_pattern = r'(\b\d+\*x\*{2}\d+\b)|(\bx\*{2}\d+\b)|(\b\d+\*x\b)|(\bx\b)|(\b\d+\b)' 107 | for p in poly.split('+'): 108 | if not re.search(poly_pattern,p): 109 | return False 110 | return True 111 | # For all functions below, just manipulate the coefs and use poly() function. 112 | def add(p1, p2): 113 | "Return a new polynomial which is the sum of polynomials p1 and p2." 114 | summed_coefs = add_coefs(p1.coefs,p2.coefs) 115 | return poly(summed_coefs) 116 | 117 | def add_coefs(p1,p2): 118 | diff = len(p1) - len(p2) 119 | if diff > 0 : # standardize by making coefs the same length 120 | p2 = p2 + (0,) * abs(diff) 121 | else: 122 | p1 = p1 + (0,) * abs(diff) 123 | return tuple([p1[i] + p2[i] for i,_ in enumerate(p1)]) 124 | 125 | def sub(p1, p2): 126 | "Return a new polynomial which is the difference of polynomials p1 and p2." 127 | p2.coefs = tuple([-e for e in p2.coefs]) 128 | return add(p1,p2) 129 | 130 | 131 | def mul(p1, p2): 132 | "Return a new polynomial which is the product of polynomials p1 and p2." 133 | p1_coefs = p1.coefs 134 | p2_coefs = p2.coefs 135 | list_coefs = list() 136 | for index,p1_coef in enumerate(p1_coefs): 137 | list_coefs.append([0]*index + [p1_coef*p2_coef for p2_coef in p2_coefs]) 138 | mul_coefs = list() 139 | for coefs in list_coefs: 140 | mul_coefs = add_coefs(tuple(coefs),tuple(mul_coefs)) # cumulative addition of coefs is the multiplication coef. 141 | return poly(mul_coefs) 142 | 143 | 144 | def power(p, n): 145 | "Return a new polynomial which is p to the nth power (n a non-negative integer)." 146 | power_poly = poly((1,)) 147 | for _ in range(0,n): #cumulative multiplication is the power fn. 148 | power_poly = mul(power_poly,p) 149 | return power_poly 150 | 151 | def deriv(p): 152 | "Return the derivative of a function p (with respect to its argument)." 153 | coefs = p.coefs 154 | derivative_coefs = list() 155 | for index,coef in enumerate(coefs): 156 | if index != 0: 157 | derivative_coefs.append(coef * index) 158 | return poly(tuple(derivative_coefs)) 159 | 160 | def integral(p, C=0): 161 | "Return the integral of a function p (with respect to its argument)." 162 | coefs = p.coefs 163 | integral_coefs = [C] 164 | for index,coef in enumerate(coefs): 165 | integral_coefs.append(coef/(index+1)) 166 | return poly(tuple(integral_coefs)) 167 | 168 | def test_poly(): 169 | global p1, p2, p3, p4, p5, p9 # global to ease debugging in an interactive session 170 | 171 | p1 = poly((10, 20, 30)) 172 | assert p1(0) == 10 173 | for x in (1, 2, 3, 4, 5, 1234.5): 174 | assert p1(x) == 30 * x**2 + 20 * x + 10 175 | assert same_name(p1.__name__, '30 * x**2 + 20 * x + 10') 176 | 177 | assert is_poly(p1) 178 | assert not is_poly(abs) and not is_poly(42) and not is_poly('cracker') 179 | 180 | p3 = poly((0, 0, 0, 1)) 181 | assert p3.__name__ == 'x**3' 182 | p9 = mul(p3, mul(p3, p3)) 183 | assert p9(2) == 512 184 | p4 = add(p1, p3) 185 | assert same_name(p4.__name__, 'x**3 + 30 * x**2 + 20 * x + 10') 186 | assert same_name(poly((1, 1)).__name__, 'x + 1') 187 | assert same_name(power(poly((1, 1)), 10).__name__, 188 | 'x**10 + 10 * x**9 + 45 * x**8 + 120 * x**7 + 210 * x**6 + 252 * x**5 + 210' + 189 | ' * x**4 + 120 * x**3 + 45 * x**2 + 10 * x + 1') 190 | 191 | assert add(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (11,22,33) 192 | assert sub(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (9,18,27) 193 | assert mul(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (10, 40, 100, 120, 90) 194 | assert power(poly((1, 1)), 2).coefs == (1, 2, 1) 195 | assert power(poly((1, 1)), 10).coefs == (1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1) 196 | assert deriv(p1).coefs == (20, 60) 197 | assert integral(poly((20, 60))).coefs == (0, 20, 30) 198 | p5 = poly((0, 1, 2, 3, 4, 5)) 199 | assert same_name(p5.__name__, '5 * x**5 + 4 * x**4 + 3 * x**3 + 2 * x**2 + x') 200 | assert p5(1) == 15 201 | assert p5(2) == 258 202 | assert same_name(deriv(p5).__name__, '25 * x**4 + 16 * x**3 + 9 * x**2 + 4 * x + 1') 203 | assert deriv(p5)(1) == 55 204 | assert deriv(p5)(2) == 573 205 | print 'tests pass' 206 | 207 | if __name__ == '__main__': 208 | test_poly() 209 | -------------------------------------------------------------------------------- /parking_lot_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Search 3 | ------- 4 | 5 | Your task is to maneuver a car in a crowded parking lot. This is a kind of 6 | puzzle, which can be represented with a diagram like this: 7 | 8 | | | | | | | | | 9 | | G G . . . Y | 10 | | P . . B . Y | 11 | | P * * B . Y @ 12 | | P . . B . . | 13 | | O . . . A A | 14 | | O . S S S . | 15 | | | | | | | | | 16 | 17 | A '|' represents a wall around the parking lot, a '.' represents an empty square, 18 | and a letter or asterisk represents a car. '@' marks a goal square. 19 | Note that there are long (3 spot) and short (2 spot) cars. 20 | Your task is to get the car that is represented by '**' out of the parking lot 21 | (on to a goal square). Cars can move only in the direction they are pointing. 22 | In this diagram, the cars GG, AA, SSS, and ** are pointed right-left, 23 | so they can move any number of squares right or left, as long as they don't 24 | bump into another car or wall. In this diagram, GG could move 1, 2, or 3 spots 25 | to the right; AA could move 1, 2, or 3 spots to the left, and ** cannot move 26 | at all. In the up-down direction, BBB can move one up or down, YYY can move 27 | one down, and PPP and OO cannot move. 28 | 29 | You should solve this puzzle (and ones like it) using search. You will be 30 | given an initial state like this diagram and a goal location for the ** car; 31 | in this puzzle the goal is the '.' empty spot in the wall on the right side. 32 | You should return a path -- an alternation of states and actions -- that leads 33 | to a state where the car overlaps the goal. 34 | 35 | An action is a move by one car in one direction (by any number of spaces). 36 | For example, here is a successor state where the AA car moves 3 to the left: 37 | 38 | | | | | | | | | 39 | | G G . . . Y | 40 | | P . . B . Y | 41 | | P * * B . Y @ 42 | | P . . B . . | 43 | | O A A . . . | 44 | | O . . . . . | 45 | | | | | | | | | 46 | 47 | And then after BBB moves 2 down and YYY moves 3 down, we can solve the puzzle 48 | by moving ** 4 spaces to the right: 49 | 50 | | | | | | | | | 51 | | G G . . . . | 52 | | P . . . . . | 53 | | P . . . . * * 54 | | P . . B . Y | 55 | | O A A B . Y | 56 | | O . . B . Y | 57 | | | | | | | | | 58 | 59 | You will write the function 60 | 61 | solve_parking_puzzle(start, N=N) 62 | 63 | where 'start' is the initial state of the puzzle and 'N' is the length of a side 64 | of the square that encloses the pieces (including the walls, so N=8 here). 65 | 66 | We will represent the grid with integer indexes. Here we see the 67 | non-wall index numbers (with the goal at index 31): 68 | 69 | | | | | | | | | 70 | | 9 10 11 12 13 14 | 71 | | 17 18 19 20 21 22 | 72 | | 25 26 27 28 29 30 31 73 | | 33 34 35 36 37 38 | 74 | | 41 42 43 44 45 46 | 75 | | 49 50 51 52 53 54 | 76 | | | | | | | | | 77 | 78 | The wall in the upper left has index 0 and the one in the lower right has 63. 79 | We represent a state of the problem with one big tuple of (object, locations) 80 | pairs, where each pair is a tuple and the locations are a tuple. Here is the 81 | initial state for the problem above in this format: 82 | """ 83 | 84 | puzzle1 = ( 85 | ('@', (31,)), 86 | ('*', (26, 27)), 87 | ('G', (9, 10)), 88 | ('Y', (14, 22, 30)), 89 | ('P', (17, 25, 33)), 90 | ('O', (41, 49)), 91 | ('B', (20, 28, 36)), 92 | ('A', (45, 46)), 93 | ('|', (0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 23, 24, 32, 39, 94 | 40, 47, 48, 55, 56, 57, 58, 59, 60, 61, 62, 63))) 95 | 96 | # A solution to this puzzle is as follows: 97 | 98 | # path = solve_parking_puzzle(puzzle1, N=8) 99 | # path_actions(path) == [('A', -3), ('B', 16), ('Y', 24), ('*', 4)] 100 | 101 | # That is, move car 'A' 3 spaces left, then 'B' 2 down, then 'Y' 3 down, 102 | # and finally '*' moves 4 spaces right to the goal. 103 | 104 | import string 105 | 106 | N = 8 107 | GOAL = 31 108 | GOAL_MARKER = '@' 109 | WALL = '|' 110 | CAR = '*' 111 | 112 | def solve_parking_puzzle(start, N=N): 113 | path = shortest_path_search(start,successors,is_goal) 114 | return path 115 | 116 | # Defines possible successor states for a given state. 117 | def successors(state,debug=False): 118 | ''' find orientation of the car 119 | find possible empty spots based on the orientation 120 | return each possible state with action. ''' 121 | successors = {} 122 | 123 | def no_collisions(new_lot): 124 | return new_lot not in occupied_lots 125 | 126 | occupied_lots = [] #inventory of occupied lots 127 | for element,position in state: 128 | if element != GOAL_MARKER: 129 | occupied_lots.extend(list(position)) 130 | if debug: 131 | print occupied_lots 132 | 133 | for index,car_state in enumerate(state): 134 | car,position = car_state 135 | if car != WALL and car != GOAL_MARKER: 136 | increment = 1 if position[-1] - position[0] < N else N # find the direction of the car 137 | new_position = move_position(position,increment) #move car in that direction 138 | if debug: 139 | print new_position 140 | while no_collisions(new_position[-1]):#reversing the car depending on how you see it 141 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 142 | new_position = move_position(new_position,increment) 143 | 144 | new_position = move_position(position,-increment) 145 | while no_collisions(new_position[0]): # forwarding the car 146 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 147 | new_position = move_position(new_position,-increment) 148 | return successors 149 | 150 | def move_position(old_position,increment): 151 | return tuple([element+increment for element in old_position]) 152 | 153 | def is_goal(state): 154 | for car,position in state: 155 | if car == CAR: 156 | return True if GOAL in position else False 157 | 158 | def locs(start, n, incr=1): 159 | "Return a tuple of n locations, starting at start and incrementing by incr." 160 | return tuple(range(start,start + incr*n,incr)) 161 | 162 | 163 | def grid(cars, N=N): 164 | """Returns a tuple of (object, locations) pairs -- the format expected for 165 | this puzzle. This function includes a wall pair, ('|', (0, ...)) to 166 | indicate there are walls all around the NxN grid, except at the goal 167 | location, which is the middle of the right-hand wall; there is a goal 168 | pair, like ('@', (31,)), to indicate this. The variable 'cars' is a 169 | tuple of pairs like ('*', (26, 27)). The return result is a big tuple 170 | of the 'cars' pairs along with the walls and goal pairs.""" 171 | grid = list() 172 | 173 | top_wall = range(0,N) 174 | left_wall = range(N,(N-1)*N,N) 175 | right_wall = range(2*N-1,(N-1)*N,N) 176 | right_wall.remove(31) 177 | bottom_wall = range((N-1)*N,N*N,1) 178 | wall = top_wall + left_wall + right_wall+ bottom_wall 179 | 180 | grid.append(('|',tuple(wall))) 181 | 182 | grid.append(('@',(31,)))# generate goal pair 31 183 | 184 | for car in cars:# determine car pair 185 | grid.append(car) 186 | 187 | return tuple(grid) 188 | 189 | 190 | def show(state, N=N): 191 | "Print a representation of a state as an NxN grid." 192 | # Initialize and fill in the board. 193 | board = ['.'] * N**2 194 | for (c, squares) in state: 195 | for s in squares: 196 | board[s] = c 197 | # Now print it out 198 | for i,s in enumerate(board): 199 | print s, 200 | if i % N == N - 1: print 201 | 202 | def shortest_path_search(start, successors, is_goal): 203 | """Find the shortest path from start state to a state 204 | such that is_goal(state) is true.""" 205 | if is_goal(start): 206 | return [start] 207 | explored = set() # set of states we have visited 208 | frontier = [ [start] ] # ordered list of paths we have blazed 209 | while frontier: 210 | path = frontier.pop(0) 211 | s = path[-1] 212 | for (state, action) in successors(s).items(): 213 | if state not in explored: 214 | explored.add(state) 215 | path2 = path + [action, state] 216 | if is_goal(state): 217 | return path2 218 | else: 219 | frontier.append(path2) 220 | return [] 221 | 222 | def path_actions(path): 223 | "Return a list of actions in this path." 224 | return path[1::2] 225 | 226 | 227 | puzzle1 = grid(( 228 | ('*', locs(26, 2)), 229 | ('G', locs(9, 2)), 230 | ('Y', locs(14, 3, N)), 231 | ('P', locs(17, 3, N)), 232 | ('O', locs(41, 2, N)), 233 | ('B', locs(20, 3, N)), 234 | ('A', locs(45, 2)))) 235 | 236 | puzzle2 = grid(( 237 | ('*', locs(26, 2)), 238 | ('B', locs(20, 3, N)), 239 | ('P', locs(33, 3)), 240 | ('O', locs(41, 2, N)), 241 | ('Y', locs(51, 3)))) 242 | 243 | puzzle3 = grid(( 244 | ('*', locs(25, 2)), 245 | ('B', locs(19, 3, N)), 246 | ('P', locs(36, 3)), 247 | ('O', locs(45, 2, N)), 248 | ('Y', locs(49, 3)))) 249 | 250 | 251 | if __name__ == '__main__': 252 | print 'Puzzle:' 253 | show(puzzle1) 254 | print 'Actions to solve : {0}'.format(path_actions(solve_parking_puzzle(puzzle1))) 255 | print 256 | print 'Puzzle:' 257 | show(puzzle2) 258 | print 'Actions to solve : {0}'.format(path_actions(solve_parking_puzzle(puzzle2))) 259 | print 260 | print 'Puzzle:' 261 | show(puzzle3) 262 | print 'Actions to solve : {0}'.format(path_actions(solve_parking_puzzle(puzzle3))) 263 | print path_actions(solve_parking_puzzle(puzzle3)) 264 | -------------------------------------------------------------------------------- /Archive/parking_lot_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | UNIT 4: Search 3 | 4 | Your task is to maneuver a car in a crowded parking lot. This is a kind of 5 | puzzle, which can be represented with a diagram like this: 6 | 7 | | | | | | | | | 8 | | G G . . . Y | 9 | | P . . B . Y | 10 | | P * * B . Y @ 11 | | P . . B . . | 12 | | O . . . A A | 13 | | O . S S S . | 14 | | | | | | | | | 15 | 16 | A '|' represents a wall around the parking lot, a '.' represents an empty square, 17 | and a letter or asterisk represents a car. '@' marks a goal square. 18 | Note that there are long (3 spot) and short (2 spot) cars. 19 | Your task is to get the car that is represented by '**' out of the parking lot 20 | (on to a goal square). Cars can move only in the direction they are pointing. 21 | In this diagram, the cars GG, AA, SSS, and ** are pointed right-left, 22 | so they can move any number of squares right or left, as long as they don't 23 | bump into another car or wall. In this diagram, GG could move 1, 2, or 3 spots 24 | to the right; AA could move 1, 2, or 3 spots to the left, and ** cannot move 25 | at all. In the up-down direction, BBB can move one up or down, YYY can move 26 | one down, and PPP and OO cannot move. 27 | 28 | You should solve this puzzle (and ones like it) using search. You will be 29 | given an initial state like this diagram and a goal location for the ** car; 30 | in this puzzle the goal is the '.' empty spot in the wall on the right side. 31 | You should return a path -- an alternation of states and actions -- that leads 32 | to a state where the car overlaps the goal. 33 | 34 | An action is a move by one car in one direction (by any number of spaces). 35 | For example, here is a successor state where the AA car moves 3 to the left: 36 | 37 | | | | | | | | | 38 | | G G . . . Y | 39 | | P . . B . Y | 40 | | P * * B . Y @ 41 | | P . . B . . | 42 | | O A A . . . | 43 | | O . . . . . | 44 | | | | | | | | | 45 | 46 | And then after BBB moves 2 down and YYY moves 3 down, we can solve the puzzle 47 | by moving ** 4 spaces to the right: 48 | 49 | | | | | | | | | 50 | | G G . . . . | 51 | | P . . . . . | 52 | | P . . . . * * 53 | | P . . B . Y | 54 | | O A A B . Y | 55 | | O . . B . Y | 56 | | | | | | | | | 57 | 58 | You will write the function 59 | 60 | solve_parking_puzzle(start, N=N) 61 | 62 | where 'start' is the initial state of the puzzle and 'N' is the length of a side 63 | of the square that encloses the pieces (including the walls, so N=8 here). 64 | 65 | We will represent the grid with integer indexes. Here we see the 66 | non-wall index numbers (with the goal at index 31): 67 | 68 | | | | | | | | | 69 | | 9 10 11 12 13 14 | 70 | | 17 18 19 20 21 22 | 71 | | 25 26 27 28 29 30 31 72 | | 33 34 35 36 37 38 | 73 | | 41 42 43 44 45 46 | 74 | | 49 50 51 52 53 54 | 75 | | | | | | | | | 76 | 77 | The wall in the upper left has index 0 and the one in the lower right has 63. 78 | We represent a state of the problem with one big tuple of (object, locations) 79 | pairs, where each pair is a tuple and the locations are a tuple. Here is the 80 | initial state for the problem above in this format: 81 | """ 82 | 83 | puzzle1 = ( 84 | ('@', (31,)), 85 | ('*', (26, 27)), 86 | ('G', (9, 10)), 87 | ('Y', (14, 22, 30)), 88 | ('P', (17, 25, 33)), 89 | ('O', (41, 49)), 90 | ('B', (20, 28, 36)), 91 | ('A', (45, 46)), 92 | ('|', (0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 23, 24, 32, 39, 93 | 40, 47, 48, 55, 56, 57, 58, 59, 60, 61, 62, 63))) 94 | 95 | # A solution to this puzzle is as follows: 96 | 97 | # path = solve_parking_puzzle(puzzle1, N=8) 98 | # path_actions(path) == [('A', -3), ('B', 16), ('Y', 24), ('*', 4)] 99 | 100 | # That is, move car 'A' 3 spaces left, then 'B' 2 down, then 'Y' 3 down, 101 | # and finally '*' moves 4 spaces right to the goal. 102 | 103 | # Your task is to define solve_parking_puzzle: 104 | N = 8 105 | 106 | def solve_parking_puzzle(start, N=N): 107 | """Solve the puzzle described by the starting position (a tuple 108 | of (object, locations) pairs). Return a path of [state, action, ...] 109 | alternating items; an action is a pair (object, distance_moved), 110 | such as ('B', 16) to move 'B' two squares down on the N=8 grid.""" 111 | path = shortest_path_search(start,successors,is_goal) 112 | return path 113 | 114 | def successors(state): 115 | # find orientation of the car 116 | # find possible empty spots based on the orientation 117 | # return each possible state with action. 118 | successors = {} 119 | 120 | def no_collisions(new_lot): 121 | return new_lot not in occupied_lots 122 | 123 | occupied_lots = [] 124 | for car_state in state: 125 | car,position = car_state 126 | if car != '@': 127 | occupied_lots.extend(list(position)) 128 | #print occupied_lots 129 | for index,car_state in enumerate(state): 130 | car,position = car_state 131 | if car != '|' and car != '@': 132 | increment = 1 if position[-1] - position[0] < N else N 133 | new_position = move_position(position,increment) 134 | #print new_position 135 | while no_collisions(new_position[-1]): 136 | #print state[:index] + (car,new_position) + state[index+1:] 137 | #print car,(car,new_position[-1] - position[-1]) 138 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 139 | new_position = move_position(new_position,increment) 140 | 141 | new_position = move_position(position,-increment) 142 | while no_collisions(new_position[0]): 143 | #print state[:index] + (car,new_position) + state[index+1:] 144 | #print car,(car,new_position[-1] - position[-1]) 145 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 146 | new_position = move_position(new_position,-increment) 147 | return successors 148 | 149 | def move_position(old_position,increment): 150 | return tuple([element+increment for element in old_position]) 151 | 152 | def is_goal(state): 153 | for car_state in state: 154 | car,position = car_state 155 | if car == '*': 156 | return True if 31 in position else False #convert 31 into a CONST 157 | 158 | # But it would also be nice to have a simpler format to describe puzzles, 159 | # and a way to visualize states. 160 | # You will do that by defining the following two functions: 161 | 162 | def locs(start, n, incr=1): 163 | "Return a tuple of n locations, starting at start and incrementing by incr." 164 | return tuple(range(start,start + incr*n,incr)) 165 | 166 | 167 | def grid(cars, N=N): 168 | """Return a tuple of (object, locations) pairs -- the format expected for 169 | this puzzle. This function includes a wall pair, ('|', (0, ...)) to 170 | indicate there are walls all around the NxN grid, except at the goal 171 | location, which is the middle of the right-hand wall; there is a goal 172 | pair, like ('@', (31,)), to indicate this. The variable 'cars' is a 173 | tuple of pairs like ('*', (26, 27)). The return result is a big tuple 174 | of the 'cars' pairs along with the walls and goal pairs.""" 175 | grid = list() 176 | 177 | top_wall = range(0,N) 178 | left_wall = range(N,(N-1)*N,N) 179 | right_wall = range(2*N-1,(N-1)*N,N) 180 | right_wall.remove(31) 181 | bottom_wall = range((N-1)*N,N*N,1) 182 | wall = top_wall + left_wall + right_wall+ bottom_wall 183 | 184 | grid.append(('|',tuple(wall))) 185 | 186 | grid.append(('@',(31,)))# generate goal pair 31 187 | 188 | for car in cars:# determine car pair 189 | grid.append(car) 190 | 191 | return tuple(grid) 192 | 193 | 194 | def show(state, N=N): 195 | "Print a representation of a state as an NxN grid." 196 | # Initialize and fill in the board. 197 | board = ['.'] * N**2 198 | for (c, squares) in state: 199 | for s in squares: 200 | board[s] = c 201 | # Now print it out 202 | for i,s in enumerate(board): 203 | print s, 204 | if i % N == N - 1: print 205 | 206 | # Here we see the grid and locs functions in use: 207 | 208 | puzzle1 = grid(( 209 | ('*', locs(26, 2)), 210 | ('G', locs(9, 2)), 211 | ('Y', locs(14, 3, N)), 212 | ('P', locs(17, 3, N)), 213 | ('O', locs(41, 2, N)), 214 | ('B', locs(20, 3, N)), 215 | ('A', locs(45, 2)))) 216 | 217 | puzzle2 = grid(( 218 | ('*', locs(26, 2)), 219 | ('B', locs(20, 3, N)), 220 | ('P', locs(33, 3)), 221 | ('O', locs(41, 2, N)), 222 | ('Y', locs(51, 3)))) 223 | 224 | puzzle3 = grid(( 225 | ('*', locs(25, 2)), 226 | ('B', locs(19, 3, N)), 227 | ('P', locs(36, 3)), 228 | ('O', locs(45, 2, N)), 229 | ('Y', locs(49, 3)))) 230 | 231 | 232 | # Here are the shortest_path_search and path_actions functions from the unit. 233 | # You may use these if you want, but you don't have to. 234 | 235 | def shortest_path_search(start, successors, is_goal): 236 | """Find the shortest path from start state to a state 237 | such that is_goal(state) is true.""" 238 | if is_goal(start): 239 | return [start] 240 | explored = set() # set of states we have visited 241 | frontier = [ [start] ] # ordered list of paths we have blazed 242 | while frontier: 243 | path = frontier.pop(0) 244 | s = path[-1] 245 | for (state, action) in successors(s).items(): 246 | if state not in explored: 247 | explored.add(state) 248 | path2 = path + [action, state] 249 | if is_goal(state): 250 | return path2 251 | else: 252 | frontier.append(path2) 253 | return [] 254 | 255 | def path_actions(path): 256 | "Return a list of actions in this path." 257 | return path[1::2] 258 | 259 | if __name__ == '__main__': 260 | #print solve_parking_puzzle(puzzle1) 261 | print path_actions(solve_parking_puzzle(puzzle1)) 262 | print solve_parking_puzzle(puzzle2) 263 | print solve_parking_puzzle(puzzle3) 264 | -------------------------------------------------------------------------------- /Archive/parking_lot_search_draft.py: -------------------------------------------------------------------------------- 1 | """ 2 | UNIT 4: Search 3 | 4 | Your task is to maneuver a car in a crowded parking lot. This is a kind of 5 | puzzle, which can be represented with a diagram like this: 6 | 7 | | | | | | | | | 8 | | G G . . . Y | 9 | | P . . B . Y | 10 | | P * * B . Y @ 11 | | P . . B . . | 12 | | O . . . A A | 13 | | O . S S S . | 14 | | | | | | | | | 15 | 16 | A '|' represents a wall around the parking lot, a '.' represents an empty square, 17 | and a letter or asterisk represents a car. '@' marks a goal square. 18 | Note that there are long (3 spot) and short (2 spot) cars. 19 | Your task is to get the car that is represented by '**' out of the parking lot 20 | (on to a goal square). Cars can move only in the direction they are pointing. 21 | In this diagram, the cars GG, AA, SSS, and ** are pointed right-left, 22 | so they can move any number of squares right or left, as long as they don't 23 | bump into another car or wall. In this diagram, GG could move 1, 2, or 3 spots 24 | to the right; AA could move 1, 2, or 3 spots to the left, and ** cannot move 25 | at all. In the up-down direction, BBB can move one up or down, YYY can move 26 | one down, and PPP and OO cannot move. 27 | 28 | You should solve this puzzle (and ones like it) using search. You will be 29 | given an initial state like this diagram and a goal location for the ** car; 30 | in this puzzle the goal is the '.' empty spot in the wall on the right side. 31 | You should return a path -- an alternation of states and actions -- that leads 32 | to a state where the car overlaps the goal. 33 | 34 | An action is a move by one car in one direction (by any number of spaces). 35 | For example, here is a successor state where the AA car moves 3 to the left: 36 | 37 | | | | | | | | | 38 | | G G . . . Y | 39 | | P . . B . Y | 40 | | P * * B . Y @ 41 | | P . . B . . | 42 | | O A A . . . | 43 | | O . . . . . | 44 | | | | | | | | | 45 | 46 | And then after BBB moves 2 down and YYY moves 3 down, we can solve the puzzle 47 | by moving ** 4 spaces to the right: 48 | 49 | | | | | | | | | 50 | | G G . . . . | 51 | | P . . . . . | 52 | | P . . . . * * 53 | | P . . B . Y | 54 | | O A A B . Y | 55 | | O . . B . Y | 56 | | | | | | | | | 57 | 58 | You will write the function 59 | 60 | solve_parking_puzzle(start, N=N) 61 | 62 | where 'start' is the initial state of the puzzle and 'N' is the length of a side 63 | of the square that encloses the pieces (including the walls, so N=8 here). 64 | 65 | We will represent the grid with integer indexes. Here we see the 66 | non-wall index numbers (with the goal at index 31): 67 | 68 | | | | | | | | | 69 | | 9 10 11 12 13 14 | 70 | | 17 18 19 20 21 22 | 71 | | 25 26 27 28 29 30 31 72 | | 33 34 35 36 37 38 | 73 | | 41 42 43 44 45 46 | 74 | | 49 50 51 52 53 54 | 75 | | | | | | | | | 76 | 77 | The wall in the upper left has index 0 and the one in the lower right has 63. 78 | We represent a state of the problem with one big tuple of (object, locations) 79 | pairs, where each pair is a tuple and the locations are a tuple. Here is the 80 | initial state for the problem above in this format: 81 | """ 82 | # TODO: Convert the ordinal positions of wall and car to fixed. 83 | 84 | puzzle1 = ( 85 | ('@', (31,)), 86 | ('*', (26, 27)), 87 | ('G', (9, 10)), 88 | ('Y', (14, 22, 30)), 89 | ('P', (17, 25, 33)), 90 | ('O', (41, 49)), 91 | ('B', (20, 28, 36)), 92 | ('A', (45, 46)), 93 | ('|', (0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 23, 24, 32, 39, 94 | 40, 47, 48, 55, 56, 57, 58, 59, 60, 61, 62, 63))) 95 | 96 | # A solution to this puzzle is as follows: 97 | 98 | # path = solve_parking_puzzle(puzzle1, N=8) 99 | # path_actions(path) == [('A', -3), ('B', 16), ('Y', 24), ('*', 4)] 100 | 101 | # That is, move car 'A' 3 spaces left, then 'B' 2 down, then 'Y' 3 down, 102 | # and finally '*' moves 4 spaces right to the goal. 103 | 104 | # Your task is to define solve_parking_puzzle: 105 | 106 | N = 8 107 | 108 | def solve_parking_puzzle(start, N=N): 109 | """Solve the puzzle described by the starting position (a tuple 110 | of (object, locations) pairs). Return a path of [state, action, ...] 111 | alternating items; an action is a pair (object, distance_moved), 112 | such as ('B', 16) to move 'B' two squares down on the N=8 grid.""" 113 | path = shortest_path_search(start,successors,is_goal) 114 | #print path 115 | for state in path[0::2]: 116 | #print state 117 | show(state) 118 | print 119 | return path_actions(path) 120 | 121 | # But it would also be nice to have a simpler format to describe puzzles, 122 | # and a way to visualize states. 123 | # You will do that by defining the following two functions: 124 | 125 | def locs(start, n, incr=1): 126 | "Return a tuple of n locations, starting at start and incrementing by incr." 127 | return tuple(range(start,start + incr*n,incr)) 128 | 129 | 130 | def grid(cars, N=N): 131 | """Return a tuple of (object, locations) pairs -- the format expected for 132 | this puzzle. This function includes a wall pair, ('|', (0, ...)) to 133 | indicate there are walls all around the NxN grid, except at the goal 134 | location, which is the middle of the right-hand wall; there is a goal 135 | pair, like ('@', (31,)), to indicate this. The variable 'cars' is a 136 | tuple of pairs like ('*', (26, 27)). The return result is a big tuple 137 | of the 'cars' pairs along with the walls and goal pairs.""" 138 | grid = list() 139 | 140 | top_wall = range(0,N) 141 | left_wall = range(N,(N-1)*N,N) 142 | right_wall = range(2*N-1,(N-1)*N,N) 143 | right_wall.remove(31) 144 | bottom_wall = range((N-1)*N,N*N,1) 145 | wall = top_wall + left_wall + right_wall+ bottom_wall 146 | 147 | grid.append(('|',tuple(wall))) 148 | 149 | grid.append(('@',(31,)))# generate goal pair 31 150 | 151 | for car in cars:# determine car pair 152 | grid.append(car) 153 | 154 | return tuple(grid) 155 | 156 | def show(state, N=N): 157 | "Print a representation of a state as an NxN grid." 158 | # Initialize and fill in the board. 159 | board = ['.'] * N**2 160 | for (c, squares) in state: 161 | for s in squares: 162 | board[s] = c 163 | # Now print it out 164 | for i,s in enumerate(board): 165 | print s, 166 | if i % N == N - 1: print 167 | 168 | # Here we see the grid and locs functions in use: 169 | 170 | puzzle2 = grid(( 171 | ('*', locs(26, 2)), 172 | ('G', locs(9, 2)), 173 | ('Y', locs(14, 3, N)), 174 | ('P', locs(17, 3, N)), 175 | ('O', locs(41, 2, N)), 176 | ('B', locs(20, 3, N)), 177 | ('A', locs(45, 2)))) 178 | 179 | puzzle3 = grid(( 180 | ('*', locs(26, 2)), 181 | ('B', locs(20, 3, N)), 182 | ('P', locs(33, 3)), 183 | ('O', locs(41, 2, N)), 184 | ('Y', locs(51, 3)))) 185 | 186 | puzzle4 = grid(( 187 | ('*', locs(25, 2)), 188 | ('B', locs(19, 3, N)), 189 | ('P', locs(36, 3)), 190 | ('O', locs(45, 2, N)), 191 | ('Y', locs(49, 3)))) 192 | 193 | def successors(state): 194 | # find orientation of the car 195 | # find possible empty spots based on the orientation 196 | # return each possible state with action. 197 | successors = {} 198 | 199 | def no_collisions(new_lot): 200 | return new_lot not in occupied_lots 201 | 202 | occupied_lots = [] 203 | for car_state in state: 204 | car,position = car_state 205 | if car != '@': 206 | occupied_lots.extend(list(position)) 207 | #print occupied_lots 208 | for index,car_state in enumerate(state): 209 | car,position = car_state 210 | if car != '|' and car != '@': 211 | increment = 1 if position[-1] - position[0] < N else N 212 | new_position = move_position(position,increment) 213 | #print new_position 214 | while no_collisions(new_position[-1]): 215 | #print state[:index] + (car,new_position) + state[index+1:] 216 | #print car,(car,new_position[-1] - position[-1]) 217 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 218 | new_position = move_position(new_position,increment) 219 | 220 | new_position = move_position(position,-increment) 221 | while no_collisions(new_position[0]): 222 | #print state[:index] + (car,new_position) + state[index+1:] 223 | #print car,(car,new_position[-1] - position[-1]) 224 | successors.update({(state[:index] + ((car,new_position),) + state[index+1:]):(car,new_position[-1] - position[-1])}) 225 | new_position = move_position(new_position,-increment) 226 | return successors 227 | 228 | def move_position(old_position,increment): 229 | return tuple([element+increment for element in old_position]) 230 | 231 | def is_goal(state): 232 | for car_state in state: 233 | car,position = car_state 234 | if car == '*': 235 | return True if 31 in position else False #convert 31 into a CONST 236 | 237 | # Here are the shortest_path_search and path_actions functions from the unit. 238 | # You may use these if you want, but you don't have to. 239 | 240 | def shortest_path_search(start, successors, is_goal): 241 | """Find the shortest path from start state to a state 242 | such that is_goal(state) is true.""" 243 | if is_goal(start): 244 | return [start] 245 | explored = set() # set of states we have visited 246 | frontier = [ [start] ] # ordered list of paths we have blazed 247 | while frontier: 248 | path = frontier.pop(0) 249 | s = path[-1] 250 | for (state, action) in successors(s).items(): 251 | if state not in explored: 252 | explored.add(state) 253 | path2 = path + [action, state] 254 | if is_goal(state): 255 | return path2 256 | else: 257 | frontier.append(path2) 258 | return [] 259 | 260 | def path_actions(path): 261 | "Return a list of actions in this path." 262 | return path[1::2] 263 | 264 | def test_unit(): 265 | assert locs(26,5,1) == (26,27,28,29,30) 266 | assert locs(19, 3, N) == (19,27,35) 267 | assert set((puzzle1[-1])[-1]) == set((puzzle2[0])[-1]) # comparing the wall elements as that is the controversial and there is no easy way to compare tuple that have been assembled differently 268 | initial_state = ( 269 | ('@', (31,)), 270 | ('*', (26, 27)), 271 | ('G', (9, 10)), 272 | ('Y', (14, 22, 30)), 273 | ('P', (17, 25, 33)), 274 | ('O', (41, 49)), 275 | ('B', (20, 28, 36)), 276 | ('A', (45, 46)), 277 | ('|', (0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 23, 24, 32, 39, 278 | 40, 47, 48, 55, 56, 57, 58, 59, 60, 61, 62, 63))) 279 | successors(initial_state) 280 | #assert solve_parking_puzzle(initial_state) == [('A', -3), ('B', 16), ('Y', 24), ('*', 4)] 281 | print solve_parking_puzzle(puzzle2) 282 | print solve_parking_puzzle(puzzle3) 283 | print solve_parking_puzzle(puzzle4) 284 | return 'tests pass' 285 | 286 | if __name__ == '__main__': 287 | print test_unit() 288 | -------------------------------------------------------------------------------- /Archive/polynomials.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | UNIT 3: Functions and APIs: Polynomials 4 | 5 | A polynomial is a mathematical formula like: 6 | 7 | 30 * x**2 + 20 * x + 10 8 | 9 | More formally, it involves a single variable (here 'x'), and the sum of one 10 | or more terms, where each term is a real number multiplied by the variable 11 | raised to a non-negative integer power. (Remember that x**0 is 1 and x**1 is x, 12 | so 'x' is short for '1 * x**1' and '10' is short for '10 * x**0'.) 13 | 14 | We will represent a polynomial as a Python function which computes the formula 15 | when applied to a numeric value x. The function will be created with the call: 16 | 17 | p1 = poly((10, 20, 30)) 18 | 19 | where the nth element of the input tuple is the coefficient of the nth power of x. 20 | (Note the order of coefficients has the x**n coefficient neatly in position n of 21 | the list, but this is the reversed order from how we usually write polynomials.) 22 | poly returns a function, so we can now apply p1 to some value of x: 23 | 24 | p1(0) == 10 25 | 26 | Our representation of a polynomial is as a callable function, but in addition, 27 | we will store the coefficients in the .coefs attribute of the function, so we have: 28 | 29 | p1.coefs == (10, 20, 30) 30 | 31 | And finally, the name of the function will be the formula given above, so you should 32 | have something like this: 33 | 34 | >>> p1 35 | 36 | 37 | >>> p1.__name__ 38 | '30 * x**2 + 20 * x + 10' 39 | 40 | Make sure the formula used for function names is simplified properly. 41 | No '0 * x**n' terms; just drop these. Simplify '1 * x**n' to 'x**n'. 42 | Simplify '5 * x**0' to '5'. Similarly, simplify 'x**1' to 'x'. 43 | For negative coefficients, like -5, you can use '... + -5 * ...' or 44 | '... - 5 * ...'; your choice. I'd recommend no spaces around '**' 45 | and spaces around '+' and '*', but you are free to use your preferences. 46 | 47 | Your task is to write the function poly and the following additional functions: 48 | 49 | is_poly, add, sub, mul, power, deriv, integral 50 | 51 | They are described below; see the test_poly function for examples. 52 | """ 53 | import re 54 | 55 | dict_simplify = { 56 | r'(\d+)\s\*\sx\*{2}0':r'\1',#this should not have spaces . The base function should be using canonical names. 57 | r'(x)\*{2}1\b':r'\1', # replaced 'x' to [a-z] 58 | r'\b1\s\*\s(x\*{2}\d)':r'\1', 59 | r'x\*{2}0':'1' 60 | } 61 | # refactor this. 62 | def canonical_poly(coefs): 63 | terms = [0]*len([coef for coef in coefs if coef != 0]) 64 | for position,coef in enumerate(coefs): 65 | if coef != 0: 66 | terms[len(coefs) - position -1] = simplify('{0} * x**{1}'.format(coef,position))#This constructs and simplify. JUST don't construct 67 | statement = ' + '.join(terms) 68 | return statement 69 | 70 | def poly(coefs): 71 | """Return a function that represents the polynomial with these coefficients. 72 | For example, if coefs=(10, 20, 30), return the function of x that computes 73 | '30 * x**2 + 20 * x + 10'. Also store the coefs on the .coefs attribute of 74 | the function, and the str of the formula on the .__name__ attribute.'""" 75 | statement = canonical_poly(coefs) 76 | poly_lambda = eval('lambda x: {0}'.format(statement)) 77 | def poly_wrapper(x): 78 | return poly_lambda(x) 79 | poly_wrapper.__name__ = statement 80 | poly_wrapper.coefs = coefs 81 | return poly_wrapper 82 | 83 | def simplify(expression): 84 | for pattern in dict_simplify: 85 | if(re.search(pattern,expression)): 86 | expression = re.sub(pattern,r'%s'%(dict_simplify[pattern]),expression) 87 | return expression 88 | 89 | def canonical_name(name): 90 | return name.replace(' ', '').replace('+-', '-') 91 | 92 | def unit_test_poly(): 93 | #assert poly((10,20,30)) == '30 * x**2 + 20 * x + 10' 94 | p1 = poly((10,20,30)) 95 | assert p1(0) == 10 96 | assert p1.__name__ == '30 * x**2 + 20 * x + 10' 97 | assert p1.coefs == (10,20,30) 98 | print 'unit tests pass' 99 | 100 | def test_poly(): 101 | global p1, p2, p3, p4, p5, p9 # global to ease debugging in an interactive session 102 | 103 | p1 = poly((10, 20, 30)) 104 | assert p1(0) == 10 105 | for x in (1, 2, 3, 4, 5, 1234.5): 106 | assert p1(x) == 30 * x**2 + 20 * x + 10 107 | assert same_name(p1.__name__, '30 * x**2 + 20 * x + 10') 108 | 109 | assert is_poly(p1) 110 | assert not is_poly(abs) and not is_poly(42) and not is_poly('cracker') 111 | 112 | p3 = poly((0, 0, 0, 1)) 113 | assert p3.__name__ == 'x**3' 114 | p9 = mul(p3, mul(p3, p3)) 115 | assert p9(2) == 512 116 | p4 = add(p1, p3) 117 | assert same_name(p4.__name__, 'x**3 + 30 * x**2 + 20 * x + 10') 118 | assert same_name(poly((1, 1)).__name__, 'x + 1') 119 | assert same_name(power(poly((1, 1)), 10).__name__, 120 | 'x**10 + 10 * x**9 + 45 * x**8 + 120 * x**7 + 210 * x**6 + 252 * x**5 + 210' + 121 | ' * x**4 + 120 * x**3 + 45 * x**2 + 10 * x + 1') 122 | 123 | assert add(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (11,22,33) 124 | assert sub(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (9,18,27) 125 | assert mul(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (10, 40, 100, 120, 90) 126 | assert power(poly((1, 1)), 2).coefs == (1, 2, 1) 127 | assert power(poly((1, 1)), 10).coefs == (1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1) 128 | assert deriv(p1).coefs == (20, 60) 129 | assert integral(poly((20, 60))).coefs == (0, 20, 30) 130 | p5 = poly((0, 1, 2, 3, 4, 5)) 131 | assert same_name(p5.__name__, '5 * x**5 + 4 * x**4 + 3 * x**3 + 2 * x**2 + x') 132 | assert p5(1) == 15 133 | assert p5(2) == 258 134 | assert same_name(deriv(p5).__name__, '25 * x**4 + 16 * x**3 + 9 * x**2 + 4 * x + 1') 135 | assert deriv(p5)(1) == 55 136 | assert deriv(p5)(2) == 573 137 | print 'tests pass' 138 | 139 | 140 | def same_name(name1, name2): 141 | """I define this function rather than doing name1 == name2 to allow for some 142 | variation in naming conventions.""" 143 | def canonical_name(name): return name.replace(' ', '').replace('+-', '-') 144 | return canonical_name(name1) == canonical_name(name2) 145 | 146 | def is_poly(x): 147 | "Return true if x is a poly (polynomial)." 148 | if not (hasattr(x,'__call__')): # is a function 149 | return False 150 | if not (hasattr(x,'__name__')): # has name attr. because my poly() function will have the expression in the name 151 | return False 152 | poly = canonical_name(x.__name__) # strip and return in standard form 153 | poly_pattern = r'\b(\d+\*)?[a-z](\*+\d+)?\b' # this should move as global and should comply to by the canonical_poly() 154 | return True if re.match(poly_pattern,poly) else False 155 | 156 | def add(p1, p2): 157 | "Return a new polynomial which is the sum of polynomials p1 and p2." 158 | # just add coef 159 | # construct the poly 160 | # write a separate function - Poly() already has this function that has a flaw to construct and simplify. JUST don't construct 161 | p1_coefs = p1.coefs 162 | p2_coefs = p2.coefs 163 | summed_coefs = coef_operation(p1_coefs,p2_coefs,'+') 164 | statement = canonical_poly(summed_coefs) 165 | poly_lambda = eval('lambda x: {0}'.format(statement)) 166 | def poly_wrapper(x): 167 | return poly_lambda(x) 168 | poly_wrapper.__name__ = statement 169 | poly_wrapper.coefs = summed_coefs 170 | return poly_wrapper 171 | 172 | 173 | def coef_operation(p1,p2,symbol): 174 | new_coefs = list() 175 | if symbol == '+': 176 | if len(p1) > len(p2): 177 | for index,coef in enumerate(p2): 178 | new_coefs.append(p1[index] +p2[index]) 179 | new_coefs += p1[len(p2):] 180 | else: 181 | for index,coef in enumerate(p1): 182 | new_coefs.append(p1[index] +p2[index]) 183 | new_coefs += p2[len(p1):] 184 | return tuple(new_coefs) 185 | 186 | 187 | def sub(p1, p2): 188 | "Return a new polynomial which is the difference of polynomials p1 and p2." 189 | p1_coefs = p1.coefs 190 | p2_coefs = tuple([-e for e in p2.coefs]) 191 | summed_coefs = coef_operation(p1_coefs,p2_coefs,'+') 192 | statement = canonical_poly(summed_coefs) 193 | poly_lambda = eval('lambda x: {0}'.format(statement)) 194 | def poly_wrapper(x): 195 | return poly_lambda(x) 196 | poly_wrapper.__name__ = statement 197 | poly_wrapper.coefs = summed_coefs 198 | return poly_wrapper 199 | 200 | 201 | def mul(p1, p2): 202 | "Return a new polynomial which is the product of polynomials p1 and p2." 203 | p1_coefs = p1.coefs 204 | p2_coefs = p2.coefs 205 | list_coefs = list() 206 | for index,p1_coef in enumerate(p1_coefs): 207 | list_coefs.append([0]*index + [p1_coef*p2_coef for p2_coef in p2_coefs]) 208 | mul_coefs = list() 209 | for coefs in list_coefs:# iterate and add with empty list 210 | mul_coefs = coef_operation(coefs,mul_coefs,'+') 211 | statement = canonical_poly(mul_coefs) 212 | poly_lambda = eval('lambda x: {0}'.format(statement)) 213 | def poly_wrapper(x): 214 | return poly_lambda(x) 215 | poly_wrapper.__name__ = statement 216 | poly_wrapper.coefs = mul_coefs 217 | return poly_wrapper 218 | 219 | 220 | def power(p, n): 221 | "Return a new polynomial which is p to the nth power (n a non-negative integer)." 222 | power_poly = poly((1,)) 223 | for _ in range(0,n): 224 | power_poly = mul(power_poly,p) 225 | return power_poly 226 | 227 | """ 228 | If your calculus is rusty (or non-existant), here is a refresher: 229 | The deriviative of a polynomial term (c * x**n) is (c*n * x**(n-1)). 230 | The derivative of a sum is the sum of the derivatives. 231 | So the derivative of (30 * x**2 + 20 * x + 10) is (60 * x + 20). 232 | 233 | The integral is the anti-derivative: 234 | The integral of 60 * x + 20 is 30 * x**2 + 20 * x + C, for any constant C. 235 | Any value of C is an equally good anti-derivative. We allow C as an argument 236 | to the function integral (withh default C=0). 237 | """ 238 | 239 | def deriv(p): 240 | "Return the derivative of a function p (with respect to its argument)." 241 | coefs = p.coefs 242 | derivative_coefs = list() 243 | for index,coef in enumerate(coefs): 244 | if index != 0: 245 | derivative_coefs.append(coef * index) 246 | statement = canonical_poly(derivative_coefs) 247 | poly_lambda = eval('lambda x: {0}'.format(statement)) 248 | def poly_wrapper(x): 249 | return poly_lambda(x) 250 | poly_wrapper.__name__ = statement 251 | poly_wrapper.coefs = tuple(derivative_coefs) 252 | return poly_wrapper 253 | 254 | def integral(p, C=0): 255 | "Return the integral of a function p (with respect to its argument)." 256 | coefs = p.coefs 257 | integral_coefs = [C] 258 | for index,coef in enumerate(coefs): 259 | integral_coefs.append(coef/(index+1)) 260 | statement = canonical_poly(integral_coefs) 261 | poly_lambda = eval('lambda x: {0}'.format(statement)) 262 | def poly_wrapper(x): 263 | return poly_lambda(x) 264 | poly_wrapper.__name__ = statement 265 | poly_wrapper.coefs = tuple(integral_coefs) 266 | return poly_wrapper 267 | 268 | 269 | """ 270 | Now for an extra credit challenge: arrange to describe polynomials with an 271 | expression like '3 * x**2 + 5 * x + 9' rather than (9, 5, 3). You can do this 272 | in one (or both) of two ways: 273 | 274 | (1) By defining poly as a class rather than a function, and overloading the 275 | __add__, __sub__, __mul__, and __pow__ operators, etc. If you choose this, 276 | call the function test_poly1(). Make sure that poly objects can still be called. 277 | 278 | (2) Using the grammar parsing techniques we learned in Unit 5. For this 279 | approach, define a new function, Poly, which takes one argument, a string, 280 | as in Poly('30 * x**2 + 20 * x + 10'). Call test_poly2(). 281 | """ 282 | 283 | 284 | def test_poly1(): 285 | # I define x as the polynomial 1*x + 0. 286 | x = poly((0, 1)) 287 | # From here on I can create polynomials by + and * operations on x. 288 | newp1 = 30 * x**2 + 20 * x + 10 # This is a poly object, not a number! 289 | assert p1(100) == newp1(100) # The new poly objects are still callable. 290 | assert same_name(p1.__name__,newp1.__name__) 291 | assert (x + 1) * (x - 1) == x**2 - 1 == poly((-1, 0, 1)) 292 | 293 | def test_poly2(): 294 | newp1 = Poly('30 * x**2 + 20 * x + 10') 295 | assert p1(100) == newp1(100) 296 | assert same_name(p1.__name__,newp1.__name__) 297 | 298 | if __name__ == '__main__': 299 | #unit_test_poly() 300 | test_poly() 301 | -------------------------------------------------------------------------------- /darts_probability.py: -------------------------------------------------------------------------------- 1 | # Unit 5: Probability in the game of Darts 2 | 3 | """ 4 | In the game of darts, players throw darts at a board to score points. 5 | The circular board has a 'bulls-eye' in the center and 20 slices 6 | called sections, numbered 1 to 20, radiating out from the bulls-eye. 7 | The board is also divided into concentric rings. The bulls-eye has 8 | two rings: an outer 'single' ring and an inner 'double' ring. Each 9 | section is divided into 4 rings: starting at the center we have a 10 | thick single ring, a thin triple ring, another thick single ring, and 11 | a thin double ring. A ring/section combination is called a 'target'; 12 | they have names like 'S20', 'D20' and 'T20' for single, double, and 13 | triple 20, respectively; these score 20, 40, and 60 points. The 14 | bulls-eyes are named 'SB' and 'DB', worth 25 and 50 points 15 | respectively. Illustration (png image): http://goo.gl/i7XJ9 16 | 17 | There are several variants of darts play; in the game called '501', 18 | each player throws three darts per turn, adding up points until they 19 | total exactly 501. However, the final dart must be in a double ring. 20 | 21 | Your first task is to write the function double_out(total), which will 22 | output a list of 1 to 3 darts that add up to total, with the 23 | restriction that the final dart is a double. See test_darts() for 24 | examples. Return None if there is no list that achieves the total. 25 | 26 | Often there are several ways to achieve a total. You must return a 27 | shortest possible list, but you have your choice of which one. For 28 | example, for total=100, you can choose ['T20', 'D20'] or ['DB', 'DB'] 29 | but you cannot choose ['T20', 'D10', 'D10']. 30 | """ 31 | 32 | def test_darts(): 33 | "Test the double_out function." 34 | assert double_out(170) == ['T20', 'T20', 'DB'] 35 | assert double_out(171) == None 36 | assert double_out(100) in (['T20', 'D20'], ['DB', 'DB']) # Could be problem area. I am not specifically looking for this. That is why i should start with 0,60,59.. 37 | print 'test_darts pass' 38 | 39 | """ 40 | My strategy: I decided to choose the result that has the highest valued 41 | target(s) first, e.g. always take T20 on the first dart if we can achieve 42 | a solution that way. If not, try T19 first, and so on. At first I thought 43 | I would need three passes: first try to solve with one dart, then with two, 44 | then with three. But I realized that if we include 0 as a possible dart 45 | value, and always try the 0 first, then we get the effect of having three 46 | passes, but we only have to code one pass. So I creted ordered_points as 47 | a list of all possible scores that a single dart can achieve, with 0 first, 48 | and then descending: [0, 60, 57, ..., 1]. I iterate dart1 and dart2 over 49 | that; then dart3 must be whatever is left over to add up to total. If 50 | dart3 is a valid element of points, then we have a solution. But the 51 | solution, is a list of numbers, like [0, 60, 40]; we need to transform that 52 | into a list of target names, like ['T20', 'D20'], we do that by defining name(d) 53 | to get the name of a target that scores d. When there are several choices, 54 | we must choose a double for the last dart, but for the others I prefer the 55 | easiest targets first: 'S' is easiest, then 'T', then 'D'. 56 | """ 57 | 58 | 59 | def double_out(total): 60 | """Return a shortest possible list of targets that add to total, 61 | where the length <= 3 and the final element is a double. 62 | If there is no solution, return None.""" 63 | single = range(1,21) 64 | double = [score * 2 for score in single] + [50] 65 | triple = [score * 3 for score in single] 66 | bulls_eye = [25,50] 67 | points = [] 68 | dart_scores = [0] + sorted(set(triple + double + single + bulls_eye),reverse = True) 69 | #print dart_scores 70 | possible_ways = [(dart1,dart2,dart3) 71 | for dart1 in dart_scores 72 | for dart2 in dart_scores 73 | for dart3 in double 74 | if dart1+dart2+dart3 == total] 75 | if len(possible_ways) > 0: 76 | for score in possible_ways[0]: # assuming [0] to be the best possible because the scores are sorted in reverse which internally assumes the player hit the triple points in the first attempt 77 | if score != 0: 78 | if score in triple: 79 | points.append('T' + str(score/3)) 80 | elif score in bulls_eye: 81 | points.append('SB' if score == 25 else 'DB') 82 | elif score in double: 83 | points.append('D' + str(score/2)) 84 | else: 85 | points.append('S' + str(score)) 86 | return points if len(points) > 0 else None 87 | 88 | 89 | 90 | """ 91 | It is easy enough to say "170 points? Easy! Just hit T20, T20, DB." 92 | But, at least for me, it is much harder to actually execute the plan 93 | and hit each target. In this second half of the question, we 94 | investigate what happens if the dart-thrower is not 100% accurate. 95 | 96 | We will use a wrong (but still useful) model of inaccuracy. A player 97 | has a single number from 0 to 1 that characterizes his/her miss rate. 98 | If miss=0.0, that means the player hits the target every time. 99 | But if miss is, say, 0.1, then the player misses the section s/he 100 | is aiming at 10% of the time, and also (independently) misses the thin 101 | double or triple ring 10% of the time. Where do the misses go? 102 | Here's the model: 103 | 104 | First, for ring accuracy. If you aim for the triple ring, all the 105 | misses go to a single ring (some to the inner one, some to the outer 106 | one, but the model doesn't distinguish between these). If you aim for 107 | the double ring (at the edge of the board), half the misses (e.g. 0.05 108 | if miss=0.1) go to the single ring, and half off the board. (We will 109 | agree to call the off-the-board 'target' by the name 'OFF'.) If you 110 | aim for a thick single ring, it is about 5 times thicker than the thin 111 | rings, so your miss ratio is reduced to 1/5th, and of these, half go to 112 | the double ring and half to the triple. So with miss=0.1, 0.01 will go 113 | to each of the double and triple ring. Finally, for the bulls-eyes. If 114 | you aim for the single bull, 1/4 of your misses go to the double bull and 115 | 3/4 to the single ring. If you aim for the double bull, it is tiny, so 116 | your miss rate is tripled; of that, 2/3 goes to the single ring and 1/3 117 | to the single bull ring. 118 | 119 | Now, for section accuracy. Half your miss rate goes one section clockwise 120 | and half one section counter-clockwise from your target. The clockwise 121 | order of sections is: 122 | 123 | 20 1 18 4 13 6 10 15 2 17 3 19 7 16 8 11 14 9 12 5 124 | 125 | If you aim for the bull (single or double) and miss on rings, then the 126 | section you end up on is equally possible among all 20 sections. But 127 | independent of that you can also miss on sections; again such a miss 128 | is equally likely to go to any section and should be recorded as being 129 | in the single ring. 130 | 131 | You will need to build a model for these probabilities, and define the 132 | function outcome(target, miss), which takes a target (like 'T20') and 133 | a miss ration (like 0.1) and returns a dict of {target: probability} 134 | pairs indicating the possible outcomes. You will also define 135 | best_target(miss) which, for a given miss ratio, returns the target 136 | with the highest expected score. 137 | 138 | If you are very ambitious, you can try to find the optimal strategy for 139 | accuracy-limited darts: given a state defined by your total score 140 | needed and the number of darts remaining in your 3-dart turn, return 141 | the target that minimizes the expected number of total 3-dart turns 142 | (not the number of darts) required to reach the total. This is harder 143 | than Pig for several reasons: there are many outcomes, so the search space 144 | is large; also, it is always possible to miss a double, and thus there is 145 | no guarantee that the game will end in a finite number of moves. 146 | """ 147 | ZONES = {'S':1,'D':2,'T':3} 148 | SECTIONS = ['20', '1', '18', '4', '13', '6', '10', '15', '2', '17', '3', '19', '7', '16', '8', '11', '14', '9', '12','5'] 149 | RINGS = ['OFF','D','S','T','S','SB','DB'] 150 | 151 | def get_adjacents(cell,items): 152 | if cell == None: 153 | return items 154 | else: 155 | for index,item in enumerate(items): 156 | if item == cell: 157 | return [items[index-1],items[index],items[index+1]] 158 | 159 | def get_adjacent_sections(section): 160 | if section == None: 161 | return SECTIONS 162 | else: 163 | index = SECTIONS.index(section) 164 | return [SECTIONS[index-1],SECTIONS[index],SECTIONS[(index+1)%len(SECTIONS)]] 165 | 166 | def get_adjacent_rings(ring): 167 | index = RINGS.index(ring) 168 | if index == 0: #OFF 169 | return [RINGS[index],RINGS[index+1],RINGS[(index+2)]] 170 | elif index == len(RINGS) - 1: #DB 171 | return [RINGS[index-2],RINGS[index-1],RINGS[(index)]] 172 | else: 173 | return [RINGS[index-1],RINGS[index],RINGS[(index+1)]] 174 | 175 | def get_neighbors(target): 176 | ring,section = get_ring(target),get_section(target) 177 | if section == None: 178 | return SECTIONS 179 | else: 180 | return [ r+s for r in get_adjacents(ring,RINGS) for s in get_adjacents(section,SECTIONS)] 181 | 182 | def get_ring(target): 183 | if target in ('SB','DB','OFF'): 184 | return target 185 | else: 186 | return target[0] 187 | 188 | def get_section(target): 189 | if target in ('SB','DB','OFF'): 190 | return None 191 | else: 192 | return target[1:] 193 | 194 | def prob_test(): 195 | T20 = DandT_miss_prob('T20',0.1) 196 | assert T20.Prob.topProb == 0.05 197 | assert T20.Target.zone == 'T' 198 | assert int(T20.Target.sector) == 20 199 | S18 = S_miss_prob('S10',0.02) 200 | assert S18.Prob.prob == 0.004 201 | SB = Bull('SB') 202 | assert SB.sector == 'B' 203 | assert SB.target == 'SB' 204 | assert SB.zone == 'S' 205 | SB = B_miss_prob('SB',0.5) 206 | assert SB.Prob.topProb == 0.375 207 | assert SB.Prob.bottomProb == 0.125 208 | #assert SB.Prob.leftProb == SB.Prob.rightProb == 0.25 209 | DB = B_miss_prob('DB',0.5) 210 | assert DB.Prob.top1 == 0.49995 211 | assert get_adjacents('T',RINGS) == ['S','T','S'] 212 | assert get_adjacents('D',RINGS) == ['OFF','D','S'] 213 | assert get_adjacents('20',SECTIONS) == ['5','20','1'] 214 | assert set(get_neighbors('T20')) == set(['T20','T5','T1','S1','S5','S20']) 215 | assert len(get_neighbors('T20')) == 9 216 | assert same_outcome(outcome('T20', 0.1), 217 | {'T20': 0.81, 'S1': 0.005, 'T5': 0.045, 218 | 'S5': 0.005, 'T1': 0.045, 'S20': 0.09}) 219 | print 'prob tests pass' 220 | 221 | def outcome(target, miss): 222 | "Return a probability distribution of [(target, probability)] pairs." 223 | target_ring = get_ring(target) 224 | target_section = get_section(target) 225 | sections = get_adjacent_sections(target_section) #gets all sections 226 | rings = get_adjacent_rings(target_ring) 227 | miss_conversion = {'S':miss/5.0,'DB':miss * 3.0} 228 | if target_ring in miss_conversion: 229 | miss = miss_conversion[target_ring] 230 | ring_prob_dist = {'S':(miss*0.5,1.0-miss,miss*0.5),'D': (miss*0.5,1.0-miss,miss*0.5),'T':(miss*0.5,1.0-miss,miss*0.5),'SB':((1.0 - miss),miss*0.25),'DB':(miss/3,1.0 - miss)} # take this out 231 | section_prob_dist = {'S':(miss*0.5,1.0-miss,miss*0.5),'T':(miss*0.5,1.0-miss,miss*0.5),'D':(miss*0.5,1.0-miss,miss*0.5)} 232 | prob_dist = {} 233 | ring_probs = ring_prob_dist[target_ring] 234 | if target_ring in ('SB','DB'): 235 | section_prob = 1.0 - miss 236 | prob_dist['SB'] = round(ring_probs[0] * section_prob,4) 237 | prob_dist['DB'] = round(ring_probs[1] * section_prob,4) 238 | remaining_prob = 1.0 - (prob_dist['SB'] + prob_dist['DB']) 239 | for section in sections: 240 | prob_dist['S'+section] = round(remaining_prob / 20,4) 241 | else: 242 | section_probs = section_prob_dist[target_ring] 243 | for i,section in enumerate(sections): 244 | section_prob = section_probs[i] 245 | for j,ring in enumerate(rings): 246 | zone = (ring + section) if ring != 'OFF' else 'OFF' 247 | ring_prob = round(ring_probs[j] * section_prob,4) 248 | if(ring_prob > 0.0): 249 | prob_dist[zone] = ring_prob + (prob_dist[zone] if zone in prob_dist else 0.0) 250 | return prob_dist 251 | 252 | def best_target(miss): 253 | "Return the target that maximizes the expected score." 254 | ring_scores = {'S':1,'D':2,'T':3,'SB':25,'DB':50,'OFF':0} 255 | rings = ['S','D','T'] 256 | miss_conversion = {'S':miss/5.0,'DB':miss * 3.0} 257 | if miss in miss_conversion: 258 | miss = miss_conversion[miss] 259 | 260 | best_hit = None 261 | for section in SECTIONS: 262 | for ring in rings: 263 | prob_dist = outcome(ring+section,miss) 264 | probable_score = 0.0 265 | for target in prob_dist: 266 | probable_score += prob_dist[target] * ring_scores[get_ring(target)] * int('1' if get_section(target) == None else get_section(target)) 267 | #print score,prob 268 | if (best_hit is None) or (best_hit[1] < probable_score) : 269 | best_hit = (ring+section,probable_score) 270 | #print best_hit[1] 271 | return best_hit[0] 272 | 273 | def same_outcome(dict1, dict2): 274 | "Two states are the same if all corresponding sets of locs are the same." 275 | return all(abs(dict1.get(key, 0) - dict2.get(key, 0)) <= 0.0001 276 | for key in set(dict1) | set(dict2)) 277 | 278 | def test_darts2(): 279 | assert best_target(0.0) == 'T20' 280 | assert best_target(0.1) == 'T20' 281 | assert best_target(0.4) == 'T19' 282 | assert same_outcome(outcome('T20', 0.0), {'T20': 1.0}) 283 | assert same_outcome(outcome('T20', 0.1), 284 | {'T20': 0.81, 'S1': 0.005, 'T5': 0.045, 285 | 'S5': 0.005, 'T1': 0.045, 'S20': 0.09}) 286 | assert (same_outcome( 287 | outcome('SB', 0.2), 288 | {'S9': 0.016, 'S8': 0.016, 'S3': 0.016, 'S2': 0.016, 'S1': 0.016, 289 | 'DB': 0.04, 'S6': 0.016, 'S5': 0.016, 'S4': 0.016, 'S20': 0.016, 290 | 'S19': 0.016, 'S18': 0.016, 'S13': 0.016, 'S12': 0.016, 'S11': 0.016, 291 | 'S10': 0.016, 'S17': 0.016, 'S16': 0.016, 'S15': 0.016, 'S14': 0.016, 292 | 'S7': 0.016, 'SB': 0.64})) 293 | assert (same_outcome(outcome('D20',0.4),{'OFF': 0.2, 'D20': 0.36, 'S1': 0.04, 'S5': 0.04, 'S20': 0.12, 'D5': 0.12, 'D1': 0.12})) 294 | assert (same_outcome(outcome('T20',1.0),{'S1': 0.5, 'S5': 0.5})) 295 | assert (same_outcome(outcome('S17',0.5),{'D17': 0.045, 'S3': 0.045, 'S2': 0.045, 'T2': 0.0025, 'T3': 0.0025, 'T17': 0.045, 'S17': 0.81, 'D2': 0.0025, 'D3': 0.0025})) 296 | assert (same_outcome(outcome('DB',0.1),{'S9': 0.022, 'S8': 0.022, 'S3': 0.022, 'S2': 0.022, 'S1': 0.022, 'DB': 0.49, 'S6': 0.022, 'S5': 0.022, 'S4': 0.022, 'S19': 0.022, 'S18': 0.022, 'S13': 0.022, 'S12': 0.022, 'S11': 0.022, 'S10': 0.022, 'S17': 0.022, 'S16': 0.022, 'S15': 0.022, 'S14': 0.022, 'S7': 0.022, 'S20': 0.022, 'SB': 0.07})) 297 | print 'tests pass' 298 | if __name__ == '__main__': 299 | test_darts() 300 | test_darts2() 301 | -------------------------------------------------------------------------------- /Archive/darts_probability.py: -------------------------------------------------------------------------------- 1 | # Unit 5: Probability in the game of Darts 2 | 3 | """ 4 | In the game of darts, players throw darts at a board to score points. 5 | The circular board has a 'bulls-eye' in the center and 20 slices 6 | called sections, numbered 1 to 20, radiating out from the bulls-eye. 7 | The board is also divided into concentric rings. The bulls-eye has 8 | two rings: an outer 'single' ring and an inner 'double' ring. Each 9 | section is divided into 4 rings: starting at the center we have a 10 | thick single ring, a thin triple ring, another thick single ring, and 11 | a thin double ring. A ring/section combination is called a 'target'; 12 | they have names like 'S20', 'D20' and 'T20' for single, double, and 13 | triple 20, respectively; these score 20, 40, and 60 points. The 14 | bulls-eyes are named 'SB' and 'DB', worth 25 and 50 points 15 | respectively. Illustration (png image): http://goo.gl/i7XJ9 16 | 17 | There are several variants of darts play; in the game called '501', 18 | each player throws three darts per turn, adding up points until they 19 | total exactly 501. However, the final dart must be in a double ring. 20 | 21 | Your first task is to write the function double_out(total), which will 22 | output a list of 1 to 3 darts that add up to total, with the 23 | restriction that the final dart is a double. See test_darts() for 24 | examples. Return None if there is no list that achieves the total. 25 | 26 | Often there are several ways to achieve a total. You must return a 27 | shortest possible list, but you have your choice of which one. For 28 | example, for total=100, you can choose ['T20', 'D20'] or ['DB', 'DB'] 29 | but you cannot choose ['T20', 'D10', 'D10']. 30 | """ 31 | 32 | def test_darts(): 33 | "Test the double_out function." 34 | assert double_out(170) == ['T20', 'T20', 'DB'] 35 | assert double_out(171) == None 36 | assert double_out(100) in (['T20', 'D20'], ['DB', 'DB']) # Could be problem area. I am not specifically looking for this. That is why i should start with 0,60,59.. 37 | print 'test_darts pass' 38 | 39 | """ 40 | My strategy: I decided to choose the result that has the highest valued 41 | target(s) first, e.g. always take T20 on the first dart if we can achieve 42 | a solution that way. If not, try T19 first, and so on. At first I thought 43 | I would need three passes: first try to solve with one dart, then with two, 44 | then with three. But I realized that if we include 0 as a possible dart 45 | value, and always try the 0 first, then we get the effect of having three 46 | passes, but we only have to code one pass. So I creted ordered_points as 47 | a list of all possible scores that a single dart can achieve, with 0 first, 48 | and then descending: [0, 60, 57, ..., 1]. I iterate dart1 and dart2 over 49 | that; then dart3 must be whatever is left over to add up to total. If 50 | dart3 is a valid element of points, then we have a solution. But the 51 | solution, is a list of numbers, like [0, 60, 40]; we need to transform that 52 | into a list of target names, like ['T20', 'D20'], we do that by defining name(d) 53 | to get the name of a target that scores d. When there are several choices, 54 | we must choose a double for the last dart, but for the others I prefer the 55 | easiest targets first: 'S' is easiest, then 'T', then 'D'. 56 | """ 57 | 58 | 59 | def double_out(total): 60 | """Return a shortest possible list of targets that add to total, 61 | where the length <= 3 and the final element is a double. 62 | If there is no solution, return None.""" 63 | single = range(1,21) 64 | double = [score * 2 for score in single] + [50] 65 | triple = [score * 3 for score in single] 66 | bulls_eye = [25,50] 67 | points = [] 68 | dart_scores = [0] + sorted(set(triple + double + single + bulls_eye),reverse = True) 69 | #print dart_scores 70 | possible_ways = [(dart1,dart2,dart3) 71 | for dart1 in dart_scores 72 | for dart2 in dart_scores 73 | for dart3 in double 74 | if dart1+dart2+dart3 == total] 75 | if len(possible_ways) > 0: 76 | for score in possible_ways[0]: # assuming [0] to be the best possible because the scores are sorted in reverse which internally assumes the player hit the triple points in the first attempt 77 | if score != 0: 78 | if score in triple: 79 | points.append('T' + str(score/3)) 80 | elif score in bulls_eye: 81 | points.append('SB' if score == 25 else 'DB') 82 | elif score in double: 83 | points.append('D' + str(score/2)) 84 | else: 85 | points.append('S' + str(score)) 86 | return points if len(points) > 0 else None 87 | 88 | 89 | 90 | """ 91 | It is easy enough to say "170 points? Easy! Just hit T20, T20, DB." 92 | But, at least for me, it is much harder to actually execute the plan 93 | and hit each target. In this second half of the question, we 94 | investigate what happens if the dart-thrower is not 100% accurate. 95 | 96 | We will use a wrong (but still useful) model of inaccuracy. A player 97 | has a single number from 0 to 1 that characterizes his/her miss rate. 98 | If miss=0.0, that means the player hits the target every time. 99 | But if miss is, say, 0.1, then the player misses the section s/he 100 | is aiming at 10% of the time, and also (independently) misses the thin 101 | double or triple ring 10% of the time. Where do the misses go? 102 | Here's the model: 103 | 104 | First, for ring accuracy. If you aim for the triple ring, all the 105 | misses go to a single ring (some to the inner one, some to the outer 106 | one, but the model doesn't distinguish between these). If you aim for 107 | the double ring (at the edge of the board), half the misses (e.g. 0.05 108 | if miss=0.1) go to the single ring, and half off the board. (We will 109 | agree to call the off-the-board 'target' by the name 'OFF'.) If you 110 | aim for a thick single ring, it is about 5 times thicker than the thin 111 | rings, so your miss ratio is reduced to 1/5th, and of these, half go to 112 | the double ring and half to the triple. So with miss=0.1, 0.01 will go 113 | to each of the double and triple ring. Finally, for the bulls-eyes. If 114 | you aim for the single bull, 1/4 of your misses go to the double bull and 115 | 3/4 to the single ring. If you aim for the double bull, it is tiny, so 116 | your miss rate is tripled; of that, 2/3 goes to the single ring and 1/3 117 | to the single bull ring. 118 | 119 | Now, for section accuracy. Half your miss rate goes one section clockwise 120 | and half one section counter-clockwise from your target. The clockwise 121 | order of sections is: 122 | 123 | 20 1 18 4 13 6 10 15 2 17 3 19 7 16 8 11 14 9 12 5 124 | 125 | If you aim for the bull (single or double) and miss on rings, then the 126 | section you end up on is equally possible among all 20 sections. But 127 | independent of that you can also miss on sections; again such a miss 128 | is equally likely to go to any section and should be recorded as being 129 | in the single ring. 130 | 131 | You will need to build a model for these probabilities, and define the 132 | function outcome(target, miss), which takes a target (like 'T20') and 133 | a miss ration (like 0.1) and returns a dict of {target: probability} 134 | pairs indicating the possible outcomes. You will also define 135 | best_target(miss) which, for a given miss ratio, returns the target 136 | with the highest expected score. 137 | 138 | If you are very ambitious, you can try to find the optimal strategy for 139 | accuracy-limited darts: given a state defined by your total score 140 | needed and the number of darts remaining in your 3-dart turn, return 141 | the target that minimizes the expected number of total 3-dart turns 142 | (not the number of darts) required to reach the total. This is harder 143 | than Pig for several reasons: there are many outcomes, so the search space 144 | is large; also, it is always possible to miss a double, and thus there is 145 | no guarantee that the game will end in a finite number of moves. 146 | """ 147 | ZONES = {'S':1,'D':2,'T':3} 148 | SECTIONS = ['20', '1', '18', '4', '13', '6', '10', '15', '2', '17', '3', '19', '7', '16', '8', '11', '14', '9', '12','5'] 149 | RINGS = ['OFF','D','S','T','S','SB','DB'] 150 | 151 | def get_adjacents(cell,items): 152 | if cell == None: 153 | return items 154 | else: 155 | for index,item in enumerate(items): 156 | if item == cell: 157 | return [items[index-1],items[index],items[index+1]] 158 | 159 | def get_adjacent_sections(section): 160 | if section == None: 161 | return SECTIONS 162 | else: 163 | index = SECTIONS.index(section) 164 | return [SECTIONS[index-1],SECTIONS[index],SECTIONS[(index+1)%len(SECTIONS)]] 165 | 166 | def get_adjacent_rings(ring): 167 | index = RINGS.index(ring) 168 | if index == 0: #OFF 169 | return [RINGS[index],RINGS[index+1],RINGS[(index+2)]] 170 | elif index == len(RINGS) - 1: #DB 171 | return [RINGS[index-2],RINGS[index-1],RINGS[(index)]] 172 | else: 173 | return [RINGS[index-1],RINGS[index],RINGS[(index+1)]] 174 | 175 | def get_neighbors(target): 176 | ring,section = get_ring(target),get_section(target) 177 | if section == None: 178 | return SECTIONS 179 | else: 180 | return [ r+s for r in get_adjacents(ring,RINGS) for s in get_adjacents(section,SECTIONS)] 181 | 182 | def get_ring(target): 183 | if target in ('SB','DB','OFF'): 184 | return target 185 | else: 186 | return target[0] 187 | 188 | def get_section(target): 189 | if target in ('SB','DB','OFF'): 190 | return None 191 | else: 192 | return target[1:] 193 | 194 | def prob_test(): 195 | T20 = DandT_miss_prob('T20',0.1) 196 | assert T20.Prob.topProb == 0.05 197 | assert T20.Target.zone == 'T' 198 | assert int(T20.Target.sector) == 20 199 | S18 = S_miss_prob('S10',0.02) 200 | assert S18.Prob.prob == 0.004 201 | SB = Bull('SB') 202 | assert SB.sector == 'B' 203 | assert SB.target == 'SB' 204 | assert SB.zone == 'S' 205 | SB = B_miss_prob('SB',0.5) 206 | assert SB.Prob.topProb == 0.375 207 | assert SB.Prob.bottomProb == 0.125 208 | #assert SB.Prob.leftProb == SB.Prob.rightProb == 0.25 209 | DB = B_miss_prob('DB',0.5) 210 | assert DB.Prob.top1 == 0.49995 211 | assert get_adjacents('T',RINGS) == ['S','T','S'] 212 | assert get_adjacents('D',RINGS) == ['OFF','D','S'] 213 | assert get_adjacents('20',SECTIONS) == ['5','20','1'] 214 | assert set(get_neighbors('T20')) == set(['T20','T5','T1','S1','S5','S20']) 215 | assert len(get_neighbors('T20')) == 9 216 | assert same_outcome(outcome('T20', 0.1), 217 | {'T20': 0.81, 'S1': 0.005, 'T5': 0.045, 218 | 'S5': 0.005, 'T1': 0.045, 'S20': 0.09}) 219 | print 'prob tests pass' 220 | 221 | def outcome(target, miss): 222 | "Return a probability distribution of [(target, probability)] pairs." 223 | target_ring = get_ring(target) 224 | target_section = get_section(target) 225 | sections = get_adjacent_sections(target_section) #gets all sections 226 | rings = get_adjacent_rings(target_ring) 227 | miss_conversion = {'S':miss/5.0,'DB':miss * 3.0} 228 | if target_ring in miss_conversion: 229 | miss = miss_conversion[target_ring] 230 | ring_prob_dist = {'S':(miss*0.5,1.0-miss,miss*0.5),'D': (miss*0.5,1.0-miss,miss*0.5),'T':(miss*0.5,1.0-miss,miss*0.5),'SB':((1.0 - miss),miss*0.25),'DB':(miss/3,1.0 - miss)} # take this out 231 | section_prob_dist = {'S':(miss*0.5,1.0-miss,miss*0.5),'T':(miss*0.5,1.0-miss,miss*0.5),'D':(miss*0.5,1.0-miss,miss*0.5)} 232 | prob_dist = {} 233 | ring_probs = ring_prob_dist[target_ring] 234 | if target_ring in ('SB','DB'): 235 | section_prob = 1.0 - miss 236 | prob_dist['SB'] = round(ring_probs[0] * section_prob,4) 237 | prob_dist['DB'] = round(ring_probs[1] * section_prob,4) 238 | remaining_prob = 1.0 - (prob_dist['SB'] + prob_dist['DB']) 239 | for section in sections: 240 | prob_dist['S'+section] = round(remaining_prob / 20,4) 241 | else: 242 | section_probs = section_prob_dist[target_ring] 243 | for i,section in enumerate(sections): 244 | section_prob = section_probs[i] 245 | for j,ring in enumerate(rings): 246 | zone = (ring + section) if ring != 'OFF' else 'OFF' 247 | ring_prob = round(ring_probs[j] * section_prob,4) 248 | if(ring_prob > 0.0): 249 | prob_dist[zone] = ring_prob + (prob_dist[zone] if zone in prob_dist else 0.0) 250 | return prob_dist 251 | 252 | def best_target(miss): 253 | "Return the target that maximizes the expected score." 254 | ring_scores = {'S':1,'D':2,'T':3,'SB':25,'DB':50,'OFF':0} 255 | rings = ['S','D','T'] 256 | miss_conversion = {'S':miss/5.0,'DB':miss * 3.0} 257 | if miss in miss_conversion: 258 | miss = miss_conversion[miss] 259 | 260 | best_hit = None 261 | for section in SECTIONS: 262 | for ring in rings: 263 | prob_dist = outcome(ring+section,miss) 264 | probable_score = 0.0 265 | for target in prob_dist: 266 | probable_score += prob_dist[target] * ring_scores[get_ring(target)] * int('1' if get_section(target) == None else get_section(target)) 267 | #print score,prob 268 | if (best_hit is None) or (best_hit[1] < probable_score) : 269 | best_hit = (ring+section,probable_score) 270 | #print best_hit[1] 271 | return best_hit[0] 272 | 273 | def same_outcome(dict1, dict2): 274 | "Two states are the same if all corresponding sets of locs are the same." 275 | return all(abs(dict1.get(key, 0) - dict2.get(key, 0)) <= 0.0001 276 | for key in set(dict1) | set(dict2)) 277 | 278 | def test_darts2(): 279 | assert best_target(0.0) == 'T20' 280 | assert best_target(0.1) == 'T20' 281 | assert best_target(0.4) == 'T19' 282 | assert same_outcome(outcome('T20', 0.0), {'T20': 1.0}) 283 | assert same_outcome(outcome('T20', 0.1), 284 | {'T20': 0.81, 'S1': 0.005, 'T5': 0.045, 285 | 'S5': 0.005, 'T1': 0.045, 'S20': 0.09}) 286 | assert (same_outcome( 287 | outcome('SB', 0.2), 288 | {'S9': 0.016, 'S8': 0.016, 'S3': 0.016, 'S2': 0.016, 'S1': 0.016, 289 | 'DB': 0.04, 'S6': 0.016, 'S5': 0.016, 'S4': 0.016, 'S20': 0.016, 290 | 'S19': 0.016, 'S18': 0.016, 'S13': 0.016, 'S12': 0.016, 'S11': 0.016, 291 | 'S10': 0.016, 'S17': 0.016, 'S16': 0.016, 'S15': 0.016, 'S14': 0.016, 292 | 'S7': 0.016, 'SB': 0.64})) 293 | assert (same_outcome(outcome('D20',0.4),{'OFF': 0.2, 'D20': 0.36, 'S1': 0.04, 'S5': 0.04, 'S20': 0.12, 'D5': 0.12, 'D1': 0.12})) 294 | assert (same_outcome(outcome('T20',1.0),{'S1': 0.5, 'S5': 0.5})) 295 | assert (same_outcome(outcome('S17',0.5),{'D17': 0.045, 'S3': 0.045, 'S2': 0.045, 'T2': 0.0025, 'T3': 0.0025, 'T17': 0.045, 'S17': 0.81, 'D2': 0.0025, 'D3': 0.0025})) 296 | assert (same_outcome(outcome('DB',0.1),{'S9': 0.022, 'S8': 0.022, 'S3': 0.022, 'S2': 0.022, 'S1': 0.022, 'DB': 0.49, 'S6': 0.022, 'S5': 0.022, 'S4': 0.022, 'S19': 0.022, 'S18': 0.022, 'S13': 0.022, 'S12': 0.022, 'S11': 0.022, 'S10': 0.022, 'S17': 0.022, 'S16': 0.022, 'S15': 0.022, 'S14': 0.022, 'S7': 0.022, 'S20': 0.022, 'SB': 0.07})) 297 | print 'tests pass' 298 | if __name__ == '__main__': 299 | test_darts() 300 | #prob_test() 301 | test_darts2() 302 | --------------------------------------------------------------------------------