Related
Let's say I have a number, l, and I'd like to split it up into roughly equal n chunks. For example:
l = 11
n = 3
step = 1 + l // n
for start in range(0, l, step):
stop = min(l, start+step)
print(start, stop)
In this case, the first chunk (chunk 0) goes from 0 to 4 (5 elements), the next chunk (chunk 1) goes from 4 to 8 (5 elements), and the last chunk (chunk 2) is slightly smaller and goes from 8 to 11 (4 elements). Of course, the values of l and n may vary but both values will always be positive integers and n will always be smaller than l.
What I need to do is to generate a list that will iterate through each chunk in a round-robin fashion and append some chunk information to a list. The list should contain a tuple of the chunk number (i.e., 0, 1, or 2) and the next available start value in that chunk (until that chunk is exhausted as controlled by the stop value). So, the output list would be:
[(0,0), (1,4), (2,8), (0,1), (1,5), (2,9), (0,2), (1,6), (2,10), (0,3), (1,7)]
Note that the last chunk has one last element than the first two chunks. Whatever the solution is, it needs to work for any l and n (as long as both values are positive integers and n is always smaller than l). For simplicity, you can assume that l will be less than 100,000,000.
What is the best way to generate this list?
Use two loops for the two levels of your problem. The outer loop runs the starting point through all numbers in range(step). From there, use that value as the starting point for the inner loop you already wrote. Note that you have to adjust your output: you're printing (start, stop) values, when your requested output has (chunk#, start) values.
Can you take it from there?
One possible solution, using generators:
from itertools import islice, zip_longest, cycle
def chunk(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
def generate(l, n):
c, step = cycle(range(n)), l // n + (l % n != 0)
yield from ((next(c), v) for vals in zip_longest(*chunk(range(l), step)) for v in vals if v is not None)
l = 11
n = 3
out = [*generate(l, n)]
print(out)
Prints:
[(0, 0), (1, 4), (2, 8), (0, 1), (1, 5), (2, 9), (0, 2), (1, 6), (2, 10), (0, 3), (1, 7)]
For:
l = 9
n = 3
The output is:
[(0, 0), (1, 3), (2, 6), (0, 1), (1, 4), (2, 7), (0, 2), (1, 5), (2, 8)]
I'm studying algorithms and one of the questions asks me to build an in-house calendar tool which stores data as a tuple of integers. My goal is to write a function that takes a list of multiple meeting time ranges and returns a list of condensed ranges.
So far, I've written pseudo-code and some real code. However, I'm trying to append overlapping times (represented as integers) and I'm stuck. Here's what I have so far:
# Variable containing list of tuples
meeting_times = [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]
# Sort meetings by start time
ordered_list = sorted(meeting_times)
# For each number in the variable
for m in range(ordered_list):
# If number overlaps with another number in variable
if m[0:] >= m[:-1]:
# Append start time of first number to end time of last number
append_meeting_time =
else:
# Continue on and loop through variable.
While it's not complete, I'd like to know if I'm the right path. If not, how can I improve my answer?
Assuming your meeting times will not overlap each other because you cannot be in two places in the same time. Thus, (1,4) (3,5) cannot happen.
One way to do this is to iterate with a variable that remembers the previous tuple, in this case I'll name it "previous", I'll keep your variable "m" as it is.
Every time you go on the next tuple in your meeting times, you compare the ending meeting time from previous tuple with current starting meeting time. Then you remove the previous tuple because it will be a duplicate. If no condense operation, then you can just append to the list and then set "m" to "previous" for the next iteration.
# Variable containing list of tuples
meeting_times = [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]
# Sort meetings by start time
ordered_list = sorted(meeting_times)
print('Given time tuple: ', ordered_list)
def condensed_meeting_time(append_meeting_time, previous):
# For each number in the variable
for m in ordered_list:
if previous: # This is to catch empty tuple that was initial
if m[0] == previous[1]: # If current starting meeting time is equal to previous ending meeting time ...
del append_meeting_time[-1] # Remove the previous element
append_meeting_time.append( (previous[0], m[1]) ) # Create new tuple to merge previous start time to current ending time
else: # If the time does not overlap, append normally and set current meeting time to 'previous' for next iteration
append_meeting_time.append(m)
previous = m
else: # Base case to append the first element.
append_meeting_time.append(m)
previous = m
return append_meeting_time
print('After condensed range: ',condensed_meeting_time([], ()))
This is the output I've got:
Given time tuple: [(0, 1), (3, 5), (4, 8), (9, 10), (10, 12)]
After condensed range: [(0, 1), (3, 5), (4, 8), (9, 12)]
I hope this helps.
I want to sort a list of tuples in a consecutive order, so the first element of each tuple is equal to the last element of the previous one.
For example:
input = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
output = [(10, 7), (7, 13), (13, 4), (4, 9), (9, 10)]
I have developed a search like this:
output=[]
given = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
t = given[0][0]
for i in range(len(given)):
# search tuples starting with element t
output += [e for e in given if e[0] == t]
t = output[-1][-1] # Get the next element to search
print(output)
Is there a pythonic way to achieve such order?
And a way to do it "in-place" (with only a list)?
In my problem, the input can be reordered in a circular way using all the tuples, so it is not important the first element chosen.
Assuming your tuples in the list will be circular, you may use dict to achieve it within complexity of O(n) as:
input = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
input_dict = dict(input) # Convert list of `tuples` to dict
elem = input[0][0] # start point in the new list
new_list = [] # List of tuples for holding the values in required order
for _ in range(len(input)):
new_list.append((elem, input_dict[elem]))
elem = input_dict[elem]
if elem not in input_dict:
# Raise exception in case list of tuples is not circular
raise Exception('key {} not found in dict'.format(elem))
Final value hold by new_list will be:
>>> new_list
[(10, 7), (7, 13), (13, 4), (4, 9), (9, 10)]
if you are not afraid to waste some memory you could create a dictionary start_dict containing the start integers as keys and the tuples as values and do something like this:
tpl = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
start_dict = {item[0]: item for item in tpl}
start = tpl[0][0]
res = []
while start_dict:
item = start_dict[start]
del start_dict[start]
res.append(item)
start = item[-1]
print(res)
if two tuples start with the same number you will lose one of them... if not all the start numbers are used the loop will not terminate.
but maybe this is something to build on.
Actually there're many questions about what you intend to have as an output and what if the input list has invalid structure to do what you need.
Assuming you have an input of pairs where each number is included twice only. So we can consider such input as a graph where numbers are nodes and each pair is an edge. And as far as I understand your question you suppose that this graph is cyclic and looks like this:
10 - 7 - 13 - 4 - 9 - 10 (same 10 as at the beginning)
This shows you that you can reduce the list to store the graph to [10, 7, 13, 4, 9]. And here is the script that sorts the input list:
# input
input = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
# sorting and archiving
first = input[0][0]
last = input[0][1]
output_in_place = [first, last]
while last != first:
for item in input:
if item[0] == last:
last = item[1]
if last != first:
output_in_place.append(last)
print(output_in_place)
# output
output = []
for i in range(len(output_in_place) - 1):
output.append((output_in_place[i], output_in_place[i+1]))
output.append((output_in_place[-1], output_in_place[0]))
print(output)
I would first create a dictionary of the form
{first_value: [list of tuples with that first value], ...}
Then work from there:
from collections import defaultdict
chosen_tuples = input[:1] # Start from the first
first_values = defaultdict()
for tup in input[1:]:
first_values[tup[0]].append(tup)
while first_values: # Loop will end when all lists are removed
value = chosen_tuples[-1][1] # Second item of last tuple
tuples_with_that_value = first_values[value]
chosen_tuples.append(tuples_with_that_value.pop())
if not chosen_with_that_value:
del first_values[value] # List empty, remove it
You can try this:
input = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
output = [input[0]] # output contains the first element of input
temp = input[1:] # temp contains the rest of elements in input
while temp:
item = [i for i in temp if i[0] == output[-1][1]].pop() # We compare each element with output[-1]
output.append(item) # We add the right item to output
temp.remove(item) # We remove each handled element from temp
Output:
>>> output
[(10, 7), (7, 13), (13, 4), (4, 9), (9, 10)]
Here is a robust solution using the sorted function and a custom key function:
input = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
def consec_sort(lst):
def key(x):
nonlocal index
if index <= lower_index:
index += 1
return -1
return abs(x[0] - lst[index - 1][1])
for lower_index in range(len(lst) - 2):
index = 0
lst = sorted(lst, key=key)
return lst
output = consec_sort(input)
print(output)
The original list is not modified. Note that sorted is called 3 times for your input list of length 5. In each call, one additional tuple is placed correctly. The first tuple keeps it original position.
I have used the nonlocal keyword, meaning that this code is for Python 3 only (one could use global instead to make it legal Python 2 code).
My two cents:
def match_tuples(input):
# making a copy to not mess up with the original one
tuples = input[:] # [(10,7), (4,9), (13, 4), (7, 13), (9, 10)]
last_elem = tuples.pop(0) # (10,7)
# { "first tuple's element": "index in list"}
indexes = {tup[0]: i for i, tup in enumerate(tuples)} # {9: 3, 4: 0, 13: 1, 7: 2}
yield last_elem # yields de firts element
for i in range(len(tuples)):
# get where in the list is the tuple which first element match the last element in the last tuple
list_index = indexes.get(last_elem[1])
last_elem = tuples[list_index] # just get that tuple
yield last_elem
Output:
input = [(10,7), (4,9), (13, 4), (7, 13), (9, 10)]
print(list(match_tuples(input)))
# output: [(10, 7), (7, 13), (13, 4), (4, 9), (9, 10)]
this is a (less efficient than the dictionary version) variant where the list is changed in-place:
tpl = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
for i in range(1, len(tpl)-1): # iterate over the indices of the list
item = tpl[i]
for j, next_item in enumerate(tpl[i+1:]): # find the next item
# in the remaining list
if next_item[0] == item[1]:
next_index = i + j
break
tpl[i], tpl[next_index] = tpl[next_index], tpl[i] # now swap the items
here is a more efficient version of the same idea:
tpl = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
start_index = {item[0]: i for i, item in enumerate(tpl)}
item = tpl[0]
next_index = start_index[item[-1]]
for i in range(1, len(tpl)-1):
tpl[i], tpl[next_index] = tpl[next_index], tpl[i]
# need to update the start indices:
start_index[tpl[next_index][0]] = next_index
start_index[tpl[i][0]] = i
next_index = start_index[tpl[i][-1]]
print(tpl)
the list is changed in-place; the dictionary only contains the starting values of the tuples and their index in the list.
To get a O(n) algorithm one needs to make sure that one doesn't do a double-loop over the array. One way to do this is by keeping already processed values in some sort of lookup-table (a dict would be a good choice).
For example something like this (I hope the inline comments explain the functionality well). This modifies the list in-place and should avoid unnecessary (even implicit) looping over the list:
inp = [(10, 7), (4, 9), (13, 4), (7, 13), (9, 10)]
# A dictionary containing processed elements, first element is
# the key and the value represents the tuple. This is used to
# avoid the double loop
seen = {}
# The second value of the first tuple. This must match the first
# item of the next tuple
current = inp[0][1]
# Iteration to insert the next element
for insert_idx in range(1, len(inp)):
# print('insert', insert_idx, seen)
# If the next value was already found no need to search, just
# pop it from the seen dictionary and continue with the next loop
if current in seen:
item = seen.pop(current)
inp[insert_idx] = item
current = item[1]
continue
# Search the list until the next value is found saving all
# other items in the dictionary so we avoid to do unnecessary iterations
# over the list.
for search_idx in range(insert_idx, len(inp)):
# print('search', search_idx, inp[search_idx])
item = inp[search_idx]
first, second = item
if first == current:
# Found the next tuple, break out of the inner loop!
inp[insert_idx] = item
current = second
break
else:
seen[first] = item
if i have a tuple set of numbers:
locSet = [(62.5, 121.0), (62.50000762939453, 121.00001525878906), (63.0, 121.0),(63.000003814697266, 121.00001525878906), (144.0, 41.5)]
I want to group them with a tolerance range of +/- 3.
aFunc(locSet)
which returns
[(62.5, 121.0), (144.0, 41.5)]
I have seen Identify groups of continuous numbers in a list but that is for continous integers.
If I have understood well, you are searching the tuples whose values differs in an absolute amount that is in the tolerance range: [0, 1, 2, 3]
Assuming this, my solution returns a list of lists, where every internal list contains tuples that satisfy the condition.
def aFunc(locSet):
# Sort the list.
locSet = sorted(locSet,key=lambda x: x[0]+x[1])
toleranceRange = 3
resultLst = []
for i in range(len(locSet)):
sum1 = locSet[i][0] + locSet[i][1]
tempLst = [locSet[i]]
for j in range(i+1,len(locSet)):
sum2 = locSet[j][0] + locSet[j][1]
if (abs(sum1-sum2) in range(toleranceRange+1)):
tempLst.append(locSet[j])
if (len(tempLst) > 1):
for lst in resultLst:
if (list(set(tempLst) - set(lst)) == []):
# This solution is part of a previous solution.
# Doesn't include it.
break
else:
# Valid solution.
resultLst.append(tempLst)
return resultLst
Here two use examples:
locSet1 = [(62.5, 121.0), (62.50000762939453, 121.00001525878906), (63.0, 121.0),(63.000003814697266, 121.00001525878906), (144.0, 41.5)]
locSet2 = [(10, 20), (12, 20), (13, 20), (14, 20)]
print aFunc(locSet1)
[[(62.5, 121.0), (144.0, 41.5)]]
print aFunc(locSet2)
[[(10, 20), (12, 20), (13, 20)], [(12, 20), (13, 20), (14, 20)]]
I hope to have been of help.
So I am making a program that generates a list of prime numbers and then sorts them into twin prime pairs, then calculates out what two sets of twin primes have the largest difference. I have gotten to sorting it into a list of twin prime pairs with my code, but now I am having a hard time figuring out how to make the next part happen. I am not quite sure how I can calculate the largest gap between the primes. Here is what I got so far:
def is_factor(n,f):
'''
Returns True if f is a factor of n,
OTW returns False.
Both n and f are ints.
'''
TV = (n % f == 0)
return TV
def properFactorsOf(n):
'''
Returns a list of the proper factors
of n. n is an int.
f is a proper factor of n if:
f is a factor of n
f > 1 and f < n.
'''
L = []
upper = n//2 # largest f to test
for f in range(2,upper + 1):
if is_factor(n,f):
L.append(f)
return L
def is_prime(n):
'''
Returns True if n is a prime,
OTW returns False.
n is an int.
Use properFactorsOf(n) to check whether n is prime.
'''
TV = len(properFactorsOf(n)) == 0
return TV
def LofPrimes(n):
'''
Returns a list of the first n primes.
Uses is_prime(n).
'''
primes = [2,3]
p = 5
while len(primes) < n:
if is_prime(p):
primes.append(p)
p += 2
return primes
def twins():
n = eval(input("How many primes?"))
L = (LofPrimes(n)) #Makes a list consisting of the function LofPrimes(n)
m = 1 #If m is zero it would take the zeroth position
L3 = [] # This is the list of twins
for i in range(len(L)-1): #keeps it in range
tp = L[m]-L[m-1] #subtract pos 1 from pos zero
if tp == 2: # If the difference of the pair is 2
L3.append(([L[m-1],L[m]])) #add the twins to the list L3
m += 1 # set m back to 1 at the end of the loop
print(L3)
So I feel like I am kind of on the right path, I made some pseudo code to give you an idea on where my thought is going:
assign a temp variable to m-1 on the first run,
assign a temp variable to m on the second run,
make a loop to go through the list of twins
take the difference of m-1 from the first set and m from the second set
in this loop calculate the max gap
return the greatest difference
Suppose we have a list of the pairs of primes, what you call L3 which could be like this:
L3 = [(3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61),
(71, 73), (101, 103), (107, 109), (137, 139)]
Then what we want to do is take the first element of a pair minus the last element of the previous pair.
We also want to accumulate a list of these values so later we can see at which index the maximum happens. The reduce function is good for this.
def helper_to_subtract_pairs(acc, x):
return acc[:-1] + [x[0] - acc[-1]] + [x[1]]
Then printing
reduce(helper_to_subtract_pairs, L3, [0])
gives
[3, 0, 4, 4, 10, 10, 16, 10, 28, 4, 28, 139]
The first element happens because inside the call to reduce we use a starting value of [0] (so the first prime 3 leads to 3 - 0). We can ignore that. The final item, 139, represents the number that would be part of the subtraction if there was one more pair on the end. But since there is not, we can ignore that too:
In [336]: reduce(helper_to_subtract_pairs, L3, [0])[1:-1]
Out[336]: [0, 4, 4, 10, 10, 16, 10, 28, 4, 28]
Now, we want the index(es) where the max occurs. For this let's use the Python recipe for argmax:
def argmax(some_list):
return max(enumerate(some_list), key=lambda x:x[1])[0]
Then we get:
In [338]: argmax(reduce(helper_to_subtract_pairs, L3, [0])[1:-1])
Out[338]: 7
telling us that the gap at index 7 is the biggest (or at least tied for the biggest). The 7th gap in this example was between (101, 103) and (71, 73) (remember Python is 0-based, so the 7th gap is really between pair 7 and pair 8).
So as a function of just the list L3, we can write:
def max_gap(prime_list):
gaps = reduce(helper_to_subtract_pairs, prime_list, [0])[1:-1]
max_idx = argmax(gaps)
return gaps[max_idx], prime_list[max_idx:max_idx + 2]
The whole thing could look like:
def argmax(some_list):
return max(enumerate(some_list), key=lambda x:x[1])[0]
def helper_to_subtract_pairs(acc, x):
return acc[:-1] + [x[0] - acc[-1]] + [x[1]]
def max_gap(prime_list):
gaps = reduce(helper_to_subtract_pairs, prime_list, [0])[1:-1]
max_idx = argmax(gaps)
return gaps[max_idx], prime_list[max_idx:max_idx + 2]
L3 = [(3, 5), (5, 7), (11, 13), (17, 19), (29, 31), (41, 43), (59, 61),
(71, 73), (101, 103), (107, 109), (137, 139)]
print max_gap(L3)
which prints
In [342]: print max_gap(L3)
(28, [(71, 73), (101, 103)])
It's going to be more effort to modify argmax to return all occurrences of the max. If this is for investigating the asymptotic growth of the gap size, you won't need to, since any max will do to summarize a particular collection of primes.