Infinite cycle over a range starting at a particular number - python

Say I have a range:
r = range(1, 6)
Using this range, I want to cycle infinitely and yield the numbers as they come:
for i in cycle(r):
yield(i)
This would correctly produce values of:
1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, ...
However, I sometimes want to start the yielding from a specific value but continue on with the range as it's defined. That is, if I want to start at 3, the sequence would be:
3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, ...
Is there a way to do this with a combination of range and cycle (or some other way)?

Just dropwhile until you reach the first value you want to emit:
>>> from itertools import cycle, dropwhile
>>> iterable = dropwhile(lambda x: x < 3, cycle(range(1, 6)))
>>> for _ in range(10):
... print(next(iterable))
...
3
4
5
1
2
3
4
5
1
2
Per the docs (emphasis mine):
Make an iterator that drops elements from the iterable as long as the
predicate is true; afterwards, returns every element.
The predicate only takes effect until the first value for which it evaluates false-y.

Since cycle starts from the beginning of the iterator given to it, give it an iterator (a sequence in this case) that starts where you want:
r = tuple(range(3,6)) + tuple(range(1,3))
The 1 above is the lowest value to repeat, the 6 is one more than the highest value, and the 3 used twice is the starting value. It should be clear how to generalize this to other cases.

Another way to do this is to chain two ranges together and pass the chain to cycle.
from itertools import cycle, chain
def shift_cycle(lo, start, stop):
return cycle(chain(range(start, stop), range(lo, start)))
for t in zip(range(12), shift_cycle(1, 3, 6)):
print('{}: {}'.format(*t))
output
0: 3
1: 4
2: 5
3: 1
4: 2
5: 3
6: 4
7: 5
8: 1
9: 2
10: 3
11: 4
This approach has an advantage over islice or dropwhile if the start and stop args are large because it doesn't need to discard the unwanted initial items.

perhaps unesthetic but practical? sys.maxsize being "practically infinite" for many purposes
import sys
r, n = 5, 3
cyc = (i%r + 1 for i in range(n, sys.maxsize))
next(cyc)
Out[106]: 4
next(cyc)
Out[107]: 5
next(cyc)
Out[108]: 1
next(cyc)
Out[109]: 2
next(cyc)
Out[110]: 3
next(cyc)
Out[111]: 4
next(cyc)
Out[112]: 5
next(cyc)
Out[113]: 1
sys.maxsize*1e-9/3600/24/365
Out[117]: 292.471208677536
that's years at 1 ns per request - on a 64 bit system
but of course it runs a bit slower
timeit.timeit('next(cyc)','r, n = 5, 3; cyc = (i%r + 1 for i in range(n, sys.maxsize))')
Out[126]: 0.2556792100261305
the modulo takes more time as requests keeps upping i
but that doesn't seem to be the big time sink
timeit.timeit('max%5', 'max=sys.maxsize')
Out[120]: 0.07545763840474251
timeit.timeit('1111%5')
Out[122]: 0.01156394737682831
timeit.timeit('111%5')
Out[123]: 0.011720469965638358

you are looking for the islice function
from itertools import islice, cycle
offset = 2
r = range(1, 6)
generator = islice(cycle(r), offset, None)

Related

Is there a better solution in Python for this problem? Thanks

