Function that fills in missing numbers to create complete sequence - python-3.x

I'm trying to create a function that will fill in any missing numbers in between two numbers in a list. The original list must be altered and cannot be new.
For example: [13,15,20] would return [13,14,15,16,17,18,19,20].
Note that I am not allowed to use a range function.
Here's my code:
def complete(list1):
i= 0
if len(list1) > 1:
for number in list1:
if number - list1[i+1] != -1:
number += 1
list1.insert(i + 1, number)
i += 1
return list1
else:
return list1
I got a "list index out of range" error.

Here is the source of your error:
...
for number in list1:
if number - list1[i+1] != -1:
...
i += 1
Basically, there comes a point (that point being the last number in list1) when i+1 gets you out of bounds and you are not doing anything to prevent that from happening. Indexing is tricky like that, so I would like to offer an indexing-free (well, almost) approach. By the way, from your comment to Bonfire's answer, I see that the task is to change original lists in-place. While mutating arguments is considered a very poor coding practice these days, here is a relatively efficient way of doing that:
import typing as t
def complete_sequence(partial: t.List[int]) -> t.List[int]:
# edge case
if len(partial) < 2:
return partial
# a lookup table for numbers we already have
observed = set(partial)
# append numbers we don't have
start = partial[0]
stop = partial[-1]
num = start + 1
while num < stop:
if not num in observed:
partial.append(num)
num += 1
# in-place sort
partial.sort()
return partial
As you see, instead of inserting values between existing numbers (paying O(n) time for each insertion), we can simply append everything (O(1) per insertion) and sort. This not only simplifies the logic (we no longer have to track those pesky indices), but also reduces computational time-complexity from O(n^2) to O(n*log(n)).

To achieve what you want to do I have made some changes to the logic:
def complete(list1):
if len(list1) < 2 : return list1
num = list1[0]
i = -1
while num < list1[-1]:
num += 1
i += 1
if num in list1: continue
if i < len(list1) - 1:
list1.insert(i + 1, num)
else:
list1.append(num)
return list1
print(complete([13, 14, 20]))
# [13, 14, 15, 16, 17, 18, 19, 20]
print(complete([13, 14, 15]))
# [13, 14, 15]

Related

Sum of consecutive data in List - Python

