Genetic algorithm - ordered crossover in python - python

I have implemented a genetic algorithm in python 3, and have posted a question on code review with no answers yet, basically because my algorithm is running very slowly. By selectively commenting out different parts of my code, I have narrowed down the bottleneck to this section of code, the crossover algorithm:
def crossover(self, mum, dad):
"""Implements ordered crossover"""
size = len(mum.vertices)
# Choose random start/end position for crossover
alice, bob = [-1] * size, [-1] * size
start, end = sorted([random.randrange(size) for _ in range(2)])
# Replicate mum's sequence for alice, dad's sequence for bob
for i in range(start, end + 1):
alice[i] = mum.vertices[i]
bob[i] = dad.vertices[i]
# # Fill the remaining position with the other parents' entries
# current_dad_position, current_mum_position = 0, 0
#
# for i in chain(range(start), range(end + 1, size)):
#
# while dad.vertices[current_dad_position] in alice:
# current_dad_position += 1
#
# while mum.vertices[current_mum_position] in bob:
# current_mum_position += 1
#
# alice[i] = dad.vertices[current_dad_position]
# bob[i] = mum.vertices[current_mum_position]
#
# # Return twins
# return graph.Tour(self.g, alice), graph.Tour(self.g, bob)
return mum, dad
The part which is commented out makes my program runtime go from ~7 seconds to 5-6 minutes (I am running 5000 iterations of the GA). Is there any way this ordered crossover can be carried out more efficiently?
What the crossover function does
For those unfamiliar, I am implementing an order-based crossover (OX2). Given two arrays of consecutive integers (the parents), two random start/end positions are selected.
mum = 4 9 2 8 3 1 5 7 6
dad = 6 4 1 3 7 2 8 5 9
^ ^
start end
The two children then share the resulting slices:
child 1 = _ _ 2 8 3 1 _ _ _
child 2 = _ _ 1 3 7 2 _ _ _
^ ^
Now the remaining slots are filled in with the entries of the other parents in the order in which they appear, as long as repetitions are avoided. So since child 1 has their slice taken from mum, the rest of the entries are taken from dad. First we take 6, then 4, then next we take 7 (not taking 1 and 3 since they already appear in child 1 from mum), then 5, then 9. So
child 1 = 6 4 2 8 3 1 7 5 9
and similarly,
child 2 = 4 9 1 3 7 2 8 5 6
This is what I am implementing in the function.

I can only guess that your problem lies with the fact that your while-loop and the increment within is not limited to the actual size of the vertices vector, put a hard limit and test again:
while current_dad_position < size and dad.vertices[current_dad_position] in alice:
current_dad_position += 1
while current_mom_position < size and mum.vertices[current_mum_position] in bob:
current_mum_position += 1
I feel compelled to say that this might not necessarily result a unique solution, as I do not know how the how the algorithm, should behave if there are not enough unique unique singular vertices available to chose from because they violate your 'not from the other parent' restriction.
For anybody to test this out, I would recommend to complete your code with an simple example input and not commenting out the code in question, but rather to mark its BEGIN and END with comments.

Okay with the knowledge that the problem is uniquely solvable becuae of construction, here is how it should look like:
import random
import numpy as np
def crossover(mum, dad):
"""Implements ordered crossover"""
size = len(mum.vertices)
# Choose random start/end position for crossover
alice, bob = [-1] * size, [-1] * size
start, end = sorted([random.randrange(size) for _ in range(2)])
# Replicate mum's sequence for alice, dad's sequence for bob
alice_inherited = []
bob_inherited = []
for i in range(start, end + 1):
alice[i] = mum.vertices[i]
bob[i] = dad.vertices[i]
alice_inherited.append(mum.vertices[i])
bob_inherited.append(dad.vertices[i])
print(alice, bob)
#Fill the remaining position with the other parents' entries
current_dad_position, current_mum_position = 0, 0
fixed_pos = list(range(start, end + 1))
i = 0
while i < size:
if i in fixed_pos:
i += 1
continue
test_alice = alice[i]
if test_alice==-1: #to be filled
dad_trait = dad.vertices[current_dad_position]
while dad_trait in alice_inherited:
current_dad_position += 1
dad_trait = dad.vertices[current_dad_position]
alice[i] = dad_trait
alice_inherited.append(dad_trait)
#repeat block for bob and mom
i +=1
return alice, bob
with
class Mum():
def __init__(self):
self.vertices =[ 4, 9, 2, 8, 3, 1, 5, 7, 6 ]
class Dad():
def __init__(self):
self.vertices = [ 6 , 4 , 1 , 3 , 7 , 2 , 8 , 5 , 9 ]
mum = Mum()
dad = Dad()
a, b = crossover(mum, dad)
# a = [6, 4, 2, 8, 3, 1, 5, 7, 9]