Given an array of scores sorted in increasing order, return true if the array contains 3 adjacent scores that differ from each other by at most 2, such as with {3, 4, 5} or {3, 5, 5}.
Examples
scoresClump([3, 4, 5]) → true
scoresClump([3, 4, 6]) → false
scoresClump([1, 3, 5, 5]) → true
A solution below here seems to work but Translating it into Python looks tricky.
function scoresClump(scores) {
for (let i = 0; i < scores.length - 1; i++) {
if (scores[i + 2] - scores[i] <= 2) {
return true;
}
}
return false;
}
I have tried this in Python and it keeps going out of range or giving the wrong output.
arr=[1,3, 5, 5]
i=0
while i<(len(arr)-1):
if (arr[i+2]-arr[i])<=2 and (arr[i+1]-arr[i])<=2:
print("True")
i=i+1
else:
print("False")
break
You're iterating until i < len(arr)-1, but you're reaching for arr[i+2], which is out of range.
In Python you usually do not need to use an index variable. As you've seen they're a source of errors, and Python offers cleaner syntax without using them. For instance:
# unpythonic
for i in range(len(scores)):
print(scores[i])
# pythonic
for score in scores:
print(score)
zip combines two (or more) things you can iterate over.
scores[2:] means "the scores list, but discarding the first two values".
scores = [1, 2, 3, 4, 5, 6]
for a, b in zip(scores, scores[2:]):
print(a, b)
# 1 3
# 2 4
# 3 6
# zip then ends because it reached the end of scores[2:]
So, you can use zip and list slicing to compare the list with itself without index variables:
def scores_clump(scores):
for a, b in zip(scores, scores[2:]):
if b - a <= 2:
return True
return False
scores_clump([1, 3, 4, 5])
# 4 - 1 is not <= 2
# 5 - 3 is <= 2, return True
scores_clump([1, 3, 6, 7, 9])
# 6 - 1 is not <= 2
# 7 - 3 is not <= 2
# 9 - 6 is not <= 2
# reached end of loop with no match, return False
scores_clump([3, 3, 7, 7, 9])
# 7 - 3 is not <= 2
# 7 - 3 is not <= 2
# 9 - 7 is <= 2, return True
Taking a leaf from Max's answer, you can reduce it further using any:
def scores_clump(scores):
return any(b - a <= 2 for a, b in zip(scores, scores[2:]))
You can use this following function -
def scoresClump(nums):
for x in range(2, len(nums)):
if nums[x] - nums[x-2] <= 2:
return True
return False
if you want a one-liner using any() -
def scoresClump(nums):
return any(nums[x] - nums[x-2] <= 2 for x in range(2, len(nums)))
>>> scoresClump([3, 4, 5])
True
>>> scoresClump([3, 4, 6])
False
>>> scoresClump([3, 4, 5, 5])
True
>>> scoresClump([3, 4])
False
>>> scoresClump([3])
False
>>> scoresClump([3, 3, 7, 7, 9])
True
Your if statement will reach arr[:-1] and then try and check two elements ahead of that, which don't exist, therefore going out of range. Rather than a while loop I would try something like this:
for x in range(0, len(arr)-2)
This is basically a "sliding window" problem where window size=3 and we keep iterating over the array until we find a window where difference between adjacent elements is less than or equal to 2.
Here we can simply check if difference between last and first element of the window is <=2 or not because we are given a sorted array.
def scoresClump(array):
for i in range(2,len(array)):
if array[i]-array[i-2]<=2:
return True
return False
print(scoresClump([1, 3, 5, 5]))

Use a custom order in range function Python

I have been struggling with this quite a bit, and was wondering if there is a solution for this.
I would like to use the range(start,stop,step) function in Python, but I would like to use a different order than what the normal functionalities allow. From what I understand the range function can do 3 things:
0, 1, 2, 3 etc
10, 9, 8, 7 etc
2, 4, 6, 8 etc
Now I am looking for the following order:
0, 10, 1, 9, 2, 8, 3, 7 etc
In this case 10 in the len(df), so the last row of the df.
You can create a generator to do that:
def my_range(n):
"""Yields numbers from 0 to n, in order 0, n, 1, n-1..."""
low = 0
high = n
while low <= high:
yield low
if high != low:
yield high
low += 1
high -= 1
Some examples:
print(list(my_range(10)))
# [0, 10, 1, 9, 2, 8, 3, 7, 4, 6, 5]
for i in my_range(5):
print(i)
0
5
1
4
2
3
This will provide every number in the interval exactly once, and lazily, just as range.
To answer the question in your comment:
if you want to mix the numbers in your_list = [1,3,4,5,7,9,10,12,13,14], you can just use this function to generate the indices:
your_list = [1,3,4,5,7,9,10,12,13,14]
for index in my_range(len(your_list)-1):
print(your_list[index], end=' ')
# 1 14 3 13 4 12 5 10 7 9
or you could build a new list in the mixed order:
new = [your_list[index] for index in my_range(len(your_list)-1)]
print(new)
# [1, 14, 3, 13, 4, 12, 5, 10, 7, 9]
range just has start, stop, step params. But you can achieve what you want by zipping two ranges together along with the chain.from_iterable function:
from itertools import chain
for val in chain.from_iterable(zip(range(11), range(10, -1, -1))):
print(val)
# 0 10 1 9 2 8 3 7 4 6 5 5 6 4 7 3 8 2 9 1 10 0
Note this solution repeats values, if you want no repeated values, then a generator is the way to go.
To keep things simple and clear the range function cannot do this.
Since it only allows to either increment or decrement at once.
but this can be achieved with loops and variables.
this is how to do it
for i in range(0,11):
print(i,10-i,end=" ")
this code will do it.

Transforming an array of integers and computing the sum

