How to generate an alternating range? - python

I need to search for an item in a list around a given index with in a given radius. Currently I use this function to generate alternating offsets for the search:
def generateSearchIndizes(radius):
for i in range(1, radius + 1):
yield i
yield -i
The code that does the search looks something like this:
for i in generateSearchIndizes():
if pred(myList[baseIndex + i]):
result = myList[baseIndex + i]
break # terminate search when first item is found
My question is, is there a more elegant way to generate the search indizes, maybe without defining a special function?

is there a more elegant way to generate the search indices
I don't think there's a more elegant way. Your code is very simple and clear.
maybe without defining a special function?
Yes, that's definitely possible.
>>> [b for a in ((x,-x) for x in range(1, 10 + 1)) for b in a]
[1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, 7, -7, 8, -8, 9, -9, 10, -10]

Here's my go at it:
from itertools import chain
>>> list(chain(*zip(range(1, 7), range(-7, 0)[::-1])))
[1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6]
Adjust as needed. :)

Perhaps a generator expression?
for i in (x/2 * (x%2 * 2 - 1) for x in xrange(2, radius*2)):
print i
It is short and fits to your "without defining a special function" requirement...
But, franky, I'd still prefer to use that special function instead - just for the sake of having more clear code. :)

Why not just do this with an inner loop, instead of creating a funky generator:
found = False
for i_abs in range(1, radius+1):
for i in (i_abs, -i_abs):
listitem = myList[baseIndex + i]
if pred(listitem):
result = listitem
found = True
break # terminate search when first item is found
if found:
break
else:
# handling in case no match found for pred
Some other comments:
you never test the 0'th element
since you are testing from both left
and right, you should stop after
i_abs reaches the halfway mark
I don't like repeating list indexing operations,
would rather repeat a variable reference; so I
lifted out the repeated myList[baseIndex+i] and
assigned it to listitem
you should add some handling in case
there is no matching element found
instead of breaking from the inner loop (which
here requires an extra found variable to
break out of the outer for loop), you might
be better just returning result right from the
inner loop,
as in:
for i_abs in range(1, radius+1):
for i in (i_abs, -i_abs):
listitem = myList[baseIndex + i]
if pred(listitem):
return listitem
Then there is no break management or found variable required.

Why alternate -i, i? Just do:
for i in range(-radius, radius+1):
listitem = myList[baseIndex + i]
if pred(listitem):
return listitem
Or if you absolutely must approach from front and back to get the outermost pred-matcher, how about:
for i in sorted(range(-radius, radius+1), key=abs):
listitem = myList[baseIndex + i]
if pred(listitem):
return listitem
If you have to do this often, just build sorted(range(-radius,radius+1),key=abs) once and keep it around for future iterating.
If you absolutely must not use the 0'th element, just insert a if not i: continue at the start of your loop.

This appears to me at least as readable as a separate function -- and arguably more understandable:
radius = 3
for outerbounds in ((-r,r) for r in range(1,radius+1)):
for i in outerbounds :
print i
# -1
# 1
# -2
# 2
# -3
# 3

Your own solution, adding a yield 0 at the start of the generator, is the most straightforward (aka pythonic).
Here's an infinite offset generator with a different algorithm:
def gen_offsets():
offset= 0
yield offset
step= 1; sign= 1
while 1:
offset+= sign*step
yield offset
step+= 1; sign= -sign
A more fanciful (aka not-so-pythonic :) way to write the algorithm above is:
import itertools as it, operator as op
def gen_offsets():
steps= it.imap(op.mul, it.count(1), it.cycle( (1, -1) ))
offset= 0
yield offset
for step in steps:
offset+= step
yield offset

Related

how to loop through each element in list and increment by one