Related

Find maximum combination sum with two separate target values as constraints

Given an array of numbers (can include duplicates) - which represents the hours it takes for a car to be manufactured. And two numbers, which represent the hours that two factories that work. Find the maximum number of unique cars that can be produced.
Test case 1:
[6, 5, 5, 4, 3] and 8, 9
The maximum combination is 4. 5 and 3 to sum 8, and 5 and 4 to sum 9.
Test case 2:
[5, 5, 6] and 8, 8
The maximum combination is 2. The factory that works 8 hours can at most complete one vehicle that takes 5 hours, while the factory that works 9 hours can at most complete the vehicle that takes 6 hours.
I believe this question I am asking is similar to the Combination Sum II problem on Leetcode. But I am unable to solve it. Any ideas?
hours = [6, 5, 5, 4, 3]
com_a = 8
com_b = 9
def get_max(hours, com_a, com_b):
if len(hours) == 0: return 0
max_cars_produced = 0
# the order of hours matters, so we check which order provides the highest number
for i in range(len(hours)):
h = hours[i]
child_1 = 0
child_2 = 0
# the order by which the companies are filled also matters
# case I: where first company is filled first
if h <= com_a: child_1 = get_max(hours[i+1:], com_a - h, com_b)
elif h <= com_b: child_1 = get_max(hours[i+1:], com_a, com_b -h)
# case 2: where second company is filled first
if h <= com_b: child_2 = get_max(hours[i+1:], com_a, com_b -h)
elif h <= com_a: child_2 = get_max(hours[i+1:], com_a - h, com_b)
if h <= com_a or h <= com_b:
# if this satisfy this condition, it means that at least one car has been manufactured
num_cars = max(child_1, child_2) + 1
if num_cars > max_cars_produced: max_cars_produced = num_cars
return max_cars_produced
get_max(hours, com_a, com_b)
It is solved by recursive function. Every time it tries to see if a car (from hours array) can be manufactured by a company. And if it could, then it check the remaining cars (hours) to see if any of them can be manufactured (child).

Long multiplication of two numbers given as strings

