Skipping elements in a List Python - python

I'm new to programming and I'm trying to do the codingbat.com problems to start. I came across this problem:
Given an array calculate the sum except when there is a 13 in the array. If there is a 13 in the array, skip the 13 and the number immediately following it. For example [1,2,13,5,1] should yield 4 (since the 13 and the 5s are skipped).
This is what I have so far. My problem is that I don't know what to do when there are multiple 13s...And I would like to learn coding efficiently. Can you guys help? (I'm using python 3.2) Thanks!
def pos(nums):
for i in nums:
if i == 13:
return nums.index(13)
return False
def sum13(lis):
if pos(lis)!= False:
return sum(lis[:pos(lis)])+sum(lis[pos(lis)+1:])
else:
return sum(lis)

One tricky thing to notice is something like this: [1, 13, 13, 2, 3]
You need to skip 2 too
def getSum(l):
sum = 0
skip = False
for i in l:
if i == 13:
skip = True
continue
if skip:
skip = False
continue
sum += i
return sum
Explanation:
You go through the items in the list one by one
Each time you
First check if it's 13, if it is, then you mark skip as True, so that you can also skip next item.
Second, you check if skip is True, if it is, which means it's a item right after 13, so you need to skip this one too, and you also need to set skip back to False so that you don't skip next item.
Finally, if it's not either case above, you add the value up to sum

You can use the zip function to loop the values in pairs:
def special_sum(numbers):
s = 0
for (prev, current) in zip([None] + numbers[:-1], numbers):
if prev != 13 and current != 13:
s += current
return s
or you can do a oneliner:
def special_sum(numbers):
return sum(current for (prev, current) in zip([None] + numbers[:-1], numbers)
if prev != 13 and current != 13)
You can also use iterators:
from itertools import izip, chain
def special_sum(numbers):
return sum(current for (prev, current) in izip(chain([None], numbers), numbers)
if prev != 13 and current != 13)
(the first list in the izip is longer than the second, zip and izip ignore the extra values).

Use a while loop to walk through the list, incrementing i manually. On each iteration, if you encounter a 13, increment i twice; otherwise, add the value to a running sum and increment i once.
def skip13s(l):
i = 0
s = 0
while (i < len(l)):
if l[i] == 13:
i += 1
else:
s += l[i]
i += 1
return s

Some FP-style :)
def add_but_skip_13_and_next(acc, x):
prev, sum_ = acc
if prev != 13 and x != 13:
sum_ += x
return x, sum_
filter_and_sum = lambda l: reduce(add_but_skip_13_and_next, l, (0,0))[1]
>>> print filter_and_sum([13,13,1,4])
4
>>> print filter_and_sum([1,2,13,5,13,13,-9,13,13,13,13,13,1,1])
4
This code works for any iterator, even it not provide the random access (direct indexing) - socket for example :)
Oneliner :)
>>> filter_and_sum = lambda l: reduce(
... lambda acc, x: (x, acc[1] + (x if x != 13 and acc[0] != 13 else 0)),
... l, (0,0))[1]
>>> print filter_and_sum([1,2,13,5,13,13,-9,13,13,13,13,13,1,1])
4

I think this is the most compact solution:
def triskaidekaphobicSum(sequence):
return sum(sequence[i] for i in range(len(sequence))
if sequence[i] != 13 and (i == 0 or sequence[i-1] != 13))
This uses the builtin sum() function on a generator expression. The generator produces all the elements in the sequence as long as they are not 13, or immediately following a 13. The extra "or" condition is to handle the first item in the sequence (which has no previous item).

You can use while loop to treat multiple 13.
def sum13(lis):
while pos(lis):
if pos(lis) == len(lis) - 1:
lis = lis[:pos(lis)]
else:
lis = lis[:pos(lis)]+lis[pos(lis)+1:]
return sum(lis)

def skipAndAddFun(inputVal, skipList):
sum = 0
for i in inputVal:
if not i in skipList:
sum += i
return sum
Usage:
skipAndAddFun([1,2,13,5,1], [13, 5])
4
This simple function will be a generic solution for your question.