Hello i want to ask how to loop through each element and increment every time by one this is what i want first i want to sum 0 , 1, 3 ,6 , 10 and after that sum can somebody help me about that i don't know how to tell if it is loop through each element or iterate.It should look like these examples.I am sorry!
ls = [0, 1, 3, 6, 10]
ls = [1, 3, 6, 10]
ls = [3, 6, 10]
ls = [6, 10]
ls = [10]
ls = []
Here's the problem who i want to solve it :
https://www.codewars.com/kata/5ce399e0047a45001c853c2b/train/python
I tried this but it doesn't work
def parts_sums(ls):
length_list = len(ls)
for i in range(0,length_list+1):
return length_list
Note that there is a built-in function sum() in Python that does that job probably better than any code you can write in Python.
sum([0, 1, 3, 6, 10])
However, if you want to practice writing your sum function by iterating through a list and summing all the elements, this is how you do it.
def my_sum(ls):
result = 0
for i in range(len(ls)):
result += ls[i]
return result
First of all, you need to initialize a variable to hold your result. The range() function generates all values from 0 to x. The for-loop assigns all values generated by the range function to i in order and executes the indented block below. The += assignment increments the left-hand side variable by the right-hand side expression value. At last, we return the result.
And if you prefer using a while-loop,
def my_sum(ls):
result = 0
i = 0
while i < len(ls):
result += ls[i]
i += 1
return result
It's always good to consult Python documentation when you are not sure how to use its built-in function.
If you want the accumulated sum of all items reversed, you can take a look at the accumulate function in itertools.
from itertools import accumulate
def parts_sums(ls):
return list(accumulate(ls[::-1]))[::-1] + [0]
Or if you want to implement with a loop,
def parts_sums(ls):
result = []
part_sum = 0
for item in ls[::-1]:
result.append(part_sum)
part_sum += item
result.append(part_sum)
return result[::-1]
Or if you want to do it without reversing the list (say if you want to yield the results)
def parts_sums(ls):
result = []
part_sum = sum(ls)
for item in ls:
result.append(part_sum)
part_sum -= item
result.append(part_sum)
return result
Note the algorithm is still O(n), not that time complexity matters in this case.
For the question you have mentioned in codewars, you need to loop it thro 2 loops and keep reducing the first element in the inner loop for sum.
def parts_sums(ls):
# your code
sum = []
for i in range(len(ls)):
sum_temp =0
for j in range(i,len(ls)):
sum_temp += ls[j]
sum.append(sum_temp)
sum.append(0) # for the final empty list
return sum
print(parts_sums([0, 1, 3, 6, 10]))
This test will check the execution time too. So you need to be fast.
Naive approach
You can use sum or create your own sum.
def parts_sums(ls):
return [
sum(ls[i:])
for i in range(len(ls) + 1)
]
But i means you'll need to loop in a list twice. So it will be slow.
Sneaky approach
In a list like [a, b, c, d, e] you are calculating:
[a+b+c+d+e, a+b+c+d, a+b+c, a+b, a, 0]. So let's start from last to first element. [0, a, a+b, a+b+c, a+b+c+d, a+b+c+d+e]. Now we see a cumulative iteration:
So get loop in the list, get the element, sum it with last element of the result ([0]) list and add it as the last element to the result list. Lastly reverse the result.
def parts_sums(ls):
res = [0]
for i in range(len(ls)-1, -1, -1):
res.append(res[-1] + ls[i])
return res[::-1]

Finding a pair of values which add up to given sum

