Related
I am trying to solve the usaco problem combination lock where you are given a two lock combinations. The locks have a margin of error of +- 2 so if you had a combination lock of 1-3-5, the combination 3-1-7 would still solve it.
You are also given a dial. For example, the dial starts at 1 and ends at the given number. So if the dial was 50, it would start at 1 and end at 50. Since the beginning of the dial is adjacent to the end of the dial, the combination 49-1-3 would also solve the combination lock of 1-3-5.
In this program, you have to output the number of distinct solutions to the two lock combinations. For the record, the combination 3-2-1 and 1-2-3 are considered distinct, but the combination 2-2-2 and 2-2-2 is not.
I have tried creating two functions, one to check whether three numbers match the constraints of the first combination lock and another to check whether three numbers match the constraints of the second combination lock.
a,b,c = 1,2,3
d,e,f = 5,6,7
dial = 50
def check(i,j,k):
i = (i+dial) % dial
j = (j+dial) % dial
k = (k+dial) % dial
if abs(a-i) <= 2 and abs(b-j) <= 2 and abs(c-k) <= 2:
return True
return False
def check1(i,j,k):
i = (i+dial) % dial
j = (j+dial) % dial
k = (k+dial) % dial
if abs(d-i) <= 2 and abs(e-j) <= 2 and abs(f-k) <= 2:
return True
return False
res = []
count = 0
for i in range(1,dial+1):
for j in range(1,dial+1):
for k in range(1,dial+1):
if check(i,j,k):
count += 1
res.append([i,j,k])
if check1(i,j,k):
count += 1
res.append([i,j,k])
print(sorted(res))
print(count)
The dial is 50 and the first combination is 1-2-3 and the second combination is 5-6-7.
The program should output 249 as the count, but it instead outputs 225. I am not really sure why this is happening. I have added the array for display purposes only. Any help would be greatly appreciated!
You're going to a lot of trouble to solve this by brute force.
First of all, your two check routines have identical functionality: just call the same routine for both combinations, giving the correct combination as a second set of parameters.
The critical logic problem is handling the dial wrap-around: you miss picking up the adjacent numbers. Run 49 through your check against a correct value of 1:
# using a=1, i=49
i = (1+50)%50 # i = 1
...
if abs(1-49) <= 2 ... # abs(1-49) is 48. You need it to show up as 2.
Instead, you can check each end of the dial:
a_diff = abs(i-a)
if a_diff <=2 or a_diff >= (dial-2) ...
Another way is to start by making a list of acceptable values:
a_vals = [(a-oops) % dial] for oops in range(-2, 3)]
... but note that you have to change the 0 value to dial. For instance, for a value of 1, you want a list of [49, 50, 1, 2, 3]
With this done, you can check like this:
if i in a_vals and j in b_vals and k in c_vals:
...
If you want to upgrade to the itertools package, you can simply generate all desired combinations:
combo = set(itertools.product(a_list, b_list_c_list) )
Do that for both given combinations and take the union of the two sets. The length of the union is the desired answer.
I see the follow-up isn't obvious -- at least, it's not appearing in the comments.
You have 5*5*5 solutions for each combination; start with 250 as your total.
Compute the sizes of the overlap sets: the numbers in each triple that can serve for each combination. For your given problem, those are [3],[4],[5]
The product of those set sizes is the quantity of overlap: 1*1*1 in this case.
The overlapping solutions got double-counted, so simply subtract the extra from 250, giving the answer of 249.
For example, given 1-2-3 and 49-6-6, you would get sets
{49, 50, 1}
{4}
{4, 5}
The sizes are 3, 1, 2; the product of those numbers is 6, so your answer is 250-6 = 244
Final note: If you're careful with your modular arithmetic, you can directly compute the set sizes without building the sets, making the program very short.
Here is one approach to a semi-brute-force solution:
import itertools
#The following code assumes 0-based combinations,
#represented as tuples of numbers in the range 0 to dial - 1.
#A simple wrapper function can be used to make the
#code apply to 1-based combos.
#The following function finds all combos which open lock with a given combo:
def combos(combo,tol,dial):
valids = []
for p in itertools.product(range(-tol,1+tol),repeat = 3):
valids.append(tuple((x+i)%dial for x,i in zip(combo,p)))
return valids
#The following finds all combos for a given iterable of target combos:
def all_combos(targets,tol,dial):
return set(combo for target in targets for combo in combos(target,tol,dial))
For example, len(all_combos([(0,1,2),(4,5,6)],2,50)) evaluate to 249.
The correct code for what you are trying to do is the following:
dial = 50
a = 1
b = 2
c = 3
d = 5
e = 6
f = 7
def check(i,j,k):
if (abs(a-i) <= 2 or (dial-abs(a-i)) <= 2) and \
(abs(b-j) <= 2 or (dial-abs(b-j)) <= 2) and \
(abs(c-k) <= 2 or (dial-abs(c-k)) <= 2):
return True
return False
def check1(i,j,k):
if (abs(d-i) <= 2 or (dial-abs(d-i)) <= 2) and \
(abs(e-j) <= 2 or (dial-abs(e-j)) <= 2) and \
(abs(f-k) <= 2 or (dial-abs(f-k)) <= 2):
return True
return False
res = []
count = 0
for i in range(1,dial+1):
for j in range(1,dial+1):
for k in range(1,dial+1):
if check(i,j,k):
count += 1
res.append([i,j,k])
elif check1(i,j,k):
count += 1
res.append([i,j,k])
print(sorted(res))
print(count)
And the result is 249, the total combinations are 2*(5**3) = 250, but we have the duplicates: [3, 4, 5]
I have a list of tuples (each tuple consists of 2 numbers) like:
array = [(1, 2), (1, 3), (2, 4), (5, 8), (8, 10)]
Lets say, these numbers are ids of some db objects (records) and inside a tuple, there are ids of duplicate objects. Which means 1 and 2 are duplicate. 1 and 3 are duplicate which means 2 and 3 are also duplicate.
if a == b and b == c then a == c
Now I want to merge all these duplicate objects ids into a single tuple like this:
output = [(1, 2, 3, 4), (5, 8, 10)]
I know I can do this using loops and redundant matches. I just want some better solution with low processing / calculations (if there is any).
You can use a data structure making it more efficient to perform a merge. Here you create some sort of opposite tree. So in your example you first would create the numbers listed:
1 2 3 4 5 8 10
Now if you iterate over the (1,2) tuple, you look up 1 and 2 in some sort of dictionary. You search their ancestors (there are none here) and then you create some sort of merge node:
1 2 3 4 5 8 10
\/
12
Next we merge (1,3) so we look up the ancestor of 1 (12) and 3 (3) and perform another merge:
1 2 3 4 5 8 10
\/ |
12 /
\/
123
Next we merge (2,4) and (5,8) and (8,10):
1 2 3 4 5 8 10
\/ | | \/ |
12 / | 58 /
\/ / \/
123 / 5810
\/
1234
You also keep a list of the "merge-heads" so you can easily return the elements.
Time to get our hands dirty
So now that we know how to construct such a datastructure, let's implement one. First we define a node:
class Merge:
def __init__(self,value=None,parent=None,subs=()):
self.value = value
self.parent = parent
self.subs = subs
def get_ancestor(self):
cur = self
while cur.parent is not None:
cur = cur.parent
return cur
def __iter__(self):
if self.value is not None:
yield self.value
elif self.subs:
for sub in self.subs:
for val in sub:
yield val
Now we first initialize a dictionary for every element in your list:
vals = set(x for tup in array for x in tup)
and create a dictionary for every element in vals that maps to a Merge:
dic = {val:Merge(val) for val in vals}
and the merge_heads:
merge_heads = set(dic.values())
Now for each tuple in the array, we lookup the corresponding Merge object that is the ancestor, we create a new Merge on top of that, remove the two old heads from the merge_head set and add the new merge to it:
for frm,to in array:
mra = dic[frm].get_ancestor()
mrb = dic[to].get_ancestor()
mr = Merge(subs=(mra,mrb))
mra.parent = mr
mrb.parent = mr
merge_heads.remove(mra)
merge_heads.remove(mrb)
merge_heads.add(mr)
Finally after we have done that we can simply construct a set for each Merge in merge_heads:
resulting_sets = [set(merge) for merge in merge_heads]
and resulting_sets will be (order may vary):
[{1, 2, 3, 4}, {8, 10, 5}]
Putting it all together (without class definition):
vals = set(x for tup in array for x in tup)
dic = {val:Merge(val) for val in vals}
merge_heads = set(dic.values())
for frm,to in array:
mra = dic[frm].get_ancestor()
mrb = dic[to].get_ancestor()
mr = Merge(subs=(mra,mrb))
mra.parent = mr
mrb.parent = mr
merge_heads.remove(mra)
merge_heads.remove(mrb)
merge_heads.add(mr)
resulting_sets = [set(merge) for merge in merge_heads]
This will worst case run in O(n2), but you can balance the tree such that the ancestor is found in O(log n) instead, making it O(n log n). Furthermore you can short-circuit the list of ancestors, making it even faster.
You can use disjoint set.
Disjoint set is actually a kind of tree structure. Let's consider each number as a tree node, and every time we read in an edge (u, v), we just easily associate the two trees u and v in (if it does not exist, create an one-node tree instead) by pointing the root node of one tree to another's. At the end, we should just walk through the forest to get the result.
from collections import defaultdict
def relation(array):
mapping = {}
def parent(u):
if mapping[u] == u:
return u
mapping[u] = parent(mapping[u])
return mapping[u]
for u, v in array:
if u not in mapping:
mapping[u] = u
if v not in mapping:
mapping[v] = v
mapping[parent(u)] = parent(v)
results = defaultdict(set)
for u in mapping.keys():
results[parent(u)].add(u)
return [tuple(x) for x in results.values()]
In the code above, mapping[u] stores the ancestor of node u (parent or root). Specially, the ancestor of one-node tree's node is itself.
See my comment on Moinuddin's answer : this accepted answer does not validates the tests that you can found at http://rosettacode.org/wiki/Set_consolidation#Python . I did not dig it up though.
I would make a new proposition, based on Willem's answer.
The problem in this proposition is the recursivity in the get_ancestor calls : why should we climb up the tree each time we are asked our ancestor, when we could just remember the last root found (and still climb up from that point in case it changed). Indeed, Willem's algorithm is not linear (something like nlogn or n²) while we could remove this non-linearity just as easily.
Another problem comes from the iterator : if the tree is too deep (I had the problem in my use case), you get a Python Exception (Too much recursion) inside the iterator. So instead of building a full tree, we should merge sub leafs (and instead of having branches with 2 leafs, we build branches with N leafs).
My version of the code is as follow :
class Merge:
def __init__(self,value=None,parent=None,subs=None):
self.value = value
self.parent = parent
self.subs = subs
self.root = None
if self.subs:
subs_a,subs_b = self.subs
if subs_a.subs:
subs_a = subs_a.subs
else:
subs_a = [subs_a]
if subs_b.subs:
subs_b = subs_b.subs
else:
subs_b = [subs_b]
self.subs = subs_a+subs_b
for s in self.subs:
s.parent = self
s.root = None
def get_ancestor(self):
cur = self if self.root is None else self.root
while cur.parent is not None:
cur = cur.parent
if cur != self:
self.root = cur
return cur
def __iter__(self):
if self.value is not None:
yield self.value
elif self.subs:
for sub in self.subs:
for val in sub:
yield val
def treeconsolidate(array):
vals = set(x for tup in array for x in tup)
dic = {val:Merge(val) for val in vals}
merge_heads = set(dic.values())
for settomerge in array:
frm = settomerge.pop()
for to in settomerge:
mra = dic[frm].get_ancestor()
mrb = dic[to].get_ancestor()
if mra == mrb:
continue
mr = Merge(subs=[mra,mrb])
merge_heads.remove(mra)
merge_heads.remove(mrb)
merge_heads.add(mr)
resulting_sets = [set(merge) for merge in merge_heads]
return resulting_sets
In small merges, this will not change many things but my experience shows that climbing up the tree in huge sets of many elements can cost a lot : in my case, I have to deal with 100k sets, each of them containing between 2 and 1000 elements, and each element may appear in 1 to 1000 sets...
I think the most efficient way to achieve this will be using set as:
def transitive_cloure(array):
new_list = [set(array.pop(0))] # initialize first set with value of index `0`
for item in array:
for i, s in enumerate(new_list):
if any(x in s for x in item):
new_list[i] = new_list[i].union(item)
break
else:
new_list.append(set(item))
return new_list
Sample run:
>>> transitive_cloure([(1,2), (1,3), (2,4), (5,8), (8,10)])
[{1, 2, 3, 4}, {8, 10, 5}]
Comparison with other answers (on Python 3.4):
This answer: 6.238126921001822
>>> timeit.timeit("moin()", setup="from __main__ import moin")
6.238126921001822
Willem's solution: 29.115453064994654 (Time related to declaration of class is excluded)
>>> timeit.timeit("willem()", setup="from __main__ import willem")
29.115453064994654
hsfzxjy's solution: 10.049749890022213
>>> timeit.timeit("hsfzxjy()", setup="from __main__ import hsfzxjy")
10.049749890022213
I create a list of len 100
li2 = list(range(100))
I use the below binary search function, with a counter, however it takes 5 searches to find 50. Should find it on the first try. (100/2) = 50 li2[50] == 50
def binary_search(li,item):
low = 0
high = len(li)-1
trys = 0
while low<=high:
mid = int((low + high)/2)
guess = li[mid]
if guess == item:
return'Found',item, 'in', trys,'searches'
elif guess > item:
trys+=1
high = mid - 1
else:
trys+=1
low = mid + 1
return item,' not found', trys, ' searches attempted'
I run binary_search(li2,50)
and returns below
('Found', 50, 'in', 5, 'searches')
range(100) will return a list with all elements from 0 up until 99. Your binary search algorithm will start searching on 49 and not on 50.
Why reinventing the wheel when you can use bisect, which often have a native implementation, so it's very fast.
bisect_left returns the insertion position of the current item in the list (which must be sorted of course). If the index is outside list range, then item is not in list. If index is within list range and item is located at this index, then you've found it:
import bisect
def binary_search(li,item):
insertion_index = bisect.bisect_left(li,item)
return insertion_index<len(li) and li[insertion_index]==item
testing:
li2 = list(range(100))
print(binary_search(li2,50))
print(binary_search(li2,-2))
print(binary_search(li2,104))
results:
True
False
False
This is a two part question, I have to make a selection of 2 indexes via a random range of any number of integers in a list. Can't return both if they're both in the same range as well
Selection1 = random.randint(0,100)
Selection2 = random.randint(0,100)
For the sake of this argument, say:
Selection1 = 10
Selection2 = 17
And the list would be like so [25, 50, 75, 100]
Both would return the index of 0 because they fall between 0-25
So both would fall into the first index range, the problem is i'm having some issues trying to fit it into this range (IE: 0-25) which will return this first index (return list[0])
What is the syntax for this type of logic in python?
I'm sure I can figure out how to return different indexes if they fall in the same range, probably just loop reset to the loop but if I can get some advice on that it wouldn't hurt.
I'll give the code i'm working with right now as a guideline. Mostly at the bottom is where i'm struggling.
Code Here
def roulette_selection(decimal_list, chromosome_fitness, population):
percentages = []
for i in range(population):
result = decimal_list[i]/chromosome_fitness
result = result * 100
percentages.append(result)
print(percentages)
range_in_fitness = []
current_percent = 0
for i in range(population):
current_percent = percentages[i] + current_percent
range_in_fitness.append(current_percent)
parent1 = random.randint(0, 100)
parent2 = random.randint(0, 100)
for i in range(population):
if parent1 >= range_in_fitness[i] and parent1<=range_in_fitness[i+1]:
print(parent1, parent2)
print(range_in_fitness)
If your list of ranges is sorted, or it is acceptable to sort it, and is contiguous (no gaps), you can use Python's bisect module to do this in an efficient manner. Example:
>>> l = [25, 50, 75, 100]
>>> import bisect
>>> bisect.bisect(l, 10)
0
>>> bisect.bisect(l, 17)
0
>>> bisect.bisect(l, 55)
2
>>> bisect.bisect(l, 25)
1
Bisect returns the index of where the input number should fall into the list to maintain sort order. Note that this is a little confusing to think about at first; In the case of 55 above, it returns 2 because it should be inserted at index 2 as it falls between the current values at indices 1 and 2. If you give it a number exactly on a range boundary, it will 'fall to the right', as evidenced by the bisect(l,25) example.
The linked documentation includes a set of recipes for searching through sorted lists using bisect.
Given an input val and a list of range delimiters delims, here are two approaches:
# Both methods require range_delims to be sorted
range_delims = [25,50,75,100]
# Simple way
def find_range1(val, delims):
for i,d in enumerate(delims):
if val < d: return i
print find_range1(10, range_delims) # 0
print find_range1(17, range_delims) # 0
print find_range1(32, range_delims) # 1
print find_range1(64, range_delims) # 2
print find_range1(96, range_delims) # 3
print find_range1(101, range_delims) # None
# More explicit, possibly unnecessarily so
import math
def find_range2(val, delims):
lbl = [float('-inf')] + delims
ubl = delims + [float('inf')]
for (i,(lb,ub)) in enumerate(zip(lbl, ubl)):
if lb <= val < ub: return i
print find_range2(10, range_delims) # 0
print find_range2(17, range_delims) # 0
print find_range2(32, range_delims) # 1
print find_range2(64, range_delims) # 2
print find_range2(96, range_delims) # 3
print find_range2(101, range_delims) # 4
The first just compares val to the elements of delims and when it finds that val is less than the element, returns the index of that element.
The second is a little more verbose, generating both upper and lower bounds, and ensuring that val is between them. For interior elements of delims the bounds are list elements, for the 2 exterior elements of delims, the bounds are the element and either + or - infinity.
Note: Both approaches require the input list of delimiters to be sorted. There are ways to deal with different delimiter list formats, but it looks like you have a sorted list of delimiters (or could sort it without issue).
Hi I'm trying to make a list of the maximum value of a unique string within a list.
example:
a = ['DS10.json', 'DS11.json', 'DT4.json', 'DT5.json', 'DT6.json', 'CJ6.json', 'CJ7.json']
should return me a list of the following:
['DS11.json', 'DT6.json', 'CJ7.json']
I have tried the following code:
def j(l):
p = []
for i in l:
digcode = i.split('.')[0]
if any(s.startswith(digcode[:2]) for s in p): #there exists prefex in list
if digcode[2:] > p[[n for n, l in enumerate(p) if l.startswith(digcode[:2])][0]][2:]:
p.pop([n for n, l in enumerate(p) if l.startswith(digcode[:2])][0])
p.append(digcode)
else:
pass
else:
p.append(digcode)
return p
But when I apply it to a larger sample it does not do an accurate job
>>> o = ['AS6.json', 'AS7.json', 'AS8.json', 'AS9.json', 'BS1.json', 'BS2.json', 'BS3.json', 'BS4.json', 'BS5.json', 'CS1.json', 'CS2.json', 'CS3.json', 'CS4.json', 'CS5.json', 'CS6.json', 'DS10.json', 'DS11.json', 'DS4.json', 'DS5.json', 'DS6.json', 'DS7.json', 'DS8.json', 'DS9.json', 'ES4.json', 'ES5.json', 'ES6.json', 'FS5.json', 'FS6.json', 'FS7.json', 'FS8.json', 'MS4.json', 'MS5.json', 'MS6.json', 'MS7.json', 'MS8.json', 'MS9.json', 'NR1.json', 'NR2.json', 'NR3.json', 'NR4.json', 'NR5.json', 'NR6.json', 'NR7.json', 'NR8.json', 'VR1.json', 'VR2.json', 'VR3.json', 'VR4.json', 'VR5.json', 'VR6.json', 'VR7.json', 'VR8.json', 'XS11.json', 'XS9.json']
>>> j(o)
['AS9', 'BS5', 'CS6', 'DS9', 'ES6', 'FS8', 'MS9', 'NR8', 'VR8', 'XS9']
which is incorrect as there is a XS11 and DS11 as an example.
I would appreciate if someone could help me rectify my problem or perhaps find a simpler solution to my problem. Thank you
You are making string comparisons; '9' is greater than '11' because the character '9' comes later in the alphabet. You'll have to convert those to integers first.
I'd use a dictionary to map prefixes to the maximum number:
def find_latest(lst):
prefixes = {}
for entry in lst:
code, value = entry[:2], int(entry.partition('.')[0][2:])
if value > prefixes.get(code, (float('-inf'), ''))[0]:
prefixes[code] = (value, entry)
return [entry for value, entry in prefixes.values()]
This is far more efficient as it doesn't loop over your whole input list each time; you are processing the list N^2 times (add one element and you are adding N tests to work through); it processes your list in N steps instead. So instead of 100 tests for 10 elements, this just executes 10 tests.
Demo:
>>> sample = ['AS6.json', 'AS7.json', 'AS8.json', 'AS9.json', 'BS1.json', 'BS2.json', 'BS3.json', 'BS4.json', 'BS5.json', 'CS1.json', 'CS2.json', 'CS3.json', 'CS4.json', 'CS5.json', 'CS6.json', 'DS10.json', 'DS11.json', 'DS4.json', 'DS5.json', 'DS6.json', 'DS7.json', 'DS8.json', 'DS9.json', 'ES4.json', 'ES5.json', 'ES6.json', 'FS5.json', 'FS6.json', 'FS7.json', 'FS8.json', 'MS4.json', 'MS5.json', 'MS6.json', 'MS7.json', 'MS8.json', 'MS9.json', 'NR1.json', 'NR2.json', 'NR3.json', 'NR4.json', 'NR5.json', 'NR6.json', 'NR7.json', 'NR8.json', 'VR1.json', 'VR2.json', 'VR3.json', 'VR4.json', 'VR5.json', 'VR6.json', 'VR7.json', 'VR8.json', 'XS11.json', 'XS9.json']
>>> def find_latest(lst):
... prefixes = {}
... for entry in lst:
... code, value = entry[:2], int(entry.partition('.')[0][2:])
... if value > prefixes.get(code, (float('-inf'), ''))[0]:
... prefixes[code] = (value, entry)
... return [entry for value, entry in prefixes.values()]
...
>>> find_latest(sample)
['FS8.json', 'VR8.json', 'AS9.json', 'MS9.json', 'BS5.json', 'CS6.json', 'XS11.json', 'NR8.json', 'DS11.json', 'ES6.json']
It looks as though your digcode[2:] values are being compared lexicographically (dictionary order), rather than numerically.
So 9 is considered to be "larger than" 11, because in a list of words, a word that began with "9" would come after a word that began with "11".
For comparison purposes you should convert digcode[2:] to a number i.e. int(digcode[2:])
if digcode[2:] > p[[n for n, l in enumerate(p) if l.startswith(digcode[:2])][0]][2:]:
to
if int(digcode[2:]) > int(p[[n for n, l in enumerate(p) if l.startswith(digcode[:2])][0]][2:]):
This gives:
>>> j(o)
['AS9', 'BS5', 'CS6', 'DS11', 'ES6', 'FS8', 'MS9', 'NR8', 'VR8', 'XS11']