I have a long list of number of which a sample look something like shown below:
L = [0, 1, 2, 0, 0, 2, 3, 1, 0, 10, 0, 2, 4, 5, 0, 0, 7, 8, 9, 0, ...]
singleData = []
sumofTwo = []
sumofThree = []
sumofFour = []
.
.
What I want to be able to do is categorize a data OR sum of two or more consecutive data into the respective lists based on the COUNT of numbers involved in the sum operation. So, if there is an occurrence of zero between two numbers, then their sum would not be considered.
For example, if I take the list above, the sum of 1 and 2 is to be added to the list sumofTwo. Similarly, if it is the sum of three consecutive numbers without the occurrence of 0's then the sum is expected to be in the sumofThree list (sum of 2,3,1; 2,4,5 and 7, 8, 9). If a number occurs between two or more 0's then it is to be appended to the singleData list (eg. 10).
How can i achieve this considering that in the list(L) there can be a sum of random consecutive numbers? Example, sum of 6 or 7 or 8 or any consecutive numbers?
I was able to segregate only a single number between 0's and the sum of two numbers. Following is the code:
for i in range(len(l)):
try:
if i == 0:
if l[i] == 0:
continue
elif l[i] != 0 and l[i+1] == 0:
singleData.append(l[i])
elif l[i] != 0 and l[i+1] != 0:
sumofTwo.append(l[i]+l[i+1])
elif i == len(l)-1:
if l[i] != 0 and l[i-1] == 0:
singleData.append(l[i])
else:
if l[i] != 0:
if l[i+1] == 0 and l[i-1] == 0:
singleData.append(l[i])
elif l[i+1] != 0:
sumofTwo.append(l[i]+l[i+1])
except IndexError:
print("Index out of range")
I realized that my code will only get messier with more cases of the sum of consecutive numbers and ultimately end up with error.
Can anybody help me out? Thanks in advance :))))
I would recommend using a dictionary to store the results:
L = [0, 1, 2, 0, 0, 2, 3, 1, 0, 10, 0, 2, 4, 5, 0, 0, 7, 8, 9, 0]
consecutive_sums = {} # Create an empty dictionary
I would also recommend directly iterating through the list, rather than using range(len(L). e.g.
for number in L:
print(number)
Then, you can just create a counter variable to check how long the current sequence is, and reset the counter when you get to a zero.
L = [0, 1, 2, 0, 0, 2, 3, 1, 0, 10, 0, 2, 4, 5, 0, 0, 7, 8, 9, 0]
consecutive_sums = {} # Create an empty dictionary
counter = 0
sum = 0
for number in L:
if number == 0: # Save the current sum and reset counter
# Check if we've already had a sequence of this length.
# If so, we have already added a list, so can append to it.
if counter in consecutive_sums:
consecutive_sums[counter].append(sum)
else: # If this is the first time we've had this length sequence
# we need to create a new key value pair in the dictionary.
# Note that the value is a list containing `sum`.
# Make sure to use a list so that you can append to it later.
consecutive_sums[counter] = [sum]
# Reset counter and sum
counter = 0
sum = 0
else: # Increment counter
counter += 1
sum += number
print(consecutive_sums)
Note that this code will not sort the dictionary keys, so the sums of sequences of length 1 may not appear at the beginning. But you can access it using consecutive_sums[1].
Also note that this version of the code also counts sequences of length 0. I suspect this is not what you want, but I'll let you figure out how to fix it!
Output:
{0: [0, 0, 0], 2: [3], 3: [6, 11, 24], 1: [10]}
EDIT:
I've intentionally tried to solve this using just builtin functions and datatypes. But if you really want to be fancy, you can use collections.defaultdict.
Below is an alternative version which uses defaultdict:
from collections import defaultdict
L = [0, 1, 2, 0, 0, 2, 3, 1, 0, 10, 0, 2, 4, 5, 0, 0, 7, 8, 9, 0]
consecutive_sums = defaultdict(list) # Create an empty defaultdict of type list
counter = 0
sum = 0
for number in L:
if number == 0: # Save the current sum and reset counter
# The magic of defaultdict:
# We don't need to check if the key exists.
# If it doesn't exist yet, defaultdict will automatically make
# an empty list for us to append to!
consecutive_sums[counter].append(sum)
# Reset counter and sum
counter = 0
sum = 0
else: # Increment counter
counter += 1
sum += number
print(consecutive_sums)
The first thing I would do is to organize a bit better our output lists, so that each can be accessed in the same way using the number of consecutive numbers. As #daviewales suggested, you could do this with a dictionary with lists as values, something like sums = {}, so that sums[1] would be the same as singleData, sums[2] the same as sumofTwo, and so on. That way, you will avoid a lot of if to know in what list you should put your data, you'll just need to use stuff like sums[nbOfValuesInSum].
Second thing, you could write a function that detects your sequences of non-zero values. A possibility would be a function that takes a list and a start index, and returns the start and end indexes of the next "interesting" sequence. It would look like this :
def findNextSequence(l, start):
while l[start] == 0:
if start == len(l)-1:
return None # there is no non-zero value left
start+=1
# when we exit the loop, start is the index of the first non-zero value
end = start + 1
while l[end] != 0:
if end == len(l)-1:
break
end+=1
# and now end is the index of the first zero value after the sequence
return (start, end)
Then you can call it in a loop, like this:
i = 0
while True:
bounds = findNextSequence(l, i)
if bounds is None:
break # there is no non-zero value left
seq = l[bounds[0]:bounds[1]] # get the start and end index of the sequence
if len(seq) not in sums:
sums[len(seq)] = []
sums[len(seq)].append(sum(seq)) # see? No need to explicitly check len(seq) to know what list I want
i = bounds[1] # get ready for the next iteration
if i == len(l):
break
NB : no need to pass l as a parameter of findNextSequence if it's a global variable

How to add each element in list to another element of same list in less time?

Input : l1 = [1,2,3,4,5,6]
Output : [7, 8, 9, 10, 11, 11]
To find the maximum sum of each pair of all elements in a list.
In general, i have to add each element in list to another element (not to itself).
Below is the code what i tried. I know this is complexity of (n^2)
Any better way to reduce complexity (can be both time and space) ?
Any better approach (may be with some modules or with just single for loop) ?
list l1 need not to be in sorted.
l2=[]
l3=[]
for i in range(len(l1)):
for j in range(len(l1)):
if i!=j:
l2.append(l1[i]+l1[j])
l3.append(max(l2))
l2.clear()
print(l3)
[7, 8, 9, 10, 11, 11]
Update:
Submitted this solution in hackerrank, but it fails for few cases.
Reason for failure is TimeLimitExceed (TLE). I assume, it's failing because of large numbers.
**Constraints:**
n = size_of_list
1<= n <= 4*10^4
1<= l1[i] <= 1024
1<= i <= n
1<= j <= n
j != i
Is it because of time-complexity, failing to handle these scenarios in above snippet ?
you can do something like this:
l1 = [1,2,3,4,5,6]
l2=[]
for i in range(len(l1)):
for j in range(i+1,len(l1)):
l2.append(l1[i]+l1[j])
l3=set(l2)
l2=list(l3)
print(l2)
output:
[3, 4, 5, 6, 7, 8, 9, 10, 11]
Sorting your list before processing it also helps.
Let me know if i have made any mistake.
You can do this in O(n).
The basic idea is to get the indices of the maximum element and the second maximum element in the list. Then for each element in the list, append the element + max_element, if the element is not the max_element. Otherwise, append the max_element + second_max_element.
I used the get_first_second_max_index to get the indices of the maximum element and the second maximum element because there might be identical elements in the list.
The code should look like this:
def get_first_second_max_index(nums):
if len(nums) < 2:
return None, None
first_max_index = 0 if nums[0] > nums[1] else 1
second_max_index = 0 if nums[0] <= nums[1] else 1
for index in range(2, len(nums)):
if nums[index] > nums[first_max_index]:
first_max_index, second_max_index = index, first_max_index
elif nums[index] > nums[second_max_index]:
second_max_index = index
return (first_max_index, second_max_index)
def max_sum_pair(nums):
res = []
if len(nums) >= 2:
first_max_index, second_max_index = get_first_second_max_index(nums)
for index in range(len(nums)):
if index == first_max_index:
res.append(nums[index] + nums[second_max_index])
else:
res.append(nums[index] + nums[first_max_index])
return res
nums = [1,2,3,4,5,6]
res = max_sum_pair(nums)
print(res)
For starters, we can use sorting to our advantage. The Python sorting method, sorted, operates in O(n log n) time. (See this question: What is the complexity of this python sort method?)
Once our numbers are sorted, the maximum sums are trivial. The maximum for a number is simply that number added to the last element in the sorted list or, if the number is the maximum element, the second to last element. Rewriting your code we could obtain:
l3 = []
l1 = sorted(l1)
for i in range(len(l1)):
if i + 1 == len(l1):
l3.append(l1[i] + l1[i - 1])
else:
l3.append(l1[i] + l1[-1])
print(l3)
I hope this helps. Let me know if I've misunderstood your question.

Index going out of range in bisect_left in Python 3

I'm writing this piece of code, in which I've used bisect_left function from the bisect module which is a first-party module of Python. I'm using it with two parameters only i.e. sorted_list and target(the one for which I have to find the suitable index value).
The issue is: If my target is greater than the sum of lowest value and highest value, the function is returning the index = len(sorted_li), due to which I'm getting index error. I can use try and except but more than that I'm curious to know why it is behaving like so.
Following is my code:
from bisect import bisect_left
li = [10,15,3,6,10]
k = 19
def binary_search(sorted_list,target):
index = bisect_left(sorted_list,target)
print(index)
if sorted_list[index] == target:
return index
else:
return False
def function(sorted_li,k):
"""
Given a list of numbers and a number k, return whether any two numbers from the list add up to k.
For example, given [10, 15, 3, 7] and k of 17, return true since 10 + 7 is 17.
"""
print(sorted_li)
for i in range(len(sorted_li)):
print('Next iteration')
print(sorted_li[i])
target = k - sorted_li[i]
j = binary_search(sorted_li,target)
if j:
if j != i:
print(sorted_li[i])
print(sorted_li[j])
return True
else:
if j + 1 < len(sorted_li):
if sorted_li[j+1] == target:
print(sorted_li[i])
print(sorted_li[j+1])
return True
if j - 1 > 0:
if sorted_li[j-1] == target:
print(sorted_li[i])
print(sorted_li[j-1])
return True
return False
if __name__ == "__main__":
li.sort()
a = function(li,k)
print(a)
It's output is as follows:
but when I'm changing k to 18, the code is working fine, the output is as follows:
I've tried with various sets of numbers for the same. The output remains the same.
You're using bisect_left which has next purpose: it looking for the insertion point for x (which is target in your case) in a to maintain sorted order.
So for your case when you call first binary_search first time for 16 (19 - 3), it compare your number with items in li list using binary algorithm and then it returns position for insert 5, because in your list [3, 6, 10, 10, 15] insertion point should be after 15 which is correct.
If you open documentation you can find next method in searching sorted list
which does exactly you need, it searching for the exact item in list and return position of it if it exists either it raises ValueError because item not found.
def index(a, x):
'Locate the leftmost value exactly equal to x'
i = bisect_left(a, x)
if i != len(a) and a[i] == x:
return i
raise ValueError

Prime factorization of a number

I'm trying to write a program to find all the prime factors of a given number, and tried the following:
def factors(nr):
i = 2
factors = []
while i<nr:
if (nr%i)==0:
factors.append(i)
nr = nr/i
else:
i = i+1
return factors
My idea is the following. Start with i = 2, while i < the number, check if the module of the number and i = 0. If this is the case, add i to a list, and run the algorithm again, but now with the new number. However, my algorithm doesn't work. Any idea why?
I know that several right answers are posted on the site, but I would like to know why my program is incorrect.
Update
So if I let the programm run for example:
factors(38), yields [2].
factors(25), yields [5].
So it stops after it has added one number to the list.
The simplest change you can make to fix your problem is to change your while loop condition:
def factors(nr):
i = 2
factors = []
while i <= nr:
if (nr % i) == 0:
factors.append(i)
nr = nr / i
else:
i = i + 1
return factors
print factors(8)
print factors(9)
print factors(10)
Output
[2, 2, 2]
[3, 3]
[2, 5]
def ba(n):
pfa=[]
y=n
for i in range(n):
if (i!=0 and i!=1):
while (y%i==0):
pfa.append(i)
y=y/i
print(pfa)

use a while loop

I am working on some homework for a class and the assignment is in codingbat.com. The problem is as follows:
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.
So far, I have this:
def sum13(nums):
sum = 0
i = 0
while i in range(len(nums)):
if i == 13:
i += 2
else:
i += 1
return sum
Also, I had this code which works better:
def sum13(nums):
sum = 0
for i in range(len(nums)):
if nums[i] != 13:
sum += nums[i]
return sum
I know I am supposed to use a while loop, but I just don't get this.
Looks like you're almost there; you just need to actually add the value of nums[i] to sum in the appropriate place. Also reconsider the indentation of your return sum line.
It is more usual to use the for loops without using the range(). That kind of loop (in Python) is usually used for looping through the element values, not through the index values. When you need the index, use the enumerate() function to get both the index and the element value, like this (but you do not need it in the case):
...
for i, e in enumerate(nums):
do something with index and/or the element
...
The problem is not related to index values. This way, the for / while solutions differ only in accessing the elements of the array. You need to use indexing with the while but you do not need indexing with the for.
The problem with your while approach also is that you cannot simply skip the index after the value 13 because the next element can also contain 13. You need to store the value of the previous element and to use it for decision whether the current value should be added to the sum or not. It will be the same in both for and while solutions. Something like this:
last = 0 # init; whatever value different from 13
sum = 0
the chosen kind of loop:
e ... # current element from nums
if e != 13 bool_operator_here last != 13: # think about what boolean operator is
add the element to the sum
remember e as the last element for the next loop
sum contains the result
[Edited later] OK, you gave up. Here is the code that solves the problem:
def sumNot13for(nums):
last = 0 # init; whatever value different from 13
sum = 0
for e in nums:
if e != 13 and last != 13:
sum += e # add the element to the sum
last = e # remember e as the last element for the next loop
return sum
def sumNot13while(nums):
last = 0 # init; whatever value different from 13
sum = 0
i = 0 # lists/arrays use zero-based indexing
while i < len(nums):
e = nums[i] # get the current element
if e != 13 and last != 13:
sum += e # add the element to the sum
last = e # remember e as the last element for the next loop
i += 1 # the index must be incremented for the next loop
return sum
if __name__ == '__main__':
print(sumNot13for([2, 5, 7, 13, 15, 19]))
print(sumNot13while([2, 5, 7, 13, 15, 19]))
print(sumNot13for([2, 5, 7, 13, 13, 13, 13, 13, 13, 15, 19]))
print(sumNot13while([2, 5, 7, 13, 13, 13, 13, 13, 13, 15, 19]))

Resources