Suppose we need to transform an array of integers and then compute the sum.
The transformation is the following:
For each integer in the array, subtract the first subsequent integer that is equal or less than its value.
For example, the array:
[6, 1, 3, 4, 6, 2]
becomes
[5, 1, 1, 2, 4, 2]
because
6 > 1 so 6 - 1 = 5
nothing <= to 1 so 1 remains 1
3 > 2 so 3 - 2 = 1
4 > 2 so 4 - 2 = 2
6 > 2 so 6 - 2 = 4
nothing <= to 2 so 2 remains 2
so we sum [5, 1, 1, 2, 4, 2] = 15
I already have the answer below but apparently there is a more optimal method. My answer runs in quadratic time complexity (nested for loop) and I can't figure out how to optimize it.
prices = [6, 1, 3, 4, 6, 2]
results = []
counter = 0
num_prices = len(prices)
for each_item in prices:
flag = True
counter += 1
for each_num in range(counter, num_prices):
if each_item >= prices[each_num] and flag == True:
cost = each_item - prices[each_num]
results.append(cost)
flag = False
if flag == True:
results.append(each_item)
print(sum(results))
Can someone figure out how to answer this question faster than quadratic time complexity? I'm pretty sure this can be done only using 1 for loop but I don't know the data structure to use.
EDIT:
I might be mistaken... I just realized I could have added a break statement after flag = False and that would have saved me from a few unnecessary iterations. I took this question on a quiz and half the test cases said there was a more optimal method. They could have been referring to the break statement so maybe there isn't a faster method than using nested for loop
You can use a stack (implemented using a Python list). The algorithm is linear since each element is compared at most twice (one time with the next element, one time with the next number smaller or equals to it).
def adjusted_total(prices):
stack = []
total_substract = i = 0
n = len(prices)
while i < n:
if not stack or stack[-1] < prices[i]:
stack.append(prices[i])
i += 1
else:
stack.pop()
total_substract += prices[i]
return sum(prices) - total_substract
print(adjusted_total([6, 1, 3, 4, 6, 2]))
Output:
15
a simple way to do it with lists, albeit still quadratic..
p = [6, 1, 3, 4, 6, 2]
out= []
for i,val in zip(range(len(p)),p):
try:
out.append(val - p[[x <= val for x in p[i+1:]].index(True)+(i+1)])
except:
out.append(val)
sum(out) # equals 15
NUMPY APPROACH - honestly don't have alot of programming background so I'm not sure if its linear or not (depending on how the conditional masking works in the background) but still interesting
p = np.array([6, 1, 3, 4, 6, 2])
out = np.array([])
for i,val in zip(range(len(p)),p):
pp = p[i+1:]
try:
new = val - pp[pp<=val][0]
out = np.append(out,new)
except:
out = np.append(out,p[i])
out.sum() #equals 15

Skip multiple iterations in a loop

Looking for something that would allow skipping multiple for loops while also having current index available.
In pseudo code, is would look something like this:
z = [1,2,3,4,5,6,7,8]
for element in z:
<calculations that need index>
skip(3 iterations) if element == 5
Is there such a thing in Python 2?
I'd iterate over iter(z), using islice to send unwanted elements into oblivion... ex;
from itertools import islice
z = iter([1, 2, 3, 4, 5, 6, 7, 8])
for el in z:
print(el)
if el == 4:
_ = list(islice(z, 3)) # Skip the next 3 iterations.
# 1
# 2
# 3
# 4
# 8
Optimization
If you're skipping maaaaaaany iterations, then at that point listifying the result will become memory inefficient. Try iteratively consuming z:
for el in z:
print(el)
if el == 4:
for _ in xrange(3): # Skip the next 3 iterations.
next(z)
Thanks to #Netwave for the suggestion.
If you want the index too, consider wrapping iter around an enumerate(z) call (for python2.7.... for python-3.x, the iter is not needed).
z = iter(enumerate([1, 2, 3, 4, 5, 6, 7, 8]))
for (idx, el) in z:
print(el)
if el == 4:
_ = list(islice(z, 3)) # Skip the next 3 iterations.
# 1
# 2
# 3
# 4
# 8
You can use a while loop for this purpose.
z = [1,2,3,4,5,6,7,8]
i = 0
while i < len(z):
# ... calculations that need index
if i == 5:
i += 3
continue
i += 1

Loop problem while iterating through a list and removing recurring elements [duplicate]

