Reputation: 21
I'm currently trying to iterate through a small list of integers and update any values which fail to meet a condition of absolute difference. The aim is to use this over multiple small lists as part of a much larger for loop.
I have the following list:
y_list = [16, 29, 10]
This list needs to satisfy two conditions:
If either of these conditions are not satisfied then the number should be adjusted to have a difference of at least 10. For example:
y_list[0]
is compared with y_list[1]
: It meets both conditions and moves on.
y_list[0]
is compared with y_list[2]
: It fails to meet condition 2 and adds 10 minus the existing difference.
y_list[1]
is compared with y_list[0]
: This now fails to meet both conditions. But rather than adjusting y_list[0]
it increases y_list[1]
by 10 minus the difference.
So far I've written the following code, which doesn't account for the final element of the example above. The print statements aren't necessary, but I've just been using them to help me ensure that the different parts of the loop are being triggered or not:
for i in range(len(y_list)):
print(f'Iteration {i}')
print(f'Old y_list: {y_list}')
for idx, value in enumerate(y_list):
difference = abs(value - y_list[i])
if value != y_list[i]:
print(f'Comparing {y_list[idx]} with {y_list[i]}')
print(f'Difference of {difference}')
if difference < 10:
print(f'Updating {y_list[idx]}\n')
y_list[idx] += 10 - difference
else:
continue
print()
print(f'New list{y_list}\n')
This gives me an updated list, but obviously it only iterates over the entire list for three rounds.
Output:
Iteration 0
Old y_list: [16, 29, 10]
Comparing 29 with 16
Difference of 13
Comparing 10 with 16
Difference of 6
Updating 10
New list[16, 29, 14]
Iteration 1
Old y_list: [16, 29, 14]
Comparing 16 with 29
Difference of 13
Comparing 14 with 29
Difference of 15
New list[16, 29, 14]
Iteration 2
Old y_list: [16, 29, 14]
Comparing 16 with 14
Difference of 2
Updating 16
Comparing 29 with 14
Difference of 15
New list[24, 29, 14]
I've attempted to use a while True
loop before the second for loop to continue the iteration haven't been successful.
I've seen examples of meeting conditions with the all()
function and itertools.takewhile()
but haven't been able get either to function with the while loop.
Any assistance very gratefully received!
EDIT
So just to come back on this and thanks for all your assistance. I found the @treuss solution to be the closest to what I was after, using pairwise
for checking.
Where I differed in my solution was on the adjust function, opting to use permutations
rather than pairwise. The reason for this was that I wanted to ensure that all possible combinations were assessed against each other, so that combinations could be tested after an update had been made, to ensure that it still met the necessary conditions. When using the pairwise
function I experienced some overlaps that didn't work in my final application. I also added a space
parameter rather than baking in the value, just to allow for some adjustment if necessary.
This has now allowed me to loop through the production of 52 plotly charts, with trace annotations appropriately spaced. It sets y values from the final y value of each trace and adjusts the final position of the label as necessary. The final asignment statement applies it to my annotations list of dictionaries which is read by plotly.
This is the final function that I used:
def adjust(l, space):
for (idx1,num1), (idx2,num2) in itertools.permutations(enumerate(l), 2):
difference = abs(l[idx1] - l[idx2])
if difference < space:
largest = max((idx1,num1), (idx2,num2), key=lambda x:x[1])
largest_index = largest[0]
l[largest_index] = l[largest_index] + (space - difference)
annotations[largest_index]['y'] = l[largest_index]
Upvotes: 1
Views: 362
Reputation: 2388
The requirements for the solution are not all clear, so I am making some additional assumptions:
Given the above, the best way is to work on sorted data, i.e. first compare the lowest to the second-lowest number, then the second-lowest to the third-lowest, etc. pairwise
from itertools
(available from Python 3.10 on) is great for doing such iterations, with older versions of python you can use zip to create something similar. Using pairwise
a check would boil down to a simple:
def check(l):
return all(x2-x1 >= 10 for x1,x2 in itertools.pairwise(sorted(l)))
For older python versions, use zip
on the sorted items:
def check2(l):
sl = sorted(l)
return all(x2-x1 >= 10 for x1,x2 in zip(sl, sl[1:]))
For adjusting the list, you can also use pairwise, but as you are changing the list inflight, you need to use the indexes to do any changes, enumerate
gives you those:
def adjust(l):
for (idx1,num1), (idx2,num2) in itertools.pairwise(sorted(enumerate(l), key=lambda tup: tup[1])):
if l[idx2] < l[idx1]+10: # Note: Don't be tempted to use num1 or num2 here
l[idx2] = l[idx1]+10
Upvotes: 3
Reputation: 42143
The accumulate
function from itertools can make this quite easy as it will progress through the list working the the last and current values:
from itertools import accumulate
def adjust(L,diff=10):
return [*accumulate(L,lambda a,b:max(b,a+diff))]
print(adjust([16, 29, 10])) # [16, 29, 39]
print(adjust([16, 29, 10, 35, 60, 67])) # [16, 29, 39, 49, 60, 70]
If you're not allowed to use libraries, you can obtain a similar result with the extend method:
def adjust(L,diff=10):
result = L[:1]
result.extend(max(v,result[-1]+diff) for v in L[1:])
return result
If you need to change the values "in-place" in the same list instance, enumerate()
can help with traversal and indexing:
for i,v in enumerate(y_list[1:]):
y_list[i+1] = max(v,y_list[i]+10)
Note that I didn't bother with condition #1 since it will never happen when condition #2 is met. Also, this approach assumes that you're not trying to minimize the number of changes
[EDIT] Here's an example of a process that anchors on the smallest elements working forward and backward in a way that will keep that element the smallest (i.e. minimally adjusting others around it):
y_list = [16, 29, 16, 10, 35, 60, 67]
anchor = y_list.index(min(y_list)) # anchor on smallest
for i,v in enumerate(y_list[anchor+1:],anchor): # forward
y_list[i+1] = y_list[i]+10 if abs(v-y_list[i])<10 else v
for i,v in enumerate(reversed(y_list[:anchor]),len(y_list)-anchor): # backward
y_list[-i-1] = y_list[-i]+10 if abs(v-y_list[-i])<10 else v
print(y_list)
# [16, 30, 20, 10, 35, 60, 70]
Upvotes: 1
Reputation: 13087
If you don't care about preserving order, then I would do this:
y_list = [16, 29, 10, 50]
y_list.sort()
for i in range(1, len(y_list)):
y_list[i] += max(0, 10 - (y_list[i] - y_list[i-1]))
print(y_list)
giving you:
[10, 20, 30, 50]
If you do care about sorting then the answer by @andrej-kesely looks like a winner to me.
Upvotes: 1
Reputation: 195438
Another take:
You can sort the input array (remembering the original indices), make sure the values have the difference at least 10 and put the values back in original order:
y_list = [16, 29, 10]
x = sorted((v, i) for i, v in enumerate(y_list))
out = [x[0]]
for v, i in x[1:]:
how_much = v - out[-1][0]
if how_much < 10:
v += 10 - how_much
out.append((v, i))
out = [v for v, _ in sorted(out, key=lambda v: v[1])]
print(out)
Prints:
[20, 30, 10]
Upvotes: 1
Reputation: 3609
Create a function that evaluates whether your list satisfies the condition and then use a while loop that runs until that function returns True. The trick then is to have more code-compact ways to check for uniqueness and do all comparisons. For uniqueness, we can just compare the set vs. the list and if the lengths match, all elements are unique. For comparisons, we use itertools.combinations
to generate all index pairs. If we run into any failure condition, we immediately return False. If it survives the gauntlet of checks, we finally return True.
from itertools import combinations
y_list = [16, 29, 10]
def satisfies_conditions(ys):
unique = len(set(ys)) == len(ys)
if not unique:
return False
for i, j in combinations(range(len(ys)), 2):
diff = abs(ys[i] - ys[j])
if diff < 10:
return False
return True
while not satisfies_conditions(y_list):
for i in range(len(y_list)):
print(f'Iteration {i}')
print(f'Old y_list: {y_list}')
for idx, value in enumerate(y_list):
difference = abs(value - y_list[i])
if value != y_list[i]:
print(f'Comparing {y_list[idx]} with {y_list[i]}')
print(f'Difference of {difference}')
if difference < 10:
print(f'Updating {y_list[idx]}\n')
y_list[idx] += 10 - difference
else:
continue
print()
print(f'New list{y_list}\n')
Upvotes: 0