Related

Index value not in list

I need to write code that returns 2 index numbers of an array. The function takes 2 arguments, one is an array and the other is an integer.
I need to check if two of the values inside the array adds up to the integer and using the numbers inside the array only once.
Here is my code:
def func(a,b):
for i in a:
cnt = 0
while cnt < len(a):
if i + a[cnt] == b and i != a[cnt]:
return list([i,a[cnt]])
else:
cnt += 1
print(func([3,7,2,10,20],27))
My output for func([3, 7, 2, 10, 20], 27) is [7, 20].
This code shows that the loop can find the numbers which add up to the integer.
But when I do this:
def func(a,b):
for i in a:
cnt = 0
while cnt < len(a):
if i + a[cnt] == b and i != a[cnt]:
return a.index(i,a[cnt])
else:
cnt += 1
print(func([3,7,2,10,20],27))
I get the Value error: 7 not in list, which clearly is.
I've had this issue working with other exercises as well.
Am I doing something wrong or the index function isn't suppose to be used like that.
What would be an efficient way to return the index numbers without having to write another loop for it?
The second parameter to index that you're passing is actually the starting index on the list in which the search for the element will start (as you can see here). If you remove it, you'll see that it returns the first value you want (but not the second). It is relevant to note that the index method will only ever return the first occurrence of the value.
def func(a,b):
for i in a:
cnt = 0
while cnt < len(a):
if i + a[cnt] == b and i != a[cnt]:
return a.index(i)
else:
cnt += 1
print(func([3,7,2,10,20],27))
>>> 1
This happens because the value you're passing as the starting index (a[cnt]) is greater than the actual index of the number (1).
By removing it, you search through all the list, and find the correct value (remembering that Python uses zero-indexed iterators).
But since you want to return a list with both values, you need to explicitly state you want the index for each, such as:
def func(a,b):
for i in a:
cnt = 0
while cnt < len(a):
if i + a[cnt] == b and i != a[cnt]:
return [a.index(i), a.index(a[cnt])]
else:
cnt += 1
print(func([3,7,2,10,20],27))
>>> [1, 4]
You could achieve the same results using two for loops, however, in a cleaner way (also gave meaningful names to variables):
def find_indices_for_sum(array, sum_value):
for i in array:
for j in array:
if i + j == sum_value and i != j:
return [array.index(i), array.index(j)]
print(find_indices_for_sum([3,7,2,10,20],27))
>>> [1, 4]
If you want to be able to deal with equal numbers, you can change the comparison strategy altogether, using indices instead of values, since the former are unique in a list, but the latter are not. enumerate is a good option here, since it allows to iterate both through index and values at the same time in a clean way.
def find_indices_for_sum(array, sum_value):
for i, value_i in enumerate(array):
for j, value_j in enumerate(array):
if i != j and value_i + value_j == sum_value:
return [i, j]
print(find_indices_for_sum([3,3],6))
>>> [0, 1]

Why this code is not working for specific type of data?

def find_even_index(arr):
for a in arr:
b = arr.index(a)
if sum(arr[b+1:]) == sum(arr[:b]):
return b
return -1
find_even_index([20,10,30,10,10,15,35])
>>> -1
My code works for all type of data except when it encounters a same digit before or after the number. The index does not change for other 10. Why?
Just writing out the solution that #Barmar is suggesting in his comments (+1):
def find_even_index(array):
for index in range(len(array)):
if sum(array[:index]) == sum(array[index + 1:]):
return index
return -1
print(find_even_index([20, 10, 30, 10, 10, 15, 35]))
Alternatively, if we wanted to avoid so many calls to sum(), and make good use of enumerate(), we could try:
def find_even_index(array):
total = sum(array)
partial_sum = 0
for index, number in enumerate(array):
if total - partial_sum == number:
return index
partial_sum += number * 2
return -1
The list.index() function will only return the index of the specified object closest to the start of the list. You can instead use enumerate to iterate through the list alongside each object's index:
def find_even_index(arr):
for i, a in enumerate(arr): # For index, number in arr
b = i
if sum(arr[b+1:]) == sum(arr[:b]):
return b
return -1
print(find_even_index([20,10,30,10,10,15,35]))
Output:
3

