How to exclude a range from another range in Python? - python

Suppose I've a range (section) and an additional list of ranges to exclude, represented by tuples (start, end):
section=(1, 100) #Range from 1 to 100
toexclude=[(10,15),(40,50),(80,83)] #3 sub-ranges
I'm looking for an efficient algorithm which returns, from these two inputs, a new list of ranges like:
[(1,9),(16,39),(51,79),(84,100)]
Which is the main range exluding the second list of ranges.
Thanks!
EDIT:
Actually the advice from deceze to use intervaltree seems to be interesting. With a few lines:
from intervaltree import Interval, IntervalTree
t=IntervalTree()
t[1:100]="main"
t.chop(10,15)
t.chop(40,50)
t.chop(80,83)
t
IntervalTree([Interval(1, 10, 'main'), Interval(15, 40, 'main'), Interval(50, 80, 'main'), Interval(83, 100, 'main')])
The intervals are considered closed apparently, but this is a minor issue.

section=(1, 100) #Range from 1 to 100
toexclude=[(10,15),(40,50),(80,83)] #3 sub-ranges
rangelists = [x for excRange in toexclude for x in range(excRange[0], excRange[1] + 1)]
first, last = section[0], section[0]
out_ranges = []
for x in range(section[0],section[1] + 1):
if x not in rangelists:
if first == 'unset':
first = x
last = x
elif x in rangelists:
if last == x - 1:
out_ranges.append((first, last))
first = 'unset'
else:
continue
if first != 'unset':
out_ranges.append((first, last))
print out_ranges

Somethink like this?
start, end = section
this_start = start
result = []
for exc_start, exc_end in toexclude:
this_end = exc_start - 1
result.append((this_start, this_end))
this_start = exc_end + 1
result.append((this_start, end))
EDIT: added if clause to correct according to Paco H. comment
start, end = section
this_start = start
result = []
for exc_start, exc_end in toexclude:
if this_end == start:
start = exc_end + 1
else:
this_end = exc_start - 1
result.append((this_start, this_end))
this_start = exc_end + 1
if this_end <= end:
result.append((this_start, end))