I am trying to solve a problem of multiplication. I know that Python supports very large numbers and it can be done but what I want to do is
Enter 2 numbers as strings.
Multiply those two numbers in the same manner as we used to do in school.
Basic idea is to convert the code given in the link below to Python code but I am not very good at C++/Java. What I want to do is to understand the code given in the link below and apply it for Python.
https://www.geeksforgeeks.org/multiply-large-numbers-represented-as-strings/
I am stuck at the addition point.
I want to do it it like in the image given below
So I have made a list which stores the values of ith digit of first number to jth digit of second. Please help me to solve the addition part.
def mul(upper_no,lower_no):
upper_len=len(upper_no)
lower_len=len(lower_no)
list_to_add=[] #saves numbers in queue to add in the end
for lower_digit in range(lower_len-1,-1,-1):
q='' #A queue to store step by step multiplication of numbers
carry=0
for upper_digit in range(upper_len-1,-1,-1):
num2=int(lower_no[lower_digit])
num1=int(upper_no[upper_digit])
print(num2,num1)
x=(num2*num1)+carry
if upper_digit==0:
q=str(x)+q
else:
if x>9:
q=str(x%10)+q
carry=x//10
else:
q=str(x%10)+q
carry=0
num=x%10
print(q)
list_to_add.append(int(''.join(q)))
print(list_to_add)
mul('234','567')
I have [1638,1404,1170] as a result for the function call mul('234','567') I am supposed to add these numbers but stuck because these numbers have to be shifted for each list. for example 1638 is supposed to be added as 16380 + 1404 with 6 aligning with 4, 3 with 0 and 8 with 4 and so on. Like:
1638
1404x
1170xx
--------
132678
--------
I think this might help. I've added a place variable to keep track of what power of 10 each intermediate value should be multiplied by, and used the itertools.accumulate function to produce the intermediate accumulated sums that doing so produces (and you want to show).
Note I have also reformatted your code so it closely follows PEP 8 - Style Guide for Python Code in an effort to make it more readable.
from itertools import accumulate
import operator
def mul(upper_no, lower_no):
upper_len = len(upper_no)
lower_len = len(lower_no)
list_to_add = [] # Saves numbers in queue to add in the end
place = 0
for lower_digit in range(lower_len-1, -1, -1):
q = '' # A queue to store step by step multiplication of numbers
carry = 0
for upper_digit in range(upper_len-1, -1, -1):
num2 = int(lower_no[lower_digit])
num1 = int(upper_no[upper_digit])
print(num2, num1)
x = (num2*num1) + carry
if upper_digit == 0:
q = str(x) + q
else:
if x>9:
q = str(x%10) + q
carry = x//10
else:
q = str(x%10) + q
carry = 0
num = x%10
print(q)
list_to_add.append(int(''.join(q)) * (10**place))
place += 1
print(list_to_add)
print(list(accumulate(list_to_add, operator.add)))
mul('234', '567')
Output:
7 4
7 3
7 2
1638
6 4
6 3
6 2
1404
5 4
5 3
5 2
1170
[1638, 14040, 117000]
[1638, 15678, 132678]

How to get inverse of integer?

I am not sure of inverse is the proper name, but I think it is.
This example will clarify what I need:
I have a max height, 5 for example, and so height can range from 0 to 4. In this case we're talking integers, so the options are: 0, 1, 2, 3, 4.
What I need, given an input ranging from 0 up to (and including) 4, is to get the inverse number.
Example:
input: 3
output: 1
visual:
0 1 2 3 4
4 3 2 1 0
I know I can do it like this:
position_list = list(range(5))
index_list = position_list[::-1]
index = index_list[3]
But this will probably use unnecessary memory, and probably unnecessary cpu usage creating two lists. The lists will be deleted after these lines of code, and will recreated every time the code is ran (within method). I'd rather find a way not needing the lists at all.
What is an efficient way to achieve the same? (while still keeping the code readable for someone new to the code)
Isn't it just max - in...?
>>> MAX=4
>>> def calc(in_val):
... out_val = MAX - in_val
... print('%s -> %s' % ( in_val, out_val ))
...
>>> calc(3)
3 -> 1
>>> calc(1)
1 -> 3
You just need to subtract from the max:
def return_inverse(n, mx):
return mx - n
For the proposed example:
position_list = list(range(5))
mx = max(position_list)
[return_inverse(i, mx) for i in position_list]
# [4, 3, 2, 1, 0]
You have maximum heigth, let's call it max_h.
Your numbers are counted from 0, so they are in [0; max_h - 1]
You want to find the complementation number that becomes max_h in sum with input number
It is max_h - 1 - your_number:
max_height = 5
input_number = 2
for input_number in range(5):
print('IN:', input_number, 'OUT:', max_height - input_number - 1)
IN: 1 OUT: 3
IN: 2 OUT: 2
IN: 3 OUT: 1
IN: 4 OUT: 0
Simply compute the reverse index and then directly access the corresponding element.
n = 5
inp = 3
position_list = list(range(n))
position_list[n-1-inp]
# 1
You can just derive the index from the list's length and the desired position, to arrive at the "inverse":
position_list = list(range(5))
position = 3
inverse = position_list[len(position_list)-1-position]
And:
for i in position_list:
print(i, position_list[len(position_list)-1-i])
In this case, you can just have the output = 4-input. If it's just increments of 1 up to some number a simple operation like that should be enough. For example, if the max was 10 and the min was 5, then you could just do 9-input+5. The 9 can be replaced by the max-1 and the 5 can be replaced with the min.
So max-1-input+min

