Related
I have a list that looks something like this:
lst_A = [32,12,32,55,12,90,32,75]
I want to replace the numbers with their rank. I am using this function to do this:
def obtain_rank(lstC):
sort_data = [(x,i) for i,x in enumerate(lstC)]
sort_data = sorted(sort_data,reverse=True)
result = [0]*len(lstC)
for i,(_,idx) in enumerate(sort_data,1):
result[idx] = i
return result
I am getting the following output while I use this:
[6, 8, 5, 3, 7, 1, 4, 2]
But what I want from this is:
[4, 7, 5, 3, 8, 1, 6, 2]
How can I go about this?
Try this:
import pandas as pd
def obtain_rank(a):
s = pd.Series(a)
return [int(x) for x in s.rank(method='first', ascending=False)]
#[4, 7, 5, 3, 8, 1, 6, 2]
You could use 2 loops:
l = [32,12,32,55,12,90,32,75]
d = list(enumerate(sorted(l, reverse = True), start = 1))
res = []
for i in range(len(l)):
for j in range(len(d)):
if d[j][1] == l[i]:
res.append(d[j][0])
del d[j]
break
print(res)
#[4, 7, 5, 3, 8, 1, 6, 2]
Here you go. In case, you are not already aware, please read https://docs.python.org/3.7/library/collections.html to understand defaultdict and deque
from collections import defaultdict, deque
def obtain_rank(listC):
sorted_list = sorted(listC, reverse=True)
d = defaultdict(deque) # deque are efficient at appending/popping elements at both sides of the sequence.
for i, ele in enumerate(sorted_list):
d[ele].append(i+1)
result = []
for ele in listC:
result.append(d[ele].popleft()) # repeating numbers with lower rank will be the start of the list, therefore popleft
return result
Update: Without using defaultdict and deque
def obtain_rank(listC):
sorted_list = sorted(listC, reverse=True)
d = {}
for i, ele in enumerate(sorted_list):
d[ele] = d.get(ele, []) + [i + 1] # As suggested by Joshua Nixon
result = []
for ele in listC:
result.append(d[ele][0])
del d[ele][0]
return result
I have three forms that looks like that:
class RoomsForm(forms.Form):
rooms = forms.IntegerField(min_value=1)
class PeopleForm(forms.Form):
adult = forms.IntegerField(min_value=1)
children = forms.IntegerField(required=False)
class ChildrenAgeForm(forms.Form):
children_age = forms.IntegerField(max_value=10, required=False)
Quantity of PeopleForm depend on value rooms field of RoomsForm and quantity of ChildrenAgeForm depends on values children field of each PeopleForm. So i create formsets for PeopleForm and ChildrenAgeForm, and multiply it using js. Finally i need to create string that looks like this if the value of rooms, of example, is 3:
'<Room Adult=2 Children=2>
<ChildAge>2</ChildAge>
<ChildAge>1</ChildAge>
</Room>
<Room Adult=1 Children=0>
</Room>
<Room Adult=1 Children=1>
<ChildAge>3</ChildAge>
</Room>'
According to this i create loop script in the views.py file:
PeopleFormSet = formset_factory(PeopleForm, extra = 1, max_num = 15)
ChildrenAgeFormSet = formset_factory(ChildrenAgeForm, extra = 1, max_num = 20)
rooms_form = RoomsForm(request.POST, prefix='rooms_form')
people_formset = PeopleFormSet(request.POST, prefix='people')
childrenage_formset = ChildrenAgeFormSet(request.POST, prefix='childrenage')
if room_form.is_valid() and people_formset.is_valid() and childrenage_formset.is_valid():
people = ''
childrenage_str = []
for i in range(0, childrenage_formset.total_form_count()):
childrenage_form = childrenage_formset.forms[i]
childrenage = str(childrenage_form.cleaned_data['children_age'])
childrenage_str += childrenage
for n in range(0, people_formset.total_form_count()):
childrenage_lst = childrenage_str
people_form = people_formset.forms[n]
adults = str(people_form.cleaned_data['adult'])
children = people_form.cleaned_data['children']
for i in range(0, children):
childage_str = ''
childage = childrenage_lst.pop(i)
childage_str += '<ChildAge>%s</ChildrenAge>' % childage
people += '<Room Adults="%s">%s</Room>' % (adults, childage_str)
But i got error pop index out of range. Hope you can help me to edit my script in the right way.
By using pop you're removing elements from the list:
>>> mylist = [0,1,2,3,4,5,6,7,8,9]
>>> for i in range(0, len(mylist)):
... print(mylist)
... print(mylist.pop(i))
...
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0
[1, 2, 3, 4, 5, 6, 7, 8, 9]
2
[1, 3, 4, 5, 6, 7, 8, 9]
4
[1, 3, 5, 6, 7, 8, 9]
6
[1, 3, 5, 7, 8, 9]
8
[1, 3, 5, 7, 9]
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: pop index out of range
So children, which you're using the length of, is constant, but childrenage_lst is constantly getting shorter and shorter. If you're confident that the two will always start out being the same length, then just access elements in childrenage_lst using []:
for i in range(0, children):
print(childrenage_lst[i])
That said, because of its initialisation, childrenage_str = '' and then childrenage_lst = childrenage_str, it looks like childrenage_lst is a string, which doesn't have a pop method, so I think there's something missing from the code you've posted, to get the TraceBack you're getting.
My goal is to iterate over the pairs [a,b] a coprime to b and a+b<=n. For example, if n=8, I want to iterate over [1, 2], [2, 3], [3, 4], [3, 5], [1, 3], [2, 5], [1, 4], [1, 5], [1, 6], [1, 7].
My first thought was a recursive function using the Stern-Brocot tree:
def Stern_Brocot(n,a=0,b=1,c=1,d=1):
if(a+b+c+d>n):
return 0
x=Stern_Brocot(n,a+c,b+d,c,d)
y=Stern_Brocot(n,a,b,a+c,b+d)
if(x==0):
if(y==0):
return [a+c,b+d]
else:
return [a+c]+[b+d]+y
else:
if(y==0):
return [a+c]+[b+d]+x
else:
return [a+c]+[b+d]+x+y
As expected,
>>> Stern_Brocot(8)
[1, 2, 2, 3, 3, 4, 3, 5, 1, 3, 2, 5, 1, 4, 1, 5, 1, 6, 1, 7]
And for n<=995, it works well. But suddenly at n>=996, it gives this error:
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
a=Stern_Brocot(996)
File "C:\Users\Pim\Documents\C Programmeren en Numerieke Wisk\Python\PE\PE127.py", line 35, in Stern_Brocot
y=Stern_Brocot(n,a,b,a+c,b+d)
...
File "C:\Users\Pim\Documents\C Programmeren en Numerieke Wisk\Python\PE\PE127.py", line 35, in Stern_Brocot
y=Stern_Brocot(n,a,b,a+c,b+d)
RuntimeError: maximum recursion depth exceeded in comparison
And since I want n to equal 120000, this approach won't work.
So my question is: what would be a good approach to iterate over parts of the Stern_Brocot tree? (if there's another way to iterate over coprime integers, that'd be good as well).
Here's an non-recursive implementation
def Stern_Brocot(n):
states = [(0, 1, 1, 1)]
result = []
while len(states) != 0:
a, b, c, d = states.pop()
if a + b + c + d <= n:
result.append((a+c, b+d))
states.append((a, b, a+c, b+d))
states.append((a+c, b+d, c, d))
return result
Before defining Stern_Brocot, add sys.setrecursionlimit(120000). This will set the program's recursion limit to 120000.
So, instead, you can do this:
import sys
sys.setrecursionlimit(120000)
def Stern_Brocot(n,a=0,b=1,c=1,d=1):
if(a+b+c+d>n):
return 0
x=Stern_Brocot(n,a+c,b+d,c,d)
y=Stern_Brocot(n,a,b,a+c,b+d)
if(x==0):
if(y==0):
return [a+c,b+d]
else:
return [a+c]+[b+d]+y
else:
if(y==0):
return [a+c]+[b+d]+x
else:
return [a+c]+[b+d]+x+y
I was faced with the problem of executing n number of concurrent events that all return iterators to the results they aquired. However, there was an optional limit parameter that says, basically, to consolidate all the iterators and return up-to limit results.
So, for example: I execute 2,000 url requests on 8 threads but just want the first 100 results, but not all 100 from the same potential thread.
Thus, unravel:
import itertools
def unravel(*iterables, with_limit = None):
make_iter = {a:iter(i) for a,i in enumerate(iterables)}
if not isinstance(with_limit, int):
with_limit = -1
resize = False
while True:
for iid, take_from in make_iter.items():
if with_limit == 0:
raise StopIteration
try:
yield next(take_from)
except StopIteration:
resize = iid
else:
with_limit -= 1
if resize:
resize = False
if len(make_iter.keys()) > 1:
make_iter.pop(resize)
else: raise StopIteration
Usage:
>>> a = [1,2,3,4,5]
>>> b = [6,7,8,9,10]
>>> c = [1,3,5,7]
>>> d = [2,4,6,8]
>>>
>>> print([e for e in unravel(c, d)])
[1, 2, 3, 4, 5, 6, 7, 8]
>>> print([e for e in unravel(c, d, with_limit = 3)])
[1, 2, 3]
>>> print([e for e in unravel(a, b, with_limit = 6)])
[1, 6, 2, 7, 3, 8]
>>> print([e for e in unravel(a, b, with_limit = 100)])
[1, 6, 2, 7, 3, 8, 4, 9, 5, 10]
Does something like this already exist, or is this a decent implementation?
Thanks
EDIT, WORKING FIX
Inspired by #abernert 's suggestion, this is what I went with. Thanks everybody!
def unravel(*iterables, limit = None):
yield from itertools.islice(
filter(None,
itertools.chain.from_iterable(
itertools.zip_longest(
*iterables
)
)
), limit)
>>> a = [x for x in range(10)]
>>> b = [x for x in range(5)]
>>> c = [x for x in range(0, 20, 2)]
>>> d = [x for x in range(1, 30, 2)]
>>>
>>> print(list(unravel(a, b)))
[1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9]
>>> print(list(unravel(a, b, limit = 3)))
[1, 1, 2]
>>> print(list(unravel(a, b, c, d, limit = 20)))
[1, 1, 1, 2, 3, 2, 2, 4, 5, 3, 3, 6, 7, 4, 4, 8, 9, 5, 10, 11]
What you're doing here is almost just zip.
You want a flat iterable, rather than an iterable of sub-iterables, but chain fixes that.
And you want to take only the first N values, but islice fixes that.
So, if the lengths are all equal:
>>> list(chain.from_iterable(zip(a, b)))
[1, 6, 2, 7, 3, 8, 4, 9, 5, 10]
>>> list(islice(chain.from_iterable(zip(a, b)), 7))
[1, 6, 2, 7, 3, 8, 4]
But if the lengths aren't equal, that will stop as soon as the first iterable finishes, which you don't want. And the only alternative in the stdlib is zip_longest, which fills in missing values with None.
You can pretty easily write a zip_longest_skipping (which is effectively the round_robin in Peter's answer), but you can also just zip_longest and filter out the results:
>>> list(filter(None, chain.from_iterable(zip_longest(a, b, c, d))))
[1, 6, 1, 2, 2, 7, 3, 4, 3, 8, 5, 6, 4, 9, 7, 8, 5, 10]
(Obviously this doesn't work as well if your values are all either strings or None, but when they're all positive integers it works fine… to handle the "or None" case, do sentinel=object(), pass that to zip_longest, then filter on x is not sentinel.)
From the itertools example recipes:
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
pending = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while pending:
try:
for next in nexts:
yield next()
except StopIteration:
pending -= 1
nexts = cycle(islice(nexts, pending))
Use itertools.islice to enforce your with_limit, eg:
print([e for e in itertools.islice(roundrobin(c, d), 3)])
>>> list(roundrobin(a, b, c, d))
[1, 6, 1, 2, 2, 7, 3, 4, 3, 8, 5, 6, 4, 9, 7, 8, 5, 10]
For what you're actually trying to do, there's probably a much better solution.
I execute 2,000 url requests on 8 threads but just want the first 100 results, but not all 100 from the same potential thread.
OK, so why are the results in 8 separate iterables? There's no good reason for that. Instead of giving each thread its own queue (or global list and lock, or whatever you're using) and then trying to zip them together, why not have them all share a queue in the first place?
In fact, that's the default way that almost any thread pool is designed (including multiprocessing.Pool and concurrent.futures.Executor in the stdlib). Look at the main example for concurrent.futures.ThreadPoolExecutor:
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
That's almost exactly your use case—spamming a bunch of URL downloads out over 5 different threads and gathering the results as they come in—without your problem even arising.
Of course it's missing with_limit, but you can just wrap that as_completed iterable in islice to handle that, and you're done.
This uses a generator and izip_longest to pull one item at a time from multiple iterators
from itertools import izip_longest
def unravel(cap, *iters):
counter = 0
for slice in izip_longest(*iters):
for entry in [s for s in slice if s is not None]:
yield entry
counter += 1
if counter >= cap: break
r = range(10)
for j in range(maxj):
# get ith number from r...
i = randint(1,m)
n = r[i]
# remove it from r...
r[i:i+1] = []
The traceback I am getting a strange error:
r[i:i+1] = []
TypeError: 'range' object does not support item assignment
Not sure why it is throwing this exception, did they change something in Python 3.2?
Good guess: they did change something. Range used to return a list, and now it returns an iterable range object, very much like the old xrange.
>>> range(10)
range(0, 10)
You can get an individual element but not assign to it, because it's not a list:
>>> range(10)[5]
5
>>> r = range(10)
>>> r[:3] = []
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
r[:3] = []
TypeError: 'range' object does not support item assignment
You can simply call list on the range object to get what you're used to:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> r = list(range(10))
>>> r[:3] = [2,3,4]
>>> r
[2, 3, 4, 3, 4, 5, 6, 7, 8, 9]
Try this for a fix (I'm not an expert on python 3.0 - just speculating at this point)
r = [i for i in range(maxj)]