section=(1, 100) #Range from 1 to 100
toexclude=[(10,15),(40,50),(80,83)] #3 sub-rang
list1 = []
list2 = [section[0]]
[list1.append(x[0]-1) for x in toexclude]
[list2.append(x[1]+1) for x in toexclude]
list1.append(section[1])
print list(zip(list2, list1)
# [(1, 9), (16, 39), (51, 79), (84, 100)]

Related

How to call a function on list to return incremental bits range

I am trying to write a function that returns from and to bits in [from:to] format.
I am not quite sure how exactly it can be done (recursively?). The expected output is in incremental range of bits. Here is the piece of code to start with,
cntlist = [5,1,4,3,1]
def find_size(cnt):
if cnt>1:
a = "[%s:%s]" % (cnt-1, cnt-cnt)
left = cnt-1
right = cnt-cnt
if cnt==1:
a = "[%s]" % (cnt)
left = a
right = a
return a, left, right
newlist = list(map(find_size, cntlist))
print(newlist)
Output:
[('[4:0]', 4, 0), ('[1]', '[1]', '[1]'), ('[3:0]', 3, 0), ('[2:0]', 2, 0), ('[1]', '[1]', '[1]')]
Expected output:
['[4:0]', '[5]', '[9:6]', '[12:10]', '[13]']
Note: If size is 1 in cntlist, the range will have only one element which will be +1 to previous range's left number.
IIUC, a simple loop should work:
def bitrange(cntlst):
out = []
total = 0
for i in cntlst:
prev = total
total += i
if i == 1:
out.append(f'[{total-1}]')
else:
out.append(f'[{total-1}:{prev}]')
return out
bitrange([5,1,4,3,1])
output:
['[4:0]', '[5]', '[9:6]', '[12:10]', '[13]']

How to use a step outside the range() function

I'm working on this code :
Alright = []
for i in range(0, 384, 48):
Alright.append(i)
Alright2 = []
Alright3 = []
for j in range(len(Alright)):
if j % 2 == 0:
Alright2 = (Alright[j], Alright[j+1])
Alright3.append(Alright2)
print(Alright3)
Out : [(0, 48), (96, 144), (192, 240), (288, 336)]
That's Good i wanted those ranges. Now what i want to do is the same thing but for ranges that are not in the past one (Alright3) and get a list with ranges that are not in Alright3, so the output must be like shown in the next step (Wanted Out). Here is my code :
Alright4 = []
for i in range(49, 384, 46):
Alright4.append(i)
Alright5 = []
Alright6 = []
for j in range(len(Alright4)):
if j % 2 == 0:
Alright5 = (Alright4[j], Alright4[j+1])
Alright6.append(Alright5)
print(Alright6)
Out : [(49, 95), (141, 187), (233, 279), (325, 371)]
I want to have this out :
Wanted Out : [(49, 95), (145, 191), (241, 287), (337, 384)]
First, let's refactor your initial code a bit:
Alright = list(range(0, 384, 48))
Alright3 = []
for j in range(0, len(Alright), 2):
Alright2 = (Alright[j], Alright[j+1])
Alright3.append(Alright2)
Now I understand what you're trying to do. I would write it as follows:
ranges = [(i, i + 48) for i in range(0, 384, 48 * 2)]
So because we're going to skip every other entry anyway, why not make the original range with a step twice as large? We also know the second value of each tuple has to be 48 more than the first value of that tuple.
This has the advantage we can use that same method to find the complement:
other_ranges = [(i, i + 46) for i in range(49, 384, 48 * 2)]
Note that the step is the same, but the second element of each tuple is only 46 more than the first.
The only issue is that the last value is 383 rather than 384 as you specified. I believe this is probably going to be the right value, but it's possible to correct afterwards if you really do need 384:
first_value, second_value = other_ranges[-1]
other_ranges[-1] = first_value, second_value + 1
I think it would be a little overcomplicated run a new loops. Just try adjusting your first method like this:
Alright = []
for i in range(0, 384, 48):
Alright.append(i)
Alright2 = []
Alright3 = []
Alright4 = [] #numbers not in Alright3
last = None #saves the higher value of the last added tuple
for j in range(len(Alright)):
if j % 2 == 0:
if last != None: #made to skip the first iteration
Alright4.append((last+1, Alright[j]-1)) #saves the range between the last added tuple and the one being added in this iteration
Alright2 = (Alright[j], Alright[j+1])
Alright3.append(Alright2)
last = Alright[j+1] #saves the higher value of the last added tuple
if last != 384:
Alright4.append((last+1, 384))
print(Alright4)
The contents of Alright4 will look like this:
Out : [(49, 95), (145, 191), (241, 287), (337, 384)]
Alright4 = []
mul = 4
for i in range(49, 384, 46):
Alright4.append(i)
print(Alright4)
Alright5 = []
Alright6 = []
for j in range(len(Alright4)):
if j % 2 == 0:
if j == 0:
Alright5 = (Alright4[j], Alright4[j+1])
Alright6.append(Alright5)
else:
if j == len(Alright4)-2:
mul += 1
Alright5 = (Alright4[j]+mul, Alright4[j+1]+mul)
Alright6.append(Alright5)
mul += 4
print(Alright6)

How can I make a multiple for loop with each value getting in one at a time?

T_list = []
Temp_k = np.linspace(298, 398, 10)
#print (Temp_k)
current = np.linspace(0, 1.4, 5)
ppH2O = np.linspace(-2, -1, 5)
H2_pressure = []
H2O_pp = ppH2O
for i in (Temp_k):
print(i, 'i')
for j in (H2O_pp):
print(j, 'j')
for k in (current):
print (k, 'k')
partial_H2 = 5*np.exp((1.653 * k)/i) - 1/j
H2_pressure.append(partial_H2)
#print (H2_pressure)
I want to make a list of an array that gives me the values of
5*np.exp((1.653 * k)/i) - 1/j,
At each Temp_k, H2O_pp, current.
For example, value of when Temp_k , H2O_pp and current is at their initial value,
value at their second value, .... till it reaches to the end values.
Could someone please help me with this?
What about this
import numpy as np
temp_k = np.linspace(298, 398, 10)
pp_h2o = np.linspace(-2, -1, 5)
currents = np.linspace(0, 1.4, 5)
h2_pressures = []
for temp in temp_k:
for pp in pp_h2o:
for current in currents:
h2_pressure = 5 * np.exp((1.653 * current) / temp) - 1 / pp
h2_pressures.append(h2_pressure)
print(f'temp: {temp:0.1f}, pp: {pp:.2f}, current: {current:.3f}, h2_pressure: {h2_pressure:.4f}')
#print(h2_pressures)
Changes:
choose more logical variable names conform Python convention
removed the () around the lists in the for loops
put the h2_pressures.append in the inner for loop
format the print out
Your problem is you put append outside of every loop. You should append what you calculated at the most inner loop, where you calculated it. Otherwise when loop is terminated there is no access to previously calculated values.
T_list = []
Temp_k = np.linspace(298, 398, 10)
#print (Temp_k)
current = np.linspace(0, 1.4, 5)
ppH2O = np.linspace(-2, -1, 5)
H2_pressure = []
H2O_pp = ppH2O
for i in (Temp_k):
print(i, 'i')
for j in (H2O_pp):
print(j, 'j')
for k in (current):
print (k, 'k')
partial_H2 = 5*np.exp((1.653 * k)/i) - 1/j
H2_pressure.append(partial_H2)

Minimum number of steps to reach a given number

I need to calculate the minimum number of ways to reach a value, x, from value n, by adding/subtracting a list of values, l, to n.
For example: Value n = 100, value X = 45
List, l,: 50,6,1
The best way to do this is to say:
100-50-6+1 = 45
I want a programme to work this out for any value of x and n given list, l
I am really struggling to outline how I would write this.
I am confused about how to overcome the following issues:
How to inform the programme if I should attempt an addition or
subtraction and how many times this should be done. For example I
might need to subtract, then add, then subtract again to reach a
solution
How do I include enough for/while loops to ensure I can provide a
solution for all possible input values
Has anyone come across an issue like this before and have any ideas how I could outline the code for such a solution (I am using Python if it helps direct me towards learning about particular functions available that could assist me)
Thanks
This is my attempt so far but I am stuck
inputA = ""
while inputA == "":
inputA = input("""Please enter two numbers, separated by a comma.
The first value should indicate the number of jugs:
The second value should indicate the volume to be measured
""")
itemList = list(inputA.split(","))
valueToMeasure = int(itemList[1])
inputB = ""
while inputB == "":
inputB = input("Plese enter the volumes for the {} jug(s) listed: ".format((itemList[0])))
if len(inputB.split(",")) != int(itemList[0]):
inputB = ""
TargetVolume = itemList[1]
jugSizes = inputB.split(",")
print("Calculating: smallest number of steps to get", TargetVolume, "ml using jugs of sizes:", jugSizes)
jugSizes.sort()
jugSizes.reverse()
largestJug = int(jugSizes[0])
ratioTable = {}
for item in jugSizes:
firstVal = int(jugSizes[0])
itemV = int(item)
valueToAssign = firstVal/itemV
ratioTable[int(item)] = int(valueToAssign)
taskPossible = True
if valueToMeasure > largestJug:
print ("Impossible task")
taskPossible = False
newList = jugSizes
if taskPossible == True:
for item in jugSizes:
if item < TargetVolume: break
newList = newList[1:]
newDict = {}
for itemA in ratioTable:
if int(itemA) < int(item):
newDict[itemA]= ratioTable[itemA]
print ("Do work with these numbers:", newDict)
This is how I would approach the problem if I understand correctly.
X = 45
largest_jug = measured = 100
jug_sizes = [50, 6, 1]
steps = []
jug_to_use = 0
while measured != X:
if jug_to_use < len(jug_sizes) - 1: # we have smaller jugs in reserve
error_with_large_jug = min([abs(measured - jug_sizes[jug_to_use] - X), abs(measured + jug_sizes[jug_to_use] - X)])
error_with_small_jug = min([abs(measured - jug_sizes[jug_to_use + 1] - X), abs(measured + jug_sizes[jug_to_use + 1] - X)])
if error_with_small_jug < error_with_large_jug:
jug_to_use += 1
if measured > X:
measured -= jug_sizes[jug_to_use]
steps.append(('-', jug_sizes[jug_to_use]))
else:
measured += jug_sizes[jug_to_use]
steps.append(('+', jug_sizes[jug_to_use]))
print(steps)
Yielding
[('-', 50), ('-', 6), ('+', 1)]
It basically starts by using the largest jug, until it's in range of the next size and so on. We can test it with randomly sized jugs of [30, 7, 1] and see it again results in an accurate answer of [('-', 30), ('-', 30), ('+', 7), ('-', 1), ('-', 1)].
Important notes:
jug_sizes should be ordered largest to smallest
This solution assumes the X can be reached with the numbers provided in jug_sizes (otherwise it will infinitely loop)
This doesn't take into account that a jug size can make the target unreachable (i.e. [50, 12, 5] where the 12 size should be skipped, otherwise the solution is unreachable
This assumes every jug should be used (related to above point)
I'm sure you could figure out solutions for all these problems based on your specific circumstances though

Python interval interesction

My problem is as follows:
having file with list of intervals:
1 5
2 8
9 12
20 30
And a range of
0 200
I would like to do such an intersection that will report the positions [start end] between my intervals inside the given range.
For example:
8 9
12 20
30 200
Beside any ideas how to bite this, would be also nice to read some thoughts on optimization, since as always the input files are going to be huge.
this solution works as long the intervals are ordered by the start point and does not require to create a list as big as the total range.
code
with open("0.txt") as f:
t=[x.rstrip("\n").split("\t") for x in f.readlines()]
intervals=[(int(x[0]),int(x[1])) for x in t]
def find_ints(intervals, mn, mx):
next_start = mn
for x in intervals:
if next_start < x[0]:
yield next_start,x[0]
next_start = x[1]
elif next_start < x[1]:
next_start = x[1]
if next_start < mx:
yield next_start, mx
print list(find_ints(intervals, 0, 200))
output:
(in the case of the example you gave)
[(0, 1), (8, 9), (12, 20), (30, 200)]
Rough algorithm:
create an array of booleans, all set to false seen = [False]*200
Iterate over the input file, for each line start end set seen[start] .. seen[end] to be True
Once done, then you can trivially walk the array to find the unused intervals.
In terms of optimisations, if the list of input ranges is sorted on start number, then you can track the highest seen number and use that to filter ranges as they are processed -
e.g. something like
for (start,end) in input:
if end<=lowest_unseen:
next
if start<lowest_unseen:
start=lowest_unseen
...
which (ignoring the cost of the original sort) should make the whole thing O(n) - you go through the array once to tag seen/unseen and once to output unseens.
Seems I'm feeling nice. Here is the (unoptimised) code, assuming your input file is called input
seen = [False]*200
file = open('input','r')
rows = file.readlines()
for row in rows:
(start,end) = row.split(' ')
print "%s %s" % (start,end)
for x in range( int(start)-1, int(end)-1 ):
seen[x] = True
print seen[0:10]
in_unseen_block=False
start=1
for x in range(1,200):
val=seen[x-1]
if val and not in_unseen_block:
continue
if not val and in_unseen_block:
continue
# Must be at a change point.
if val:
# we have reached the end of the block
print "%s %s" % (start,x)
in_unseen_block = False
else:
# start of new block
start = x
in_unseen_block = True
# Handle end block
if in_unseen_block:
print "%s %s" % (start, 200)
I'm leaving the optimizations as an exercise for the reader.
If you make a note every time that one of your input intervals either opens or closes, you can do what you want by putting together the keys of opens and closes, sort into an ordered set, and you'll be able to essentially think, "okay, let's say that each adjacent pair of numbers forms an interval. Then I can focus all of my logic on these intervals as discrete chunks."
myRange = range(201)
intervals = [(1,5), (2,8), (9,12), (20,30)]
opens = {}
closes = {}
def open(index):
if index not in opens:
opens[index] = 0
opens[index] += 1
def close(index):
if index not in closes:
closes[index] = 0
closes[index] += 1
for start, end in intervals:
if end > start: # Making sure to exclude empty intervals, which can be problematic later
open(start)
close(end)
# Sort all the interval-endpoints that we really need to look at
oset = {0:None, 200:None}
for k in opens.keys():
oset[k] = None
for k in closes.keys():
oset[k] = None
relevant_indices = sorted(oset.keys())
# Find the clear ranges
state = 0
results = []
for i in range(len(relevant_indices) - 1):
start = relevant_indices[i]
end = relevant_indices[i+1]
start_state = state
if start in opens:
start_state += opens[start]
if start in closes:
start_state -= closes[start]
end_state = start_state
if end in opens:
end_state += opens[end]
if end in closes:
end_state -= closes[end]
state = end_state
if start_state == 0:
result_start = start
result_end = end
results.append((result_start, result_end))
for start, end in results:
print(str(start) + " " + str(end))
This outputs:
0 1
8 9
12 20
30 200
The intervals don't need to be sorted.
This question seems to be a duplicate of Merging intervals in Python.
If I understood well the problem, you have a list of intervals (1 5; 2 8; 9 12; 20 30) and a range (0 200), and you want to get the positions outside your intervals, but inside given range. Right?
There's a Python library that can help you on that: python-intervals (also available from PyPI using pip). Disclaimer: I'm the maintainer of that library.
Assuming you import this library as follows:
import intervals as I
It's quite easy to get your answer. Basically, you first want to create a disjunction of intervals based on the ones you provide:
inters = I.closed(1, 5) | I.closed(2, 8) | I.closed(9, 12) | I.closed(20, 30)
Then you compute the complement of these intervals, to get everything that is "outside":
compl = ~inters
Then you create the union with [0, 200], as you want to restrict the points to that interval:
print(compl & I.closed(0, 200))
This results in:
[0,1) | (8,9) | (12,20) | (30,200]

Categories