Deleting indexes in List based on specific values

I am trying to solve the following:
Return the sum of the numbers in the array, returning 0 for an empty array. Except the number 13 is very unlucky, so it does not count and numbers that come immediately after a 13 also do not count.
Here is what I have, the idea here being the 13 and 1(right after it) get deleted and then the remaining numbers are summed. The issue I have is the delete portion, it's not actually deleting anything. Is this a syntax issue?
x = [1,2,2,1,13,1]
def sum13(nums):
for i in nums:
if i == 13:
del nums[i:i+1]
return sum(nums)
print(sum13(x))
20 <-- should be 6
Your problem is with your index. i is the number in the list, not the index.
Here's a way to solve your problem:
x = [1,2,2,1,13,1]
def sum13(nums):
for i, num in enumerate(nums):
if num == 13:
del nums[i:i+2] # This removes the index i and the index i+1
return sum(nums)
print(sum13(x))
>>> 6
EDIT:
As Thierry Lathuille mentioned in the comments, this doesn't adequately account for the case where you have repeated '13's. Assuming you want this behavior, here's a way you can do that:
def sum13(nums):
for i, num in enumerate(nums):
if num == 13:
stop_cut = i + 1
while nums[stop_cut] == 13:
stop_cut += 1
del nums[i:stop_cut+1]
return sum(nums)
Here's example with recurrent function. As longest as there's 13 in list we sum everything that's before it and sum13 everything that's after that 13.
x = [1,2,2,1,13,1]
def sum13(nums, first_call=False):
if not first_call and nums[0] != 13:
nums = nums[1:]
if 13 in nums:
return sum(nums[:nums.index(13)]) + sum13(nums[nums.index(13)+1:])
return sum(nums)
print(sum13(x, True)) # -> 6
Note that this solution works with neighboring 13s.
x = [13, 13, 1]
print(sum13(x, True)) # -> 0
As long as you are looping through the list, just keep a running sum and a record of the previous value. If i is not 13 and the previous was not 13 add to the sum No need to modify the list passed in.
def sum13(nums):
sum = 0
last = None
for i in nums:
if i != 13 and last != 13:
sum += i
last = i
return sum
One issue is that you are using your list element value as your index. Here's a solution using a generator. First identify index of values to ignore, then create a new list excluding those values.
x = [1,2,2,1,13,1]
def sum13(nums):
def filter13(nums):
for n, i in enumerate(nums):
if i == 13:
yield n
yield n + 1
bad_ix = set(filter13(nums))
new_nums = [x for n, x in enumerate(nums) if n not in bad_ix]
return sum(new_nums)
sum13(x)

How to see if the list contains consecutive numbers