How to find the number of ways to get 21 in Blackjack?

Some assumptions:
One deck of 52 cards is used
Picture cards count as 10
Aces count as 1 or 11
The order is not important (ie. Ace + Queen is the same as Queen + Ace)
I thought I would then just sequentially try all the possible combinations and see which ones add up to 21, but there are way too many ways to mix the cards (52! ways). This approach also does not take into account that order is not important nor does it account for the fact that there are only 4 maximum types of any one card (Spade, Club, Diamond, Heart).
Now I am thinking of the problem like this:
We have 11 "slots". Each of these slots can have 53 possible things inside them: 1 of 52 cards or no card at all. The reason it is 11 slots is because 11 cards is the maximum amount of cards that can be dealt and still add up to 21; more than 11 cards would have to add up to more than 21.
Then the "leftmost" slot would be incremented up by one and all 11 slots would be checked to see if they add up to 21 (0 would represent no card in the slot). If not, the next slot to the right would be incremented, and the next, and so on.
Once the first 4 slots contain the same "card" (after four increments, the first 4 slots would all be 1), the fifth slot could not be that number as well since there are 4 numbers of any type. The fifth slot would then become the next lowest number in the remaining available cards; in the case of four 1s, the fifth slot would become a 2 and so on.
How would you do approach this?
divide and conquer by leveraging the knowledge that if you have 13 and pick a 10 you only have to pick cards to sum to 3 left to look at ... be forwarned this solution might be slow(took about 180 seconds on my box... it is definately non-optimal) ..
def sum_to(x,cards):
if x == 0: # if there is nothing left to sum to
yield []
for i in range(1,12): # for each point value 1..11 (inclusive)
if i > x: break # if i is bigger than whats left we are done
card_v = 11 if i == 1 else i
if card_v not in cards: continue # if there is no more of this card
new_deck = cards[:] # create a copy of hte deck (we do not want to modify the original)
if i == 1: # one is clearly an ace...
new_deck.remove(11)
else: # remove the value
new_deck.remove(i)
# on the recursive call we need to subtract our recent pick
for result in sum_to(x-i,new_deck):
yield [i] + result # append each further combination to our solutions
set up your cards as follows
deck = []
for i in range(2,11): # two through ten (with 4 of each)
deck.extend([i]*4)
deck.extend([10]*4) #jacks
deck.extend([10]*4) #queens
deck.extend([10]*4) #kings
deck.extend([11]*4) # Aces
then just call your function
for combination in sum_to(21,deck):
print combination
unfortunately this does allow some duplicates to sneak in ...
in order to get unique entries you need to change it a little bit
in sum_to on the last line change it to
# sort our solutions so we can later eliminate duplicates
yield sorted([i] + result) # append each further combination to our solutions
then when you get your combinations you gotta do some deep dark voodoo style python
unique_combinations = sorted(set(map(tuple,sum_to(21,deck))),key=len,reverse=0)
for combo in unique_combinations: print combo
from this cool question i have learned the following (keep in mind in real play you would have the dealer and other players also removing from the same deck)
there are 416 unique combinations of a deck of cards that make 21
there are 300433 non-unique combinations!!!
the longest number of ways to make 21 are as follows
with 11 cards there are 1 ways
[(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3)]
with 10 cards there are 7 ways
with 9 cards there are 26 ways
with 8 cards there are 54 ways
with 7 cards there are 84 ways
with 6 cards there are 94 ways
with 5 cards there are 83 ways
with 4 cards there are 49 ways
with 3 cards there are 17 ways
with 2 cards there are 1 ways
[(10, 11)]
there are 54 ways in which all 4 aces are used in making 21!!
there are 106 ways of making 21 in which NO aces are used !!!
keep in mind these are often suboptimal plays (ie considering A,10 -> 1,10 and hitting )
Before worrying about the suits and different cards with value 10 lets figure out how many different value combinations resulting to 21 there are. For example 5, 5, 10, 1 is one such combination. The following function takes in limit which is the target value, start which indicates the lowest value that can be picked and used which is the list of picked values:
def combinations(limit, start, used):
# Base case
if limit == 0:
return 1
# Start iteration from lowest card to picked so far
# so that we're only going to pick cards 3 & 7 in order 3,7
res = 0
for i in range(start, min(12, limit + 1)):
# Aces are at index 1 no matter if value 11 or 1 is used
index = i if i != 11 else 1
# There are 16 cards with value of 10 (T, J, Q, K) and 4 with every
# other value
available = 16 if index == 10 else 4
if used[index] < available:
# Mark the card used and go through combinations starting from
# current card and limit lowered by the value
used[index] += 1
res += combinations(limit - i, i, used)
used[index] -= 1
return res
print combinations(21, 1, [0] * 11) # 416
Since we're interested about different card combinations instead of different value combinations the base case in above should be modified to return number of different card combinations that can be used to generate a value combination. Luckily that's quite easy task, Binomial coefficient can be used to figure out how many different combinations of k items can be picked from n items.
Once the number of different card combinations for each value in used is known they can be just multiplied with each other for the final result. So for the example of 5, 5, 10, 1 value 5 results to bcoef(4, 2) == 6, value 10 to bcoef(16, 1) == 16 and value 1 to bcoef(4, 1) == 4. For all the other values bcoef(x, 0) results to 1. Multiplying those values results to 6 * 16 * 4 == 384 which is then returned:
import operator
from math import factorial
def bcoef(n, k):
return factorial(n) / (factorial(k) * factorial(n - k))
def combinations(limit, start, used):
if limit == 0:
combs = (bcoef(4 if i != 10 else 16, x) for i, x in enumerate(used))
res = reduce(operator.mul, combs, 1)
return res
res = 0
for i in range(start, min(12, limit + 1)):
index = i if i != 11 else 1
available = 16 if index == 10 else 4
if used[index] < available:
used[index] += 1
res += combinations(limit - i, i, used)
used[index] -= 1
return res
print combinations(21, 1, [0] * 11) # 186184
So I decided to write the script that every possible viable hand can be checked. The total number comes out to be 188052. Since I checked every possible combination, this is the exact number (as opposed to an estimate):
import itertools as it
big_list = []
def deck_set_up(m):
special = {8:'a23456789TJQK', 9:'a23456789', 10:'a2345678', 11:'a23'}
if m in special:
return [x+y for x,y in list(it.product(special[m], 'shdc'))]
else:
return [x+y for x,y in list(it.product('a23456789TJQKA', 'shdc'))]
deck_dict = {'as':1,'ah':1,'ad':1,'ac':1,
'2s':2,'2h':2,'2d':2,'2c':2,
'3s':3,'3h':3,'3d':3,'3c':3,
'4s':4,'4h':4,'4d':4,'4c':4,
'5s':5,'5h':5,'5d':5,'5c':5,
'6s':6,'6h':6,'6d':6,'6c':6,
'7s':7,'7h':7,'7d':7,'7c':7,
'8s':8,'8h':8,'8d':8,'8c':8,
'9s':9,'9h':9,'9d':9,'9c':9,
'Ts':10,'Th':10,'Td':10,'Tc':10,
'Js':10,'Jh':10,'Jd':10,'Jc':10,
'Qs':10,'Qh':10,'Qd':10,'Qc':10,
'Ks':10,'Kh':10,'Kd':10,'Kc':10,
'As':11,'Ah':11,'Ad':11,'Ac':11}
stop_here = {2:'As', 3:'8s', 4:'6s', 5:'4h', 6:'3c', 7:'3s', 8:'2h', 9:'2s', 10:'2s', 11:'2s'}
for n in range(2,12): # n is number of cards in the draw
combos = it.combinations(deck_set_up(n), n)
stop_point = stop_here[n]
while True:
try:
pick = combos.next()
except:
break
if pick[0] == stop_point:
break
if n < 8:
if len(set([item.upper() for item in pick])) != n:
continue
if sum([deck_dict[card] for card in pick]) == 21:
big_list.append(pick)
print n, len(big_list) # Total number hands that can equal 21 is 188052
In the output, the the first column is the number of cards in the draw, and the second number is the cumulative count. So the number after "3" in the output is the total count of hands that equal 21 for a 2-card draw, and a 3-card draw. The lower case a is a low ace (1 point), and uppercase A is high ace. I have a line (the one with the set command), to make sure it throws out any hand that has a duplicate card.
The script takes 36 minutes to run. So there is definitely a trade-off between execution time, and accuracy. The "big_list" contains the solutions (i.e. every hand where the sum is 21)
>>>
================== RESTART: C:\Users\JBM\Desktop\bj3.py ==================
2 64
3 2100
4 14804
5 53296
6 111776
7 160132
8 182452
9 187616
10 188048
11 188052 # <-- This is the total count, as these numbers are cumulative
>>>