This question already has answers here:
Modifying list while iterating [duplicate]
(7 answers)
Closed 8 years ago.
I want to iterate through a list, and remove the items that count more than once, so they don't get printed repeatedly by the for loop.
However, some items appearing only one time in the list seem to get affected too by this, and I can't figure out why.
Any input would be greatly appreciated.
Example Output:
listy = [2,2,1,3,4,2,1,2,3,4,5]
for i in listy:
if listy.count(i)>1:
print i, listy.count(i)
while i in listy: listy.remove(i)
else:
print i, listy.count(i)
Outputs:
2 4
3 2
1 2
thus ignoring completely 4 and 5.
You should not modify a list while iterating over it. This one should work:
listy = [2,2,1,3,4,2,1,2,3,4,5]
found = set()
for i in listy:
if not i in found:
print i, listy.count(i)
found.add(i)
The result is:
2 4
1 2
3 2
4 2
5 1
The reason for your problems is that you modify the list while you are iterating over it.
If you don't care about the order in which items appear in the output and don't care about the count, you can simply use use a set:
>>> listy = [2,2,1,3,4,2,1,2,3,4,5]
>>> print set(listy)
set([1, 2, 3, 4, 5])
If you do care about the count, use the Counter class from the collections module in the Standard Library:
>>> import collections
>>> collections.Counter(listy)
Counter({2: 4, 1: 2, 3: 2, 4: 2, 5: 1})
>>> c = collections.Counter(listy)
>>> for item in c.iteritems():
... print "%i has a count of %i" % item
...
1 has a count of 2
2 has a count of 4
3 has a count of 2
4 has a count of 2
5 has a count of 1
If you do care about both the order and the count, you have to build a second list:
>>> checked = []
>>> counts = []
>>> for item in listy:
>>> if item not in checked:
>>> checked.append(item)
>>> counts.append(listy.count(item))
>>> print zip(checked, counts)
... [(2, 4), (1, 2), (3, 2), (4, 2), (5, 1)]
This is the least efficient solution, of course.
If you don't want to keep the counts for later, you don't need the counts list:
listy = [2,2,1,3,4,2,1,2,3,4,5]
checked = set()
for item in listy:
# "continue early" looks better when there is lots of code for
# handling the other case
if item in checked:
continue
checked.add(item)
print item, listy.count(item)
Don't modify a list while iterating over it, it will mess you up every time:
listy = [2,2,1,3,4,2,1,2,3,4,5]
# * * * Get hit
for i in listy:
print i
if listy.count(i) > 1:
print i, listy.count(i), 'item and occurences'
while i in listy: listy.remove(i)
else:
print i, listy.count(i)
First, you remove four 2s. Two are right at the beginning, so that puts you at the first 1.
Then you advance one when you get the next i from listy, putting you at the first 3.
Then you remove two 3s. The first is right there, so that puts you at the first 4.
Then you advance one again. The 2 is gone already, so this puts you at the second 1.
You then delete both 1s; this moves you forward two spaces. The 2 and 3 are gone, so this puts you at the 5.
You advance one, this moves you off the end of the list so the loop is over.
If what you want is to print each item only once, you can use the simple set method, or you could use the itertools unique_everseen recipe:
def unique_everseen(iterable, key=None):
"List unique elements, preserving order. Remember all elements ever seen."
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
# unique_everseen('ABBCcAD', str.lower) --> A B C D
seen = set()
seen_add = seen.add
if key is None:
for element in ifilterfalse(seen.__contains__, iterable):
seen_add(element)
yield element
else:
for element in iterable:
k = key(element)
if k not in seen:
seen_add(k)
yield element
Which extends the basic set version to allow you to specify a special way to compare items.
If you want to know which items are only in the list once:
listy2 = filter(lambda i: listy.count(i) == 1, listy)
listy2 now has all the single occurrences.
If you don't like the lambda, just do:
def getsingles(listy):
def singles(i):
return listy.count(i) == 1
return singles
then:
listy2 = filter(getsingles(listy), listy)
This makes a special function that will tell you which items are in listy only once.
The reason of the behavior you get is here, in the note:
http://docs.python.org/reference/compound_stmts.html#index-811
Update 1
agf's solution isn't a good one for performance reason: the list is filtered according to the count of each element. The counting is done for each element, that is to say the counting process that consists to run through the entire list to count, is done as many times as there are elements in list: it's overconsuming time, imagine if your list is 1000 length
A better solution I think is to use an instance of Counter:
import random
from collections import Counter
li = [ random.randint(0,20) for i in xrange(30)]
c = Counter(li)
print c
print type(c)
res = [ k for k in c if c[k]==1]
print res
result
Counter({8: 5, 0: 3, 4: 3, 9: 3, 2: 2, 5: 2, 11: 2, 3: 1, 6: 1, 10: 1, 12: 1, 15: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1})
<class 'collections.Counter'>
[3, 6, 10, 12, 15, 16, 17, 18, 19, 20]
Another solution would be to add the read elements in a set in order that the program avoids to make a count for an already seen element.
Update 2
errrr.... my solution is stupid, you don't want to select the element appearing only one time in the list....
Then the following code is the right one , I think:
import random
from collections import Counter
listy = [ random.randint(0,20) for i in xrange(30)]
print 'listy==',listy
print
c = Counter(listy)
print c
print type(c)
print
slimmed_listy = []
for el in listy:
if el in c:
slimmed_listy.append(el)
print 'element',el,' count ==',c[el]
del c[el]
print
print 'slimmed_listy==',slimmed_listy
result
listy== [13, 10, 1, 1, 13, 11, 18, 15, 3, 15, 12, 11, 15, 18, 11, 10, 14, 10, 20, 3, 18, 9, 11, 2, 19, 15, 5, 14, 1, 1]
Counter({1: 4, 11: 4, 15: 4, 10: 3, 18: 3, 3: 2, 13: 2, 14: 2, 2: 1, 5: 1, 9: 1, 12: 1, 19: 1, 20: 1})
<class 'collections.Counter'>
element 13 count == 2
element 10 count == 3
element 1 count == 4
element 11 count == 4
element 18 count == 3
element 15 count == 4
element 3 count == 2
element 12 count == 1
element 14 count == 2
element 20 count == 1
element 9 count == 1
element 2 count == 1
element 19 count == 1
element 5 count == 1
slimmed_listy== [13, 10, 1, 11, 18, 15, 3, 12, 14, 20, 9, 2, 19, 5]
In case you wouldn't want the result in the order of listy, the code would be even simpler
Update 3
If you want only to print, then I propose:
import random
from collections import Counter
listy = [ random.randint(0,20) for i in xrange(30)]
print 'listy==',listy
print
def gener(li):
c = Counter(li)
for el in li:
if el in c:
yield el,c[el]
del c[el]
print '\n'.join('element %4s count %4s' % x for x in gener(listy))
result
listy== [16, 2, 4, 9, 15, 19, 1, 1, 3, 5, 12, 15, 12, 3, 17, 13, 8, 11, 4, 6, 15, 1, 0, 1, 3, 3, 6, 5, 0, 8]
element 16 count 1
element 2 count 1
element 4 count 2
element 9 count 1
element 15 count 3
element 19 count 1
element 1 count 4
element 3 count 4
element 5 count 2
element 12 count 2
element 17 count 1
element 13 count 1
element 8 count 2
element 11 count 1
element 6 count 2
element 0 count 2
Modifying a list while you iterate over it is a bad idea in every language I have encountered. My suggestion: don't do that. Here are some better ideas.
Use a set to find single occurrences
source = [2,2,1,3,4,2,1,2,3,4,5]
for s in set(source):
print s
And you get this:
>>> source = [2,2,1,3,4,2,1,2,3,4,5]
>>> for s in set(source):
... print s
...
1
2
3
4
5
If you want the counts, use defaultdict
from collections import defaultdict
d = defaultdict(int)
source = [2,2,1,3,4,2,1,2,3,4,5]
for s in source:
d[s] += 1
for k, v in d.iteritems():
print k, v
You'll get this:
>>> for k, v in d.iteritems():
... print k, v
...
1 2
2 4
3 2
4 2
5 1
If you want your results sorted, use sort and operator
import operator
for k, v in sorted(d.iteritems(), key=operator.itemgetter(1)):
print k, v
You'll get this:
>>> import operator
>>> for k, v in sorted(d.iteritems(), key=operator.itemgetter(1)):
... print k, v
...
5 1
1 2
3 2
4 2
2 4
I am not sure if it is a good idea to iterate the list and remove elements at the same time. If you really just want to output all items and their number of occurrences, I would do it like this:
listy = [2,2,1,3,4,2,1,2,3,4,5]
listx = []
listc = []
for i in listy:
if not i in listx:
listx += [i]
listc += [listy.count(i)]
for x, c in zip(listx, listc):
print x, c
Like agf said, modifying a list while you iterate it will cause problems. You could solve your code by using while and pop:
single_occurrences = []
while listy:
i = listy.pop(0)
count = listy.count(i)+1
if count > 1:
print i, count
while i in listy: listy.remove(i)
else:
print i, count
single_occurrences.append(i)
Output:
2 4
1 2
3 2
4 2
5 1
One way to do that would be to create a result list and test whether the tested value is in it :
res=[]
listy = [2,2,1,3,4,2,1,2,3,4,5]
for i in listy:
if listy.count(i)>1 and i not in res:
res.append(i)
for i in res:
print i, listy.count(i)
Result :
2 4
1 2
3 2
4 2

Categories

Resources