lst = [3, 4, 1, 2, 9]
givenSum = 12
table = {}
x = 0
y = 0
for i in range(0, len(lst)):
table[givenSum - lst[i]] = 1
i += 1
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
break
This is the output
9 and 3 is equal to 12
3 and 9 is equal to 12
I don't know why it's being shown up twice. I need to find a pair of values that add up to the given sum and this is the only way I could think of. I only want it to show up once though any ideas on how I can do that?
There are better solutions, but to fix your issue making minimal changes to your code:
lst = [3, 4, 1, 2, 9]
givenSum = 12
for x in range(0, len(lst) - 1):
for y in range(x + 1, len(lst)):
if lst[x] + lst[y] == givenSum:
print(lst[x], "and", lst[y], "is equal to", givenSum)
break
This will print
3 and 9 is equal to 12
Note that the redundant table is completely removed from the code.
If you run it for a better test case:
lst = [3, 4, 5, 6, 7, 1, 2, 9]
it will print
3 and 9 is equal to 12
5 and 7 is equal to 12
First, to address why the looping continues and gives a second output, break can only break out of its immediate loop. Since you have a nested loop, the break only stops the for y in table: inner loop, but allows for x in table outer loop to move onto it's next iteration. So eventually, x is able to take the value of 3 later on, thus giving you the two outputs you see.
So, if you need a way to stop the iteration entirely when a solution is found, you need to either chain the break statements using a for else syntax (which arguably might be tough to read) as follows,
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
break #breaks from inner loop
else: #for else syntax: this block runs if and only if there was no break encountered during looping.
continue #jumps the outer loop to next iteration
break #this break is set at outer loop's level. Essentially, we can only reach this portion if there is a break in the inner loop.
For else says: run through the whole iteration, and if no break is found, executes the code in the else block. Essentially, the "else" of a "for else" is like a "for - no break".
However, one easier alternative is to use a function with a return (which also makes it easier to read the code).
def find_given_sum(lst, givenSum):
table = {}
x = 0
y = 0
for i in range(0, len(lst)):
table[givenSum - lst[i]] = 1
i += 1
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
return #this returns immediately out of the function, thus stopping the iteration.
Also, you could just repeat the break condition, but repeating code is generally not a good practice.
Hope this helps address why the two outputs are being printed. Now, as for the solution itself, there's actually a much better way to solve this. It builds upon the idea of compliments, which you seem to have a sense of in your table. But it doesn't require iteration over the table itself. As a hint: the ideal solution runs in O(n) time. I will not discuss the ideal solution, but hope this prompts you to find the best approach.
Looping twice for n elements costs you O(N^2) time, which is inefficient for large lists. Modified and tested the code to use hash map/dictionary to store the list elements and now it will take only O(N) time.
Map = dict()
lst = [3, 4, 1, 2, 9]
givenSum = 12
for i in range(0, len(lst)):
rem=givenSum-lst[i]
if rem in Map:
print lst[i],lst[Map[rem]]
else:
Map[lst[i]]=i
Store the value of each list element into the map whenever it does not exist in the map.
At each iteration, take the difference between givenSum and current element at that iteration and then search for that difference in the Map.
If it exists it means the pair exists or else not.
In this approach you are running the loop only once, which takes O(N) time because accessing elements in hash map is O(1)/constant time.
Use itertools to get the result
import itertools
sum = 10
lst = [3, 4, 1, 2, 9]
ans = list(filter(lambda x : x[0]+x[1] == sum,itertools.combinations(lst, 2)))

How can I code loop in python

I have a problem with the loop in python. I want create a list x[X0,X1,....Xn], with this algorithm:
X1=X0-(5+X0*2); X2=X1-(5+X1*2);.....
I try this but the result is not correct.
a=list(np.empty(10))
a[0]=1
for i in range(10):
a.append(a[i]-(5+a[i]*2))
print (a [i])
If you manually iterating the result gives:
[1,-6,1,-6, ....]
But after loop it gives:
[1,-1.29074375768e-231,2.19254982219e-314,.....]
The loop are easy in C but I did not understand the functioning in Python, if you have an idea ?
The problem is that list(np.empty(10)) doesn't do what you think it does. You expect it to return a list with 10 zeros, but it actually returns a list of 10 "random" numbers (actually, it returns a list of 10 uninitialized values).
From numpy docs:
empty, unlike zeros, does not set the array values to zero, and may
therefore be marginally faster. On the other hand, it requires the
user to manually set all the values in the array, and should be used
with caution.
(emphasize mine)
Instead, you should simply create a list with a single element, 1, and go on from there:
a = [1]
for i in range(10):
a.append(a[i] - (5 + a[i] * 2))
print(a)
# [1, -6, 1, -6, 1, -6, 1, -6, 1, -6, 1]
There's no reason to start your list with anything more than the single element. In C, you have to decide up front how many elements you want in your list, but that's not the case in Python, so doing list(np.empty(10)) creates a list with ten pointless elements in it that are just getting in your way.
a = [1] # This creates a list that starts off with the element 1.
for i in range(10):
a.append(a[i] - (5 + a[i] * 2))
print (a[i])
They way you have arranged your equation will constantly print out -6, and 1, therefore:
a = [1]
iti = 0
while(True):
thing = a[iti]-(5+(a[iti]*2))
a.append(thing)
iti+=1
print(thing)
I chose not to use the for loop to show a more understandable example, but you can still use that if you like.
I suggest you read up on functions, lists and for loops in python, as from your question it appears that you do not understand them very well.
There are several reasons why your code isn't currently working, but they are all easy fixed.
Firstly, you initiate a list with a numpy list, which is unnecessary.
a = list(np.empty(10))
can simply become
a = list()
or even simpler,
a = []
Next up, not an error but a suggestion,
a[0] = 1
can be removed and instead when you initialize the list, place one as the first item
a = [1]
after that, your code should work. So in summary:
n = 10 # or whatever number you want
a = [1]
for i in range(n - 1):
a.append(a[i] - (5 + a[i] * 2))
and if you want it as a function:
def formula_list(n):
a = [1]
for i in range(n - 1):
a.append(a[i] - (5 + a[i] * 2))
return a