How to find the following desired probability using computational method (e.g. using Python)?

There are 64 students in our class. We had one round of random team assignment and will soon have a second round. In each round, we randomly divide the students into 16 groups of size 4 each.
What's probability that no pair of teammates in Round 1 are teammates again in Round 2?
Let's label the students according to their round-1 team:
0000 1111 2222 3333 4444 5555 6666 7777 8888 9999 AAAA BBBB CCCC DDDD EEEE FFFF
The number of ways to assign round-2 teams, without restrictions, is
64! / ((4! ** 16) * (4! ** 16)) == 864285371844932000669608560277139342915848604
^ ^ ^
| | ways of rearranging each round-2 team
| indistinguishable arrangements for round-1 team members
raw number of permutations
The number of ways to assign round-2 teams without per-team duplicates is complicated because how we assign the first team changes the combinations available for the second team (and so on). But it should still be tractable with clever math and careful memoization!
from functools import lru_cache
# lookup table for `choose(n, k)` for n up to 16 and k up to 4
ncr = {}
for n in range(17):
nn = n
ntok = 1
ktok = 1
ncr[n, 0] = 1
for k in range(1, min(n, 4) + 1):
ntok *= nn
nn -= 1
ktok *= k
ncr[n, k] = ntok // ktok
#lru_cache(maxsize=None)
def team_combos(zeros, ones, twos, threes, fours):
"""
Calculate the number of unique second-round 4-person
team combinations such that no team has members from
the same first-round team.
"""
if ones or twos or threes or fours:
total_ways = 0
# number of members to take from teams having one person remaining
for b in range(min(ones, 4) + 1):
zeros_ = zeros + b
b_ways = ncr[ones, b]
b_rem = 4 - b # number of members yet to be chosen
# number of members to take from teams having two persons remaining
for c in range(min(twos, b_rem) + 1):
ones_ = ones - b + c
bc_ways = b_ways * ncr[twos, c]
bc_rem = b_rem - c # number of members yet to be chosen
# number of members to take from teams having three persons remaining
for d in range(min(threes, bc_rem) + 1):
e = bc_rem - d # number of members yet to be chosen
# ... all of which _must_ now come from
# teams having four persons remaining
if e <= fours:
bcde_ways = bc_ways * ncr[threes, d] * ncr[fours, e]
total_ways += bcde_ways * team_combos(zeros_, ones_, twos - c + d, threes - d + e, fours - e)
return total_ways
else:
return 1
then
>>> team_combos(0, 0, 0, 0, 16) # start with 16 four-person teams
6892692735539278753058456514221737762215000
then the final probability is exactly
>>> 6892692735539278753058456514221737762215000 / 864285371844932000669608560277139342915848604
0.0079750195480295
If by "computationally" you mean "with a simulation":
import random
def make_teams(teamsize, numteams):
students = list(range(teamsize * numteams)) # Py2 & Py3 compatible
random.shuffle(students)
teammates = {}
for i in range(0, len(students), teamsize):
team = set(students[i:i+teamsize])
for t in team: teammates[t] = team.difference([t])
return teammates
ref_tms = make_teams(4, 16)
common_teammates = 0
for i in range(10000):
new_tms = make_teams(4, 16)
if any(ref_tms[t].intersection(new_tms[t]) for t in ref_tms):
common_teammates += 1
print('Common teammates seen in {} cases out of 10000'.format(
common_teammates)
Of course a sample of 10000 examples is small-ish, but it should start giving you an idea. You can run this a few times and see how the frequency (takes as an estimate of the probability) varies, and compute confidence bounds, etc, etc.

Categories