Related
I have a Python list consisting of data:
[60, 120, 180, 240, 480]
I need to calculate the intervals between the elements in the list and get the value in between the elements. For the above list, I'm looking for this output:
[[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
The first and last values of the list is directly transferred, but the values in-between are obtained by the following method: e.g. for 60 and 120: (120 - 60 = 60 / 2 = 30 + 60 = 90)
I cannot work out how to do this in a simple pythonic fashion, and I have buried myself in if/else statements to solve it.
You can do this fairly simply with pairwise. It's included in Python as of version 3.10, but if you're on an earlier version, you can get it from more_itertools or implement it yourself. (I also use mean, which is a handy convenience even though it's trivial to reimplement.)
from itertools import pairwise
from statistics import mean
original = [60, 120, 180, 240, 480]
midpoints = [mean(pair) for pair in pairwise(original)]
output = list(pairwise([original[0], *midpoints, original[-1]]))
print(output)
[(60, 90), (90, 150), (150, 210), (210, 360), (360, 480)]
Note that this outputs each pair as a tuple, rather than a list, as in your sample output. I think this is more idiomatic, and I would prefer it in my own code. However, if you'd prefer lists, it's a simple change:
from itertools import pairwise
from statistics import mean
original = [60, 120, 180, 240, 480]
midpoints = [mean(pair) for pair in pairwise(original)]
output = [
list(pair) for pair in pairwise([original[0], *midpoints, original[-1]])
]
print(output)
[[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
We can replace inner points by midpoints and then turn adjacent pairs into intervals:
a[1:-1] = map(mean, pairwise(a))
a[:] = pairwise(a)
As non-modifying function (Try it online!):
from itertools import pairwise
from statistics import mean
def intervals(a):
a = a[:]
a[1:-1] = map(mean, pairwise(a))
return list(pairwise(a))
print(intervals([60, 120, 180, 240, 480]))
Output:
[(60, 90), (90, 150), (150, 210), (210, 360), (360, 480)]
The intervals are tuples instead of lists, but like CrazyChucky I think that tuples are more idiomatic for this (unless you actually have a need for them to be lists).
The shortest version that i can do
A = [60, 120, 180, 240, 480]
B = []
for i in range(len(A)):
if i == 0:
B.append([A[i], (A[i] + A[i+1])//2])
else:
if(i < len(A)-1):
B.append([B[i - 1][1], (A[i] + A[i+1])//2])
else:
B.append([B[i - 1][1], A[i]])
print(B)
x_list = [60, 120, 180, 240, 480]
y = []
for i in range(len(x_list) - 1):
y.append(int((x_list[i]+x_list[i+1])/2))
print(y)
z = [[x_list[0], y[0]]]
for i in range(0, len(y)-1):
z.append([y[i], y[i+1]])
print(z)
z.append([y[-1], x_list[-1]])
print(z)
Try the above.
The first line printed are the midpoints, and the last row shows the final list.
It's a matter of understanding indices. There are probably faster ways to do it, but learning, it's better to iterate.
$ python example.py
[90, 150, 210, 360]
[[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
$
You can use the following:
def getIntervals(l):
l2 = []
l2.append(l[0])
for i in range(0, len(l) - 1):
l2.append((l[i] + l[i+1]) // 2)
l2.append(l[-1])
ret = []
for i in range(len(l2) - 1):
ret.append([l2[i], l2[i+1]])
return ret
And then you can call it like this:
x = [60, 120, 180, 240, 480]
y = getIntervals(x)
print(y) # prints [[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
Basically, you iterate through the list, and then you find the endpoints of each interval, and then you iterate through again and form the pairs of endpoints.
Most pythonic way I can come up with:
from itertools import pairwise # python ≥3.10 only
l = [60, 120, 180, 240, 480]
mid_points = map(lambda pair: sum(pair)//2, pairwise(l)) # [90, 150, 210, 360]
all_values = l[0], *mid_points, l[-1] # pre/append 60 and 480 resp. to mid_points
# all_values: (60, 90, 150, 210, 360, 480)
list(pairwise(all_values))
# [(60, 90), (90, 150), (150, 210), (210, 360), (360, 480)]
If you don't have Python 3.10 you can emulate pairwise with:
from itertools import tee
def pairwise(iterable):
# pairwise('ABCDEFG') --> AB BC CD DE EF FG
a, b = tee(iterable)
next(b, None)
return zip(a, b)
Solution written in function format (without needing to import anything):
Function:
def get_interval_list(my_list):
interval_start = 0
interval_end = 0
interval_list = []
for i in range(len(my_list)):
if i == 0:
interval_start = my_list[0]
else:
interval_start = interval_end
if i == len(my_list)-1:
interval_end = my_list[-1]
else:
current_num = my_list[i]
next_num = my_list[i+1]
interval_end = (current_num + next_num) / 2
interval_list.append([int(interval_start), int(interval_end)])
return interval_list
Function with commented explanations:
def get_interval_list(my_list):
# variables (self explanatory... interval_list temporarily stores the interval start and
# end pair for each number being iterated through)
interval_start = 0
interval_end = 0
interval_list = []
# for loop iterates through the conditional statements for a number of times dependent on the
# length of my_list, finds interval_start and interval_end for each number in my_list,
# and then add each pair to interval_list
for i in range(len(my_list)):
# conditional statement checks if the current number is the first item in my_list,
# and if it is, assigns it to interval_start - if it isn't, the interval_end for the
# previous number will be the same as the interval_start for the
# current number (as per your requested output)
if i == 0:
interval_start = my_list[0]
else:
interval_start = interval_end
# conditional statement checks if the current number is the last item in my_list,
# and if it is, assigns it to interval_end - if it isn't, we calculate interval_end by
# adding the current number and the next number in my_list and dividing by 2
if i == len(my_list)-1:
interval_end = my_list[-1]
else:
current_num = my_list[i]
next_num = my_list[i+1]
interval_end = (current_num + next_num) / 2
# values of interval_start and interval_end are added as a pair to interval_list
# (use int() here if you do not want your interval pairs to be returned as floats)
interval_list.append([int(interval_start), int(interval_end)])
return interval_list
Sample run using function above:
list_of_numbers = [60, 120, 180, 240, 480]
print(get_interval_list(list_of_numbers))
Output:
[[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
(Could be written more simply but I didn't want to sacrifice readability)
Just a side note: we don't need to use (120 - 60 = 60 / 2 = 30 + 60 = 90) to calculate the midpoints. There is a much simpler way; all we have to do is add the upper and lower limits and divide by 2 like so:
(60 + 120) / 2 = 90
(This mathematical method of finding midpoints works for any "range")
This is the best i can come up with, but i'm really unsure if this is the simplest approach:
def generate_split_intervals(input_list):
first_list_value = input_list[0]
last_list_value = input_list[-1]
return_list = []
last_append = 0
for idx, item in enumerate(input_list):
if item == first_list_value:
last_append = int(first_list_value + (abs(input_list[idx+1]-first_list_value)/2))
return_list.append([first_list_value, last_append ])
elif item == last_list_value:
return_list.append([last_append, last_list_value])
else:
this_append = int(item + (abs(input_list[idx+1]-item)/2))
return_list.append([last_append, this_append])
last_append = this_append
return return_list
arr = [60, 120, 180, 240, 480]
def foo(x, i):
return [(x[i] + x[max(0, i - 1)])//2,
(x[i] + x[min(len(x) - 1, i + 1)])//2]
[foo(arr, i) for i in range(len(arr))]
# [[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
OR
lz = zip((zip(arr, [arr[0]] + arr[:-1])),
(zip(arr, arr[1:] + [arr[-1]])))
[[sum(a)//2, sum(b)//2] for a, b in lz]
# [[60, 90], [90, 150], [150, 210], [210, 360], [360, 480]]
I'm stuck again on trying to make this merge sort work.
Currently, I have a 2d array with a Unix timecode(fig 1) and merge sorting using (fig 2) I am trying to check the first value in each array i.e array[x][0] and then move the whole array depending on array[x][0] value, however, the merge sort creates duplicates of data and deletes other data (fig 3) my question is what am I doing wrong? I know it's the merge sort but cant see the fix.
fig 1
[[1422403200 100]
[1462834800 150]
[1458000000 25]
[1540681200 150]
[1498863600 300]
[1540771200 100]
[1540771200 100]
[1540771200 100]
[1540771200 100]
[1540771200 100]]
fig 2
import numpy as np
def sort(data):
if len(data) > 1:
Mid = len(data) // 2
l = data[:Mid]
r = data[Mid:]
sort(l)
sort(r)
z = 0
x = 0
c = 0
while z < len(l) and x < len(r):
if l[z][0] < r[x][0]:
data[c] = l[z]
z += 1
else:
data[c] = r[x]
x += 1
c += 1
while z < len(l):
data[c] = l[z]
z += 1
c += 1
while x < len(r):
data[c] = r[x]
x += 1
c += 1
print(data, 'done')
unixdate = [1422403200, 1462834800, 1458000000, 1540681200, 1498863600, 1540771200, 1540771200,1540771200, 1540771200, 1540771200]
price=[100, 150, 25, 150, 300, 100, 100, 100, 100, 100]
array = np.column_stack((unixdate, price))
sort(array)
print(array, 'sorted')
fig 3
[[1422403200 100]
[1458000000 25]
[1458000000 25]
[1498863600 300]
[1498863600 300]
[1540771200 100]
[1540771200 100]
[1540771200 100]
[1540771200 100]
[1540771200 100]]
I couldn't spot any mistake in your code.
I have tried your code and I can tell that the problem does not happen, at least with regular Python lists: The function doesn't change the number of occurrence of any element in the list.
data = [
[1422403200, 100],
[1462834800, 150],
[1458000000, 25],
[1540681200, 150],
[1498863600, 300],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100],
]
sort(data)
from pprint import pprint
pprint(data)
Output:
[[1422403200, 100],
[1458000000, 25],
[1462834800, 150],
[1498863600, 300],
[1540681200, 150],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100],
[1540771200, 100]]
Edit, taking into account the numpy context and the use of np.column_stack.
-I expect what happens there is that np.column_stack actually creates a view mapping over the two arrays. To get a real array rather than a link to your existing arrays, you should copy that array:-
array = np.column_stack((unixdate, price)).copy()
Edit 2, taking into account the numpy context
This behavior has actually nothing to do with np.column_stack; np.column_stack already performs a copy.
The reason your code doesn't work is because slicing behaves differently with numpy than with python. Slicing create a view of the array which maps indexes.
The erroneous lines are:
l = data[:Mid]
r = data[Mid:]
Since l and r just map to two pieces of the memory held by data, they are modified when data is. This is why the lines data[c] = l[z] and data[c] = r[x] overwrite values and create copies when moving values.
If data is a numpy array, we want l and r to be copies of data, not just views. This can be achieved using the copy method.
l = data[:Mid]
r = data[Mid:]
if isinstance(data, np.ndarray):
l = l.copy()
r = r.copy()
This way, I tested, the copy works.
Note
If you wanted to sort the data using python lists rather than numpy arrays, the equivalent of np.column_stack in vanilla python is zip:
z = zip([10, 20, 30, 40], [100, 200, 300, 400], [1000, 2000, 3000, 4000])
z
# <zip at 0x7f6ef80ce8c8>
# `zip` creates an iterator, which is ready to give us our entries.
# Iterators can only be walked once, which is not the case of lists.
list(z)
# [(10, 100, 1000), (20, 200, 2000), (30, 300, 3000), (40, 400, 4000)]
The entries are (non-mutable) tuples. If you need the entries to be editable, map list on them:
z = zip([10, 20, 30, 40], [100, 200, 300, 400], [1000, 2000, 3000, 4000])
li = list(map(list, z))
# [[10, 100, 1000], [20, 200, 2000], [30, 300, 3000], [40, 400, 4000]]
To transpose a matrix, use zip(*matrix):
def transpose(matrix):
return list(map(list, zip(*matrix)))
transpose(l)
# [[10, 20, 30, 40], [100, 200, 300, 400], [1000, 2000, 3000, 4000]]
You can also sort a python list li using li.sort(), or sort any iterator (lists are iterators), using sorted(li).
Here, I would use (tested):
sorted(zip(unixdate, price))
What is the best way to do this? Looking to take the difference but not like this horrible way. For each A, B, C it is subtracted from subtract from
A = [500, 500, 500, 500, 5000]
B = [100, 100, 540, 550, 1200]
C = [540, 300, 300, 100, 10]
triples= [tuple(A),tuple(B), tuple(C)]
subtract_from = tuple([1234,4321,1234,4321,5555])
diff = []
for main in subtract_from:
for i in range(len(triples)):
for t in triples[i]:
diff[i].append(main-t)
Try something like this:
all_lists = [A, B, C]
[[i-j for i,j in zip(subtract_from,l)] for l in all_lists]
[
[734, 3821, 734, 3821, 555],
[1134, 4221, 694, 3771, 4355],
[694, 4021, 934, 4221, 5545]
]
It is the best practice of doing this. no need to import any library, just use builtins.
You could try using map and operator:
import operator
A = [500, 500, 500, 500, 5000]
B = [100, 100, 540, 550, 1200]
C = [540, 300, 300, 100, 10]
l = [A, B, C]
subtract_from = [1234,4321,1234,4321,5555]
diff = list((list(map(operator.sub, subtract_from , i)) for i in l))
print(diff)
# [[734, 3821, 734, 3821, 555], [1134, 4221, 694, 3771, 4355], [694, 4021, 934, 4221, 5545]]
First of all, if you want tuples, use tuples explicitly without converting lists. That being said, you should write something like this:
a = 500, 500, 500, 500, 5000
b = 100, 100, 540, 550, 1200
c = 540, 300, 300, 100, 10
vectors = a, b, c
data = 1234, 4321, 1234, 4321, 5555
diff = [
[de - ve for de, ve in zip(data, vec)]
for vec in vectors
]
If you want list of tuples, use tuple(de - ve for de, ve in zip(data, vec)) instead of [de - ve for de, ve in zip(data, vec)].
I think everyone else nails it with list comprehensions already so here's a few odd ones in cases if you are using a mutable lists and reusing it in an imperative style is acceptable style, then the following code can be done
A = [500, 500, 500, 500, 5000]
B = [100, 100, 540, 550, 1200]
C = [540, 300, 300, 100, 10]
subtract_from = (1234,4321,1234,4321,5555)
for i,x in enumerate(subtract_from):
A[i], B[i], C[i] = x-A[i], x-B[i], x-C[i]
# also with map
#for i,x in enumerate(zip(subtract_from,A,B,C)):
# A[i], B[i], C[i] = map(x[0].__sub__, x[1:])
diff = [A,B,C]
It's less elegant but more efficient*(...I have not done any benchmark for this claim)
So I have this bit of code:
Points = [ [400,100],[600,100],[800,100] , [300,300],[400,300],[500,300],[600,300] , [200,500],[400,500],[600,500],[800,500],[1000,500] , [300,700],[500,700][700,700][900,700] , [200,900],[400,900],[600,900] ]
And it produces this Error:
line 43, in <module>
Points = [ [400,100],[600,100],[800,100] , [300,300],[400,300],[500,300],[600,300] , [200,500],[400,500],[600,500],[800,500],[1000,500] , [300,700],[500,700][700,700][900,700] , [200,900],[400,900],[600,900] ]
TypeError: list indices must be integers, not tuple
What can I do to fix it?
You forgot two commas:
[500,700][700,700][900,700]
Now Python sees an attempt to index the list on the left-hand side with a (700, 700) tuple:
>>> [500,700][700,700]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not tuple
The second [900, 700] 'list' would give you the same problem but doesn't yet come into play.
Fix it by adding commas between:
[500, 700], [700, 700], [900, 700]
or, as a complete list:
Points = [[400, 100], [600, 100], [800, 100], [300, 300], [400, 300], [500, 300], [600, 300], [200, 500], [400, 500], [600, 500], [800, 500], [1000, 500], [300, 700], [500, 700], [700, 700], [900, 700], [200, 900], [400, 900], [600, 900]]
You forgot to seperate a few by commas. See the fix.
>>> Points = [[400,100], [600,100], [800,100], [300,300], [400,300], [500,300], [600,300] ,[200,500], [400,500], [600,500], [800,500], [1000,500], [300,700], [500,700], [700,700],[900,700], [200,900], [400,900], [600,900]]
Forgetting the commas leads Python to believe that you're trying to access the first list with the second, which throws an error.
You need to separate each of the lists (in the outer list) with a ,:
Points = [ [400,100],[600,100],[800,100] , [300,300],[400,300],[500,300],[600,300] ,[200,500],[400,500],[600,500],[800,500],[1000,500] , [300,700],[500,700],[700,700],[900,700] , [200,900],[400,900],[600,900] ]
this is my code :
attackUp = [10, 15,10, 15,10, 15]
defenceUp = [10, 15,10, 15,10, 15]
magicUp = [10, 15,10, 15,10, 15]
attType = [1,1,1,1,1,1]
weightDown = [10, 15,10, 15,10, 15]
#装饰器数据
accAttackSword = [100, 100,100, 100,100, 100]
accAttackSaber = [100, 100,100, 100,100, 100]
accAttackAx = [100, 100,100, 100,100, 100]
accAttackHammer = [100, 100,100, 100,100, 100]
accAttackSpear = [100, 100,100, 100,100, 100]
accAttackFight = [100, 100,100, 100,100, 100]
accAttackBow = [100, 100,100, 100,100, 100]
accAttackMagicGun = [100, 100,100, 100,100, 100]
accAttackMagic = [100, 100,100, 100,100, 100]
mStrInstrument = [100, 100,100, 100,100, 100]
mStrCharms = [100, 100,100, 100,100, 100]
accDefencePhy = [100, 100,100, 100,100, 100]
accDefenceMag = [100, 100,100, 100,100, 100]
accWeight = [100, 90, 0, 0, 100, 90]
#战术书数据
bookTurn = [1,1]
bookAttackPhy = [100, 100]
bookAttackMag = [100, 100]
bookStrInstrument = [100, 100]
bookStrCharms = [100, 100]
bookDefencePhy = [100, 100]
bookDefenceMag = [100, 100]
bookWeight = [100, 100]
you can see that : Many variables has the same value , but i cant define them like this :
bookAttackPhy = bookAttackMag =bookStrInstrument=bookStrCharms=bookDefencePhy=[100, 100]
because all change if one of them changes.
Which is the best and easiest to define these variables?
Well, a step in the right direction would be to create a base list and then copy it using slice notation:
base = [100, 100, 100, 100]
value_a = base[:]
value_b = base[:]
and so on. This doesn't gain you much for the shorter lists, but it should be useful for the longer ones at least.
But I think more generally, a richer data structure would be better for something like this. Why not create a class? You could then use setattr to fill up class members in a fairly straightforward way.
class Weapons(object):
def __init__(self, base):
for weapon in ["saber", "sword", "axe"]:
setattr(self, weapon, base[:])
w = Weapons([100, 100, 100])
print w.__dict__
#output: {'sword': [100, 100, 100],
# 'saber': [100, 100, 100],
# 'axe': [100, 100, 100]}
w.axe[0] = 10
print w.axe # output: [10, 100, 100]
print w.sword # output: [100, 100, 100]
Define them all as empty arrays, then group the ones that need the same values into a list and iterate through that list, assigning the common values to each variable.
You could do something like:
defaultAttack = [100, 100,100, 100,100, 100]
accAttackSword = list(defaultAttack)
accAttackSaber = list(defaultAttack)
The list() constructor makes a copy of the list, so they will be able to change independently.
You can use list multiplication
accAttackSword = [100]*6
....
bookWeight = [100]*2
....
You might consider grouping all of the variables with similar prefixes either into dictionaries or nested lists (EDIT - or classes/objects). This could have benefits later for organization, and would allow you to iterate thru and set them all to the same initial values.
bookVars = ['AttackPhy', 'AttackMag', 'StrInstrument', 'StrCharms']
bookValues = dict()
for i in bookVars:
bookValues[i] = [100]*2
And to access...
bookValues
{'AttackMag': [100, 100], 'StrCharms': [100, 100], 'StrInstrument': [100, 100], 'AttackPhy': [100, 100]}
bookValues['AttackMag']
[100, 100]
EDIT - check out senderle's thing too. at a glance his seems a little better, but id definitely consider using one of our ideas - the point is to structure it a little more. whenever you have groups of variables with similar prefixed names, consider grouping them together in a more meaningful way. you are already doing so in your mind, so make the code follow!