I am new to programming in Python and am trying to write a For/While loop that returns a value relative to another in a list based on the current value in the loop. The closest analog from excel I can think of is the OFFSET function, but my searches have failed to come up with an answer. Also, I believe VBA in excel allows you to use relative cells instead of absolute. Maybe this is a better analog.
As an example:
Suppose I have a simple list abc = [2,5,21,54,23,12,23] and I want to create a variable that is the current value in the loop divided by the prior value. So in this instance 5 would be divided by 2, 21 divided by 5, etc.
An example of the code would like something like:
for val in abc:
xyz = val/val[-1]
print(xyz)
I understand I would need to write an exception for the first iteration and the syntax is incorrect, but just trying to convey my message.
My search found that pandas has an ability to do this, but I was wanting to know if it is possible to do without pandas. Would appreciate any input.
You can't just loop by value since the value has no context of its position in the list. One way is to loop by index:
# start from 1, not 0
for i in range(1, len(abc)):
xyz = abc[i] / abc[i - 1]
print(xyz)
Or, zip the list with its tail:
for prev, cur in zip(abc, abc[1:]):
xyz = cur / prev
print(xyz)
For intuition on why this works:
prev
v
abc -> 1 2 3 4 5 6 7
abc[1:] -> 2 3 4 5 6 7
^
cur
You can use enumerate to do this:
abc = [2,5,21,54,23,12,23]
for index, val in enumerate(abc):
print("VALUE: {}, PREVIOUS: {}, RESULT: {}".format(val, abc[index-1], val/abc[index-1]))
However, there is a gotcha here. Python can have negative indexes. Where it wraps around the the other side of the list. If you do the above, the first time through the loop you will see it do this:
VALUE: 2, PREVIOUS: 23, RESULT: 0.08695652173913043
So, to protect against that, you could do this:
abc = [2,5,21,54,23,12,23]
for index, val in enumerate(abc):
if index > 0:
print("VALUE: {}, PREVIOUS: {}, RESULT: {}".format(val, abc[index-1], val/abc[index-1]))
else:
print("First Value")
Resulting in this output:
First Value
VALUE: 5, PREVIOUS: 2, RESULT: 2.5
VALUE: 21, PREVIOUS: 5, RESULT: 4.2
VALUE: 54, PREVIOUS: 21, RESULT: 2.5714285714285716
VALUE: 23, PREVIOUS: 54, RESULT: 0.42592592592592593
VALUE: 12, PREVIOUS: 23, RESULT: 0.5217391304347826
VALUE: 23, PREVIOUS: 12, RESULT: 1.9166666666666667
Pythonic style:
abc = [2,5,21,54,23,12,23]
gen = (abc[i + 1] / v for i, v in enumerate(abc) if i + 1 < len(abc))
for i in gen:
print(i)
Result:
2.5
4.2
2.5714285714285716
0.42592592592592593
0.5217391304347826
1.9166666666666667
Related
I'm kind of stuck on an Edabit Challenge (Link: https://edabit.com/challenge/S9KCN5kqoDbhNdKh5 ),
and the objective is to basically find the total amount of characters in a list (all of the values are strings) and return that value.
Some examples are provided:
count_characters([
"###",
"###",
"###"
]) ➞ 9
count_characters([
"22222222",
"22222222",
]) ➞ 16
count_characters([
"------------------"
]) ➞ 18
count_characters([]) ➞ 0
count_characters(["", ""]) ➞ 0
My current code to achieve the goal is:
def count_characters(lst):
print(lst) #I print the list
count = 0 #Setting my count var to 0
for item in lst: #Using a for loop to iterate through the list's values
count += len(lst) #Getting the length of each value and adding it to the variable 'count'
print(len(lst)) #I also print the length of each value
print(count) #Outside the for loop, I print the count variable
return count #Then I return it
(There are multiple tests to check if the entire function works btw)
When I ran the code, this is what the console outputs:
['###', '###', '###']
3
3
3
9
Test Passed
['22222222', '22222222']
2
2
4
FAILED: 4 should equal 16
ERROR: Traceback:
in <module>
File "./frameworks/python/cw-2.py", line 28, in assert_equals
expect(actual == expected, message, allow_raise)
File "./frameworks/python/cw-2.py", line 18, in expect
raise AssertException(message)
cw-2.AssertException: 4 should equal 16
I don't understand why the code did not find the length of the 2nd test, but worked for the 1st test.
Thank you
A very minor mistake...in the loop
for item in lst:
count+=len(item)
You are doing
for item in lst:
count+=len(lst)
As in the 1st case, both length of list & length of each element is 3, the code gave the desired output but not for the 2nd case
I want to create a function (without using libraries) which takes as input three integer numbers (>0) (a,b,c) , for example:
a = 6
b = 6
c = 3
and returns a list containing c elements (so in this case the returned list should contain 3 elements) taken from a list of a numbers (so in this case the initial list is [1,2,3,4,5,6]). The c elements of the returned list have to be the ones that managed to remain in the initial list after removing a number every b positions from the list of a elements until len(return_list) = c.
So for a = 6, b = 6 and c = 3 the function should do something like this:
1) initial_list = [1,2,3,4,5,6]
2) first_change = [2,3,4,5,6] #the number after the 6th (**b**) is 1 because after the last element you keep counting returning to the first one, so 1 is canceled
3) second_change = [2,4,5,6] #you don't start to count positions from the start but from the first number after the eliminated one, so new number after the 6th is 3 and so 3 is canceled
4) third_change = [2,4,5] #now the number of elements in the list is equal to **c**
Notice that if, when counting, you end up finishing the elements from the list, you keep the counting and return to the first element of the list.
I made this function:
def findNumbers(a,b,c):
count = 0
dictionary = {}
countdown = a
for x in range(1,a+1):
dictionary[x] = True
while countdown > c:
for key,value in dictionary.items():
if value == True:
count += 1
if count == b+1:
dictionary[key] = False
count = 0
countdown -= 1
return [key for key in dictionary.keys() if dictionary[key] == True]
It works in some cases, like the above example. But it doesn't work everytime.
For example:
findNumbers(1000,1,5)
returns:
[209, 465, 721, 977] #wrong result
instead of:
[209, 465, 721, 849, 977] #right result
and for bigger numbers, like:
findNumbers(100000, 200000, 5)
it takes too much time to even do its job, I don't know if the problem is the inefficiency of my algorithm or because there's something in the code that gives problems to Python. I would like to know a different approach to this situation which could be both more efficient and able to work in every situation. Can anyone give me some hints/ideas?
Thank you in advance for your time. And let me know if you need more explanations and/or examples.
You can keep track of the index of the last list item deleted like this:
def findNumbers(a, b, c):
l = list(range(1, a + 1))
i = 0
for n in range(a - c):
i = (i + b) % (a - n)
l.pop(i)
return l
so that findNumbers(6, 6, 3) returns:
[2, 4, 5]
and findNumbers(1000, 1, 5) returns:
[209, 465, 721, 849, 977]
and findNumbers(100000, 200000, 5) returns:
[10153, 38628, 65057, 66893, 89103]
I thought I could be recursive about the problem, so I wrote this:
def func(a,b,c):
d = [i+1 for i in range(a)]
def sub(d,b,c):
if c == 0: return d
else:
k = b % len(d)
d.pop(k)
d = d[k:] + d[:k]
return sub(d,b,c-1)
return sub(d,b,a-c)
so that func(6,6,3) returns: [2, 4, 5] successfully and func(1000,1,5) returns: [209, 465, 721, 849, 977] unfortunately with an error.
It turns out that for values of a > 995, the below flag is raised:
RecursionError: maximum recursion depth exceeded while calling a Python object
There was no need to try func(100000,200000,5) - lesson learnt.
Still, rather than dispose of the code, I decided to share it. It could serve as a recursive thinking precautionary.
Given a sorted list of increasing numbers, I'm trying to create a new list that only keeps values that are at least 3 greater than the previous number. I have tried some conditional statements, but fail to get the correct format. For example, from
a = [3,4,8,12,14,16]
we would obtain
new_a = [3,8,12,16]
Only 14 would drop out because it is less than 3 away from 12, but keep 16 because it is greater than 3 from 12. Also 4 would drop out. Any help would be appreciated!
This should do:
new_a = a[:1]
for i in a[1:]:
if i >= new_a[-1] + 3:
new_a.append(i)
a = [3,4,8,12,14,16]
b = a[:1]
last_num = b[0]
for num in a:
if last_num is not None and last_num + 3 <= num:
b.append(num)
last_num = num
print(b)
Possibly overkill, but if you end up doing this computation a lot (as well as adding new numbers to the list) you can try subclassing list.
class Newlist(list):
def __init__(self, *args):
super(Newlist, self).__init__(*args)
if len(self) > 0:
i = 1
while i<len(self):
if self.__getitem__(i) < 3+self.__getitem__(i-1):
self.remove(self.__getitem__(i))
i += 1
def append(self, item):
if len(self) == 0:
super(Newlist, self).append(item)
elif item >= self.__getitem__(-1) + 3:
super(Newlist, self).append(item)
else: return
Thus you can initialize a list with
a = Newlist([3, 4, 8, 12, 14, 16])
Which will automatically be shortened to [3, 8, 12, 16]
Also append is overriden to only allow new values that follow your rule. Example: a.append(20) will add 20 to the end of a, but a.append(17) will do nothing.
I am trying to write a function that takes a list input and returns the index of the smallest number in that list.
For example,
minPos( [5,4,3,2,1] ) → 4
When I run my function, I get a List Index error, can someone please help? Thanks. I cannot use the built in function min().
def MinPos(L):
Subscript = 0
Hydrogen = 1
SmallestNumber = L[Subscript]
while L[Subscript] < len(L):
while L[Subscript] < L[Subscript + Hydrogen]:
Subscript += 1
return SmallestNumber
while L[Subscript] > L[Subscript + Hydrogen]:
Subscript += 1
return SmallestNumber
def main():
print MinPos( [-5,-4] )
Maybe something like this:
>>> def min_pos(L):
... min = None
... for i,v in enumerate(L):
... if min is None or min[1] > v:
... min = (i,v)
... return min[0] if min else None
>>> min_pos([1,3,4,5])
0
>>> min_pos([1,3,4,0,5])
3
Edit: Return None if empty list
Since you already know how to find the minimum value, you simply feed that value to the index() function to get the index of this value in the list. I.e,
>>> n = ([5,4,3,2,1])
>>> n.index(min(n))
4
This will return the index of the minimum value in the list. Note that if there are several minima it will return the first.
I would recommend use of for ... and enumarate():
data = [6, 3, 2, 4, 2, 5]
try:
index, minimum = 0, data[0]
for i, value in enumerate(data):
if value < minimum:
index, minimum = i, value
except IndexError:
index = None
print index
# Out[49]: 2
EDIT added guard against empty data
I try to discretize some numbers, by looking if they are in a given range, and then assign a number based on the range, however the result which I get is not exactly correct.
mapp is the a dictionary of which defines ranges, and the values which correspond to the given range.
lst is the list of numbers that I want to match against those ranges, and assign identifiers to them
mapp = {(0,100): 1, (100,400): 2, (400,800): 3}
lst = [3.5, 5.4, 300.12, 500.78, 600.45, 900.546]
def discretize(mapping_dict, list_of_values):
print "\n"
location = []
for x in sorted(list_of_values):
for (lower_bound,upper_bound),value in mapping_dict.items():
if round(x) in range(lower_bound,upper_bound):
print round(x), "yes", value
distance = mapping_dict[(lower_bound,upper_bound)]
location.append((distance))
else:
print round(x), "no"
distance = len(mapping_dict.items())+10
location.append((distance))
return location
The result which I expect is: [1, 1, 2, 3, 3, 13] , however that's not what I get.
This is the actual result which I get, which is incorrect:
4.0 yes 1
4.0 no #wrong!
5.0 yes 1
5.0 no #wrong!
300.0 yes 2
300.0 no #wrong!
501.0 yes 3
501.0 no #wrong!
600.0 yes 3
600.0 no #wrong!
901.0 no #CORRECT
[1, 13, 1, 13, 2, 13, 3, 13, 3, 13, 13]
I get no at 4.0 which is not correct, etc, etc.
Where is the problem?
Thanks
mapp = {(0,100): 1, (100,400): 2, (400,800): 3}
lst = [3.5, 5.4, 300.12, 500.78, 600.45, 900.546]
result = []
for l in lst:
for m in mapp:
if m[0] < l < m[1]:
result.append(mapp[m])
print result
Output:
[1, 1, 2, 3, 3]
EDIT:
result = []
for l in lst:
flag=True
for m in mapp:
if m[0] < l < m[1]:
result.append(mapp[m])
flag = False
break
if flag:
result.append(-1)
print result
Output:
[1, 1, 2, 3, 3, -1]
I think I have faced a similar problem once, because I found a small RangeDict class:
class RangeDict (dict):
def __init__ (self, *args):
super ().__init__ ()
def __setitem__ (self, k, v):
if not isinstance (k, slice): raise ValueError ('Indices must be slices.')
super ().__setitem__ ( (k.start, k.stop), v)
def __getitem__ (self, k):
for (start, stop), v in self.items ():
if start <= k < stop: return v
raise IndexError ('{} out of bounds.'.format (k) )
I hope this class wraps your desired funcionality. Obviously lookup is O(N) and not O(1).
Sample usage:
r = RangeDict ()
r [0:100] = 1
r [100:400] = 2
r [400:800] = 3
for x in [3.5, 5.4, 300.12, 500.78, 600.45, 900.546]:
print (r [x] )
#Last value raises IndexError
Putting an else after your for loop you were an the right track! When you put an else after a loop, that else block is executed each time the loop exits normally, i.e. without using e.g. break. Thus, (assuming that your groups are non-overlapping) you just need to add a break statement to the end of your if block, i.e. after location.append((distance)). Then it works as expected.
Also, instead of checking whether the number is in the range (which creates and searches a list each time!) you should just use <= and <. Also, you already have the value, so why not use it?
for (lower_bound, upper_bound), value in mapping_dict.items():
if lower_bound <= x < upper_bound:
location.append(value)
break
else:
location.append(len(mapping_dict) + 10)