I want to test if a list contains consecutive integers and no repetition of numbers.
For example, if I have
l = [1, 3, 5, 2, 4, 6]
It should return True.
How should I check if the list contains up to n consecutive numbers without modifying the original list?
I thought about copying the list and removing each number that appears in the original list and if the list is empty then it will return True.
Is there a better way to do this?
For the whole list, it should just be as simple as
sorted(l) == list(range(min(l), max(l)+1))
This preserves the original list, but making a copy (and then sorting) may be expensive if your list is particularly long.
Note that in Python 2 you could simply use the below because range returned a list object. In 3.x and higher the function has been changed to return a range object, so an explicit conversion to list is needed before comparing to sorted(l)
sorted(l) == range(min(l), max(l)+1))
To check if n entries are consecutive and non-repeating, it gets a little more complicated:
def check(n, l):
subs = [l[i:i+n] for i in range(len(l)) if len(l[i:i+n]) == n]
return any([(sorted(sub) in range(min(l), max(l)+1)) for sub in subs])
The first code removes duplicates but keeps order:
from itertools import groupby, count
l = [1,2,4,5,2,1,5,6,5,3,5,5]
def remove_duplicates(values):
output = []
seen = set()
for value in values:
if value not in seen:
output.append(value)
seen.add(value)
return output
l = remove_duplicates(l) # output = [1, 2, 4, 5, 6, 3]
The next set is to identify which ones are in order, taken from here:
def as_range(iterable):
l = list(iterable)
if len(l) > 1:
return '{0}-{1}'.format(l[0], l[-1])
else:
return '{0}'.format(l[0])
l = ','.join(as_range(g) for _, g in groupby(l, key=lambda n, c=count(): n-next(c)))
l outputs as: 1-2,4-6,3
You can customize the functions depending on your output.
We can use known mathematics formula for checking consecutiveness,
Assuming min number always start from 1
sum of consecutive n numbers 1...n = n * (n+1) /2
def check_is_consecutive(l):
maximum = max(l)
if sum(l) == maximum * (maximum+1) /2 :
return True
return False
Once you verify that the list has no duplicates, just compute the sum of the integers between min(l) and max(l):
def check(l):
total = 0
minimum = float('+inf')
maximum = float('-inf')
seen = set()
for n in l:
if n in seen:
return False
seen.add(n)
if n < minimum:
minimum = n
if n > maximum:
maximum = n
total += n
if 2 * total != maximum * (maximum + 1) - minimum * (minimum - 1):
return False
return True
import numpy as np
import pandas as pd
(sum(np.diff(sorted(l)) == 1) >= n) & (all(pd.Series(l).value_counts() == 1))
We test both conditions, first by finding the iterative difference of the sorted list np.diff(sorted(l)) we can test if there are n consecutive integers. Lastly, we test if the value_counts() are all 1, indicating no repeats.
I split your query into two parts part A "list contains up to n consecutive numbers" this is the first line if len(l) != len(set(l)):
And part b, splits the list into possible shorter lists and checks if they are consecutive.
def example (l, n):
if len(l) != len(set(l)): # part a
return False
for i in range(0, len(l)-n+1): # part b
if l[i:i+3] == sorted(l[i:i+3]):
return True
return False
l = [1, 3, 5, 2, 4, 6]
print example(l, 3)
def solution(A):
counter = [0]*len(A)
limit = len(A)
for element in A:
if not 1 <= element <= limit:
return False
else:
if counter[element-1] != 0:
return False
else:
counter[element-1] = 1
return True
The input to this function is your list.This function returns False if the numbers are repeated.
The below code works even if the list does not start with 1.
def check_is_consecutive(l):
"""
sorts the list and
checks if the elements in the list are consecutive
This function does not handle any exceptions.
returns true if the list contains consecutive numbers, else False
"""
l = list(filter(None,l))
l = sorted(l)
if len(l) > 1:
maximum = l[-1]
minimum = l[0] - 1
if minimum == 0:
if sum(l) == (maximum * (maximum+1) /2):
return True
else:
return False
else:
if sum(l) == (maximum * (maximum+1) /2) - (minimum * (minimum+1) /2) :
return True
else:
return False
else:
return True
1.
l.sort()
2.
for i in range(0,len(l)-1)))
print(all((l[i+1]-l[i]==1)
list must be sorted!
lst = [9,10,11,12,13,14,15,16]
final = True if len( [ True for x in lst[:-1] for y in lst[1:] if x + 1 == y ] ) == len(lst[1:]) else False
i don't know how efficient this is but it should do the trick.
With sorting
In Python 3, I use this simple solution:
def check(lst):
lst = sorted(lst)
if lst:
return lst == list(range(lst[0], lst[-1] + 1))
else:
return True
Note that, after sorting the list, its minimum and maximum come for free as the first (lst[0]) and the last (lst[-1]) elements.
I'm returning True in case the argument is empty, but this decision is arbitrary. Choose whatever fits best your use case.
In this solution, we first sort the argument and then compare it with another list that we know that is consecutive and has no repetitions.
Without sorting
In one of the answers, the OP commented asking if it would be possible to do the same without sorting the list. This is interesting, and this is my solution:
def check(lst):
if lst:
r = range(min(lst), max(lst) + 1) # *r* is our reference
return (
len(lst) == len(r)
and all(map(lst.__contains__, r))
# alternative: all(x in lst for x in r)
# test if every element of the reference *r* is in *lst*
)
else:
return True
In this solution, we build a reference range r that is a consecutive (and thus non-repeating) sequence of ints. With this, our test is simple: first we check that lst has the correct number of elements (not more, which would indicate repetitions, nor less, which indicates gaps) by comparing it with the reference. Then we check that every element in our reference is also in lst (this is what all(map(lst.__contains__, r)) is doing: it iterates over r and tests if all of its elements are in lts).
l = [1, 3, 5, 2, 4, 6]
from itertools import chain
def check_if_consecutive_and_no_duplicates(my_list=None):
return all(
list(
chain.from_iterable(
[
[a + 1 in sorted(my_list) for a in sorted(my_list)[:-1]],
[sorted(my_list)[-2] + 1 in my_list],
[len(my_list) == len(set(my_list))],
]
)
)
)
Add 1 to any number in the list except for the last number(6) and check if the result is in the list. For the last number (6) which is the greatest one, pick the number before it(5) and add 1 and check if the result(6) is in the list.
Here is a really short easy solution without having to use any imports:
range = range(10)
L = [1,3,5,2,4,6]
L = sorted(L, key = lambda L:L)
range[(L[0]):(len(L)+L[0])] == L
>>True
This works for numerical lists of any length and detects duplicates.
Basically, you are creating a range your list could potentially be in, editing that range to match your list's criteria (length, starting value) and making a snapshot comparison. I came up with this for a card game I am coding where I need to detect straights/runs in a hand and it seems to work pretty well.

Sum a list of numbers until a number X is found

In my program I need to put a while function which sums this list until a particular number is found:
[5,8,1,999,7,5]
The output is supposed to be 14, because it sums 5+8+1 and stops when it finds 999.
My idea looks like:
def mentre(llista):
while llista != 999:
solution = sum(llista)
return solution
Use the iter-function:
>>> l = [5,8,1,999,7,5]
>>> sum(iter(iter(l).next, 999))
14
iter calls the first argument, until the second argument is found. So all numbers are summed up, till 999 is found.
Since you mention using a while loop, you could try a generator-based approach using itertools.takewhile:
>>> from itertools import takewhile
>>> l = [5,8,1,999,7,5]
>>> sum(takewhile(lambda a: a != 999, l))
14
The generator consumes from the list l as long as the predicate (a != 999) is true, and these values are summed. The predicate can be anything you like here (like a normal while loop), e.g. you could sum the list while the values are less than 500.
An example of explicitly using a while loop would be as follows:
def sum_until_found(lst, num):
index = 0
res = 0
if num in lst:
while index < lst.index(num):
res += lst[index]
index += 1
else:
return "The number is not in the list!"
return res
Another possible way is:
def sum_until_found(lst, num):
index = 0
res = 0
found = False
if num in lst:
while not found:
res += lst[index]
index += 1
if lst[index] == num:
found = True
else:
return "The number is not in the list!"
return res
There's many ways of doing this without using a while loop, one of which is using recursion:
def sum_until_found_3(lst, num, res=0):
if num in lst:
if lst[0] == num:
return res
else:
return sum_until_found_3(lst[1:], num, res + lst[0])
else:
return "The number is not in the list!"
Finally, an even simpler solution:
def sum_until_found(lst, num):
if num in lst:
return sum(lst[:lst.index(num)])
else:
return "The number is not in the list!"
Use index and slice
def mentre(llista):
solution = sum(lista[:lista.index(999)])
return solution
Demo
>>> lista = [5,8,1,999,7,5]
>>> sum(lista[:lista.index(999)])
14

Categories