Counting down and then up

I'm new to python and trying to run a function that will, given one variable, count down to zero and then up to the original variable. the output should look something like this:
>>> functionname(5)
5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5
so far I've written the code below, but this doesn't count up all the way to the original variable. I guess I need to somehow save the variable in order to refer to it later, but I have no idea how to do that, since python automatically changes n as the function goes on.
def functionname(n):
n = orginal
while n > 0:
print n
n=n-1
print n
if n==0:
print n
n=n+1
I would be very grateful for some pointers, as I seem to be completely stuck at the moment.
Just count from negative to positive and use math:
def fn(n):
print ', '.join(str(abs(i)) for i in range(-n, n+1))
fn(5)
# 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5
Pointers:
If you already know the range you want to iterate over, it's much cleaner and more direct to use a for loop instead of a while.
You can use the range function to generate number sequences.
You can convert simple for loops into list-comprehensions as I did above.
The "simple" clean implementation of your requirements would look something like this:
def fn(n):
for i in range(n, 0, -1):
print i,
for i in range(n + 1):
print i,
Other notes:
range can count backwards too
The end argument to range isn't included in the range itself, that's why the second loop specifies n + 1 as the limit (instead of just n)
Adding a trailing comma on your print makes it add a space at the end instead of a line-break.
Your second block is an if n == 0: (which you know it is since the while loop terminated when n hit 0); presumably you want while n <= 5.
Note that there are nicer ways to accomplish the same thing in Python. For example, using a pair of ranges with itertools.chain to iterate each range one after another allows you to simplify to:
import itertools
def functionname(n):
for i in itertools.chain(range(n, 0, -1), range(n+1)):
print i
Personally, I'd do something like...
def count(n):
for x in range(n, -n, -1):
print(str(abs(x)) + ",")
At the suggestion of dlewin, here's a list comprehension of the same...
def count(n):
print(','.join(str(abs(x)) for x in range(n, -n, -1)))
You need a second while loop that starts at 0 and goes back up to "original".
Do you know about "for" loops yet? Those are better for counting.
Your idea about having original is correct however you are using the assignment operator the wrong way 'round. Also the if n==0 line should be another loop (while or for as suggested by other answers), counting back up to original.
So I'd start with copying the value from n to original like this:
original = n
Hope that helps!
You got some bad formatting there. Remember to indent properly for functions and while and if statements.
So first, set n to 5. Then count down from there until you reach 0 with a while loop:
while n != -1:
print n
n -= 1
Then after the loop breaks, count back up again and reset n to 0:
n = 0
while n < 6:
print n
n += 1

replace while loop with range function

Can I replace while loop with range function. I am using following code
check = 0
while check<5:
print check
check+=2
I am writing in following way
for _check in range(0,5,2):
print _check
is it correct way?
> Editing My question
if I am not using _check variable inside for loop. Can I avoid to declare also
Yes, you are using range() correctly, but you may want to use xrange() instead here:
for check in xrange(0, 5, 2):
print check
xrange() produces the same results when iterated over, but doesn't build a whole list of all possible indices first, and as a result uses less memory:
>>> range(0, 5, 2)
[0, 2, 4]
>>> xrange(0, 5, 2)
xrange(0, 6, 2)
Since the end-point is not included in the values, it doesn't matter if you use 5 or 6 here as the endpoint, it is just calculated for you from the input parameters.
If you are not using the loop variable, you can use _ to indicate that you are ignoring it in the loop. This is just a naming convention:
for _ in xrange(0, 5, 2):
# do something 3 times.
in which case you may as well just calculate how many indices there are between 0 and 5 with a step of two and simplify your loop to:
upper_limit, step = 5, 2
for _ in xrange((upper_limit - 1 + step) // step):
# do something 3 times.
Regarding the original while loop, using check = check + 1
for x in xrange(5):
print x
See the documentation here for more information on control flow tools (like loops).
EDIT
If you want to increment by 2 during each iteration, you can use the above code with the increment specified:
for x in xrange(0,5,2):
print x

Categories