Question:
"Given a sequence of integers as an array, determine whether it is possible to obtain a strictly increasing sequence by removing no more than one element from the array.
Note: sequence a0, a1, ..., an is considered to be a strictly increasing if a0 < a1 < ... < an. Sequence containing only one element is also considered to be strictly increasing.
Examples:
For sequence = [1, 3, 2, 1], the output should be
solution(sequence) = false. There is no one element in this array that can be removed in order to get a strictly increasing sequence.
For sequence = [1, 3, 2], the output should be
solution(sequence) = true. You can remove 3 from the array to get the strictly increasing sequence [1, 2]. Alternately, you can remove 2 to get the strictly increasing sequence [1, 3]."
Here's my code:
def solution(sequence):
if len(sequence) == 1:
return True
else:
count = 0
for i in range(0,len(sequence) - 1):
if sequence[i] >= sequence[i + 1]:
count = count + 1
for i in range(0,len(sequence) - 2):
if sequence[i] >= sequence[i + 2]:
count = count + 1
return count <= 1
My code covers three cases:
Case 1: when the sequence is just one element long. I caught that in the first if statement.
Case 2: If there's more than one down-step – a case where the neighbour is less than the element being considered – then there is no way to adjust the sequence with just one removal, and so we get false (count > 1). I caught this in the first if statement.
Case 3: There are some cases, however, where there is only one down-step but there is still no way to remove just one element. This happens when the second element along is also less than the element being considered. For example, with [1,4,3,2] even if you removed the 3, you would still get a downstep. Now I covered this case by doing a second check, which checked whether the element two along is less, and if it is, then we add to the count.
Case 4: There is a case my code doesn't cover, which seems to be the only one, and that is when an element's neighbour and the next element along are both smaller than the element under consideration, but we could solve the issue just by getting rid of the element under consideration. So, with [1,4,2,3] both 2 and 3 are smaller than 4, but if we just got rid of the 4, then we're good. This case can occur either when the problem element is the first in the sequence or not. I'm not sure how to capture this properly. I suppose you might add in a conditional which looked at whether i-2 is less than i+1, but this won't work when i indexes the first element and it's quite cumbersome. I'm not sure how to go sort this out.
I'm quite sure I've overcomplicated things and what really is needed is to step back and think of a less piece-meal solution, but I'm stuck. Could anyone help? Note that we don't have to actually obtain the strictly increasing sequence; we just have to see whether we could.
Here is an idea you can a look at (edited after comments)
def distinct(L: list[int]) -> bool:
return len(L) == len(set(L))
def almost_increasing(L: list[int]) -> bool:
# some trivial cases
if len(L) <= 2: return True
if L[1: ] == sorted(L[1: ]) and distinct(L[1: ]): return True
if L[:-1] == sorted(L[:-1]) and distinct(L[:-1]): return True
return any(
L[ :i] == sorted(L[ :i]) and distinct(L[ :i]) and
L[i+1:] == sorted(L[i+1:]) and distinct(L[i+1:]) and
L[i-1 ] < L[i+1]
for i in range(1, len(L)-1)
)
And here is a nice way you can test it with hypothesis and pytest:
#given(L=st.lists(st.integers(), min_size=2, max_size=6))
def test_almost_increasing(L: list[int]):
expected = False
for i in range(len(L)):
Lcopy = L.copy()
del Lcopy[i]
expected |= (Lcopy == sorted(Lcopy) and distinct(Lcopy))
received = almost_increasing(L)
assert received == expected
Let's split the input into at most two increasing subsequences. If this is not possible, return false.
If there's only one sequence, return true.
If the length of either sequence is 1, the answer is true - we simply remove this element.
Otherwise we can join two sequences by either removing the last item from the first sequence, or the first item of the second one. That is,
if a[j-2] < a[j] -> ok, remove a[j - 1]
if a[j-1] < a[j + 1] -> ok, remove a[j]
where j is the index where the second sequence starts.
Code:
def check(a):
j = 0
for i in range(1, len(a)):
if a[i - 1] >= a[i]:
if j > 0:
return None
j = i
if j == 0:
return a
if j == 1:
return a[1:]
if j == len(a) - 1:
return a[:-1]
if a[j - 2] < a[j]:
return a[:(j - 1)] + a[j:]
if a[j - 1] < a[j + 1]:
return a[:j] + a[(j + 1):]
return None
assert check([2, 4, 6, 8]) == [2, 4, 6, 8], 'j == 0'
assert check([9, 4, 6, 8]) == [4, 6, 8], 'j == 1'
assert check([4, 6, 8, 1]) == [4, 6, 8], 'j == len-1'
assert check([2, 4, 9, 6, 8]) == [2, 4, 6, 8], 'j-2 < j'
assert check([2, 4, 1, 6, 8]) == [2, 4, 6, 8], 'j-1 < j+1'
assert check([2, 2, 2, 2]) is None, 'early return'
assert check([2, 8, 9, 6, 1]) is None, 'early return'
assert check([2, 4, 9, 3, 5]) is None, 'last return'
assert check([2]) == [2]
Try this
def solution(sequence):
n = len(sequence)
for i in range(n):
count = 0
trail = sequence[:]
del trail[i]
m = len(trail)
for j in range(m-1):
if trail[j] >= trail[j+1]:
count += 1
if count == 0:
return True
return False
This is not efficient nor optimized but it does the work.
Try this:
def is_solution(list_to_check):
if len(list_to_check) == 1:
return True
for i in range(1, len(list_to_check)):
if list_to_check[i] <= list_to_check[i - 1]:
new_list = list_to_check[:i - 1] + list_to_check[i:]
if (list_to_check[i] > list_to_check[i - 2]
and new_list == sorted(new_list)):
return True
elif (i == len(list_to_check) - 1
and list_to_check[:-1] == sorted(list_to_check[:-1])):
return True
return False
if __name__ == '__main__':
list_to_check = [1, 2, 1]
print(is_solution(list_to_check))
def solution(sequence):
"""determine strict increase"""
sequence_length = len(sequence)
if (
sequence_length == 0
or not len(set(sequence)) + 1 >= sequence_length
):
return False
return True
Related
I am trying to figure out if an input list is a strictly increasing list. Moreover, If removing only one element from the list results in a strictly increasing list, we still consider the list true. Here is my code. It seems to have an index error, but I do not understand why.
def almostIncreasingSequence(sequence):
n=len(sequence)
count=0
if n<=2:
return True
for i in range (n-1):
#test if the i-th element is bigger or equal to the elements after it. If it is, remove that element, and add one to count
for j in range (i+1,n):
if sequence[i]>=sequence[j]:
sequence.pop(i)
count+=1
#if there is more than one element that has to be taken out, it's false
if count>1:
return False
return True
Alright, so it turns out this problem is not that easy.
If you want an efficient solution, I think your best bet may be an algorithm similar to the longest increasing subsequence problem.
But here, we don't care about the actual longest increasing subsequence - we just need it's length. Also, we can short-circuit when maintaining our ordered list if we have had to perform n insertions already (where n is our restriction on the number of "out of order" elements).
This also generalizes very well to the n element "almost increasing" case, and in the worst case performs n-1 binary searches on lists of size M-n-1 to M, where M is the size of the list.
import bisect
def almost_increasing(li, n=1):
if len(li) < 2:
return True
ordered_li = [li[0]]
violator_count = 0
for ele in li[1:]:
if ele < ordered_li[0]:
violator_count += 1
ordered_li[0] = ele
elif ele > ordered_li[-1]:
ordered_li.append(ele)
else:
violator_count += 1
insertion_pos = bisect.bisect_right(ordered_li, ele)
ordered_li[insertion_pos] = ele
if violator_count > n: return False
return True
The idea behind this algorithm is as follows:
We move through the list, and maintain an ordered subsequence of our list all the while.
When we reach a new element
if that element cannot be appended onto our ordered subsequence, it is a "violator" of the increasing property. We subsequently insert it into the ordered subsequence in the correct position, using bisect for binary search.
otherwise, we just append it to our ordered subsequence and continue on.
At the end of each iteration, if we have too many violators already we can short-circuit out. Otherwise, after the loop is done we are guaranteed to have an increasing subsequence that has length within n of the length of our original list.
Demo
>>> almost_increasing([5, 1, 2, 3, 4])
True
>>> almost_increasing([1, 2, 5, 2, 15, 0, 176])
False
>>> almost_increasing([1, 2, 5, 2, 15, 0, 176], 2)
True
def almost_increasing_sequence(sequence):
if len(sequence) < 3:
return True
a, b, *sequence = sequence
skipped = 0
for c in sequence:
if a < b < c: # XXX
a, b = b, c
continue
elif b < c: # !XX
a, b = b, c
elif a < c: # X!X
a, b = a, c
skipped += 1
if skipped == 2:
return False
return a < b
if __name__ == '__main__':
assert almost_increasing_sequence([]) is True
assert almost_increasing_sequence([1]) is True
assert almost_increasing_sequence([1, 2]) is True
assert almost_increasing_sequence([1, 2, 3]) is True
assert almost_increasing_sequence([3, 1, 2]) is True
assert almost_increasing_sequence([1, 2, 3, 0, 4, 5, 6]) is True
assert almost_increasing_sequence([1, 2, 3, 0]) is True
assert almost_increasing_sequence([1, 2, 0, 3]) is True
assert almost_increasing_sequence([10, 1, 2, 3, 4, 5]) is True
assert almost_increasing_sequence([1, 2, 10, 3, 4]) is True
assert almost_increasing_sequence([1, 2, 3, 12, 4, 5]) is True
assert almost_increasing_sequence([3, 2, 1]) is False
assert almost_increasing_sequence([1, 2, 0, -1]) is False
assert almost_increasing_sequence([5, 6, 1, 2]) is False
assert almost_increasing_sequence([1, 2, 3, 0, -1]) is False
assert almost_increasing_sequence([10, 11, 12, 2, 3, 4, 5]) is False
If you've written for i in range(len(some_list)) in Python, you've probably done the wrong thing. This is indeed why this is failing. n is the length of the sequence as it stands before any processing, but that length can change as you pop items from the list.
Better instead is to compare each mark which indices need to be removed and do them all at once, or better yet -- don't remove them at all!! It's a side-effect that's not well-explained.
You can build this by building a list of all sequences that might be strictly increasing using itertools.combinations, comparing each pair with itertools's pairwise recipe, then short-circuiting as long as at least one is.
import itertools
def pairwise(iterable):
(a, b) = itertools.tee(iterable)
next(b, None) # advance b
return zip(a, b)
def almostIncreasingSequence(sequence):
if not sequence:
return True
# in case of empty list
combos = itertools.combinations(sequence, len(sequence)-1)
# combos is each ordered combination that's missing one element
# it is processed as an iterator, so will do no extra work if we can
# exit early.
def strictly_increasing(cs):
return all(a < b for (a, b) in pairwise(cs))
return any(strictly_increasing(c) for c in combos)
The only thing you need to do is walk the list, counting the number of times sequence[i] > sequence[i+1]. If it happens at most once, your list is almost monotonically increasing.
def almostIncreasingSequence(sequence):
count = 0
for i in range(0, len(sequence) - 1):
if sequence[i] > sequence[i+1]:
count += 1
return count < 2
You can also avoid counting, since the number of exceptions is small. Just return False as soon as you find the second exception, as tracked by the value of a Boolean variable initialized to True.
def almostIncreasingSequence(sequence):
increasing = True
for i in range(0, len(sequence) - 1):
if sequence[i] > sequence[i+1]:
if increasing:
increasing = False
else:
return False
return True
I have a list:
day_list = [1,2,3,4,5,6]
I want to write a function which will take any number (no) from this list and return the next and previous element.
So, for example, if I pass 2, I should get [1,3] (1 being the previous and 3 the next elements).
But there is a catch:
If no = 1 the previous element should be 6 and similarly for no = 6 next should be 1
I would like something like:
def getPrevNext(no):
....
....
return [prev,next]
result = getPrevNext(2) # result should be [1,3]
result2 = getPrevNext(5) # result2 should be [4,5]
result3 = getPrevNext(1) # result3 should be [6,2]
result4 = getPrevNext(6) # result3 should be [5,1]
I tried:
def dummy(no):
if no == 1:
prev_no = 6
next_no = 2
elif no == 6:
prev_no = 5
next_no = 1
else:
prev_no = no - 1
next_no = no + 1
return [prev_no,next_no]
But this seems like a very naive and basic approach.. Is there a better way to do this?
FYI List of days not required, that was just for understanding the total no.
#juanpa.arrivillaga's answer covers the specifics of this question quite well (which is a question on how to use modulo arithmetic). However, in the general case of accessing the previous and next elements of any list, this is how I'd do it -
def getPrevNext(l, no):
i = l.index(no)
return [l[i - 1], l[(i + 1) % len(l)]]
days = list(range(1, 7))
getPrevNext(days, 2)
[1, 3]
getPrevNext(days, 1)
[6, 2]
getPrevNext(days, 6)
[5, 1]
The first expression l[i - 1] takes advantage python's ability to access elements using negative indices. The second expression, l[(i + 1) % len(l)], is a common circular list access idiom.
To return a tuple instead of a list, drop the enclosing square brackets in the return statement -
def getPrevNextAsTuple(l, no):
i = l.index(no)
return l[i - 1], l[(i + 1) % len(l)]
Note that this does not handle the possibility of no not being in the list. In that case, you'd use something like exception handling to catch any ValueErrors raised -
def getPrevNext(l, no):
try:
i = l.index(no)
except ValueError:
return None
return l[i - 1], l[(i + 1) % len(l)]
If you are working with "days" encoded 1-6, use the following:
>>> def prev_next_day(day):
... day -= 1
... prev = ((day - 1) % 6) + 1
... next_ = ((day + 1) % 6) + 1
... return [prev, next_]
...
>>> prev_next_day(2)
[1, 3]
>>> prev_next_day(5)
[4, 6]
>>> prev_next_day(1)
[6, 2]
>>> prev_next_day(6)
[5, 1]
You can just use slicing (and optionally, for elegance, mod) like so:
day_list = [1,2,3,4,5,6]
def get_prev_next(my_list, no):
try:
pointer = my_list.index(no)
except ValueError:
return []
else:
return [my_list[pointer-1], my_list[(pointer+1) % len(my_list)]]
Examples:
# first element
print(get_prev_next(day_list, 1)) # -> [6, 2]
# last element
print(get_prev_next(day_list, 6)) # -> [5, 1]
# any other
print(get_prev_next(day_list, 3)) # -> [2, 4]
# non-existent element
print(get_prev_next(day_list, 98)) # -> []
I would say the following works:
def getPrevNext(no):
day_list = [1, 2, 3, 4, 5, 6]
return([day_list[(no-2) % 6], day_list[(no) % 6]])
Depending on whether or not you also only accept 1 <= no <= 6, you could add a condition to this function like so:
def getPrevNext(no):
day_list = [1, 2, 3, 4, 5, 6]
return([day_list[(no-2) % 6], day_list[(no) % 6]] if 1 <= no <= 6 else 0)
def getem(lst,element):
index=lst.index(element)
try:
if index!=0 and index!=(len(lst)-1):
return [lst[index-1],lst[index+1]]
else:
if index==0:
return [lst[len(lst)-1],lst[index+1]]
else:
return [lst[index-1],lst[0]]
except Exception as e:
print("element does not exists")
try this function or you can use circular linked-list in this function i have mimicked that behaviour
Are you sure you need a list of days?
def f(n):
assert 0<n<7, "numbers should be between 1 and 6"
return n-1 if n!=1 else 6, n+1 if n!=6 else 1
f(1)
#result (6, 2)
I think the trick you mentioned is famous as circular linked list. Please correct me if I'm wrong about it.
day_list = [1,2,3,4,5,6]
def get_prev_next(elem):
if elem not in day_list:
return False # element not found
index = day_list.index(elem)
return day_list[i - 1], l[(i + 1) % len(day_list)]
For the previous, you can take advantage of the fact that when indexing a list, negative indexes mean "negative starting on the last element". It helps me thinking about the [-1] as "overflowing", which means going to the first element (index 0) and start counting from the last element.
For the next, you can use the modulo (%) operator, to make sure you keep your indexes between 0 and len(days_list). This is only critical in the case the index of the number you want to calculate the next is the last element of the list (index 5, value 6 in the day_list). Just adding +1 to the index 5 would put you in 6, which is not a valid index. But then you get the module of 6 % 6 and "becomes" 0.
day_list = [1, 2, 3, 4, 5, 6]
def find(thing):
position = day_list.index(thing)
prev_pos = day_list[position - 1]
next_pos = day_list[(position + 1) % len(day_list)]
return prev_pos, next_pos
if __name__ == "__main__":
for i in range(1, 7):
print("prev_next %s: %s" % (i, find(i)))
Outputs:
prev_next 1: (6, 2)
prev_next 2: (1, 3)
prev_next 3: (2, 4)
prev_next 4: (3, 5)
prev_next 5: (4, 6)
prev_next 6: (5, 1)
I need to write a function that given an input list, all adjacent elements in the list are swapped with each other. If the length of the list is odd, then the last element just stays put. I wrote the function iteratively, like so:
>>>def swap(nums):
for i in range(0,len(nums),2):
try:
nums[i],nums[i+1] = nums[i+1], nums[i]
except:
pass
return nums
>>>swap([1,2,3,4,5])
[2, 1, 4, 3, 5]
I used the exact same logic as before for the recursive version:
def swap(nums, c=0):
try:
nums[c], nums[c+1] = nums[c+1], nums[c]
return swap(nums, c+2)
except:
return nums
Although both work, I feel like I'm cheating a bit with these try/except blocks, and I won't become a better programmer by using them all the time. Can anybody give me suggestions on how to approach these problems without relying on try/except blocks?
For the iterative version, you can use range(0, len(nums)-1, 2) to keep looping till the item before last as the following:
def swap(nums):
for i in range(0, len(nums) - 1, 2):
nums[i], nums[i + 1] = nums[i + 1], nums[i]
return nums
And in the recursive version, you can check if c >= len(nums) - 1 to check if you have reached last item:
def swap(nums, c=0):
if c >= len(nums) - 1:
return nums
nums[c], nums[c+1] = nums[c+1], nums[c]
return swap(nums, c+2)
this way you can avoid try/except because you will not raise index out of range exception. And for reference, if you want to use try/except it is better to use except IndexError: instead of general except:.
Input:
print(swap([1, 2, 3, 4, 5, 6]))
print(swap([1, 2, 3, 4, 5]))
Output:
[2, 1, 4, 3, 6, 5]
[2, 1, 4, 3, 5]
EDIT:
As #agtoever mentioned, you can modify the recursive version to be:
def swap(nums):
if len(nums) < 2:
return nums
return [nums[1], nums[0]] + swap(nums[2:])
Iteratively:
for i in range(0, len(nums) - (len(nums) % 2), 2): #Skips the last element in an odd length list
nums[c], nums[c+1] = nums[c + 1], nums[c]
Recursively:
import math
def swap(nums):
if len(nums) == 2:
return [nums[1], nums[0]]
if len(nums) == 1:
return nums[0]
half = int(math.ceil(len(nums) / 2.0))
return swap(nums[:half]) + swap(nums[half:])
def swap(li):
if len(li) < 2:
return li
else:
return [li[1], li[0]] + swap(li[2:])
or
def swap(li):
def swap_iter(inlist, out):
if len(inlist) < 2:
return out + inlist
else:
return swap_iter(inlist[2:], out + [inlist[1], inlist[0]])
return swap_iter(li, [])
I need to write a recursive function sums_to(nums, k) that takes a list of integers and returns True if the sum of all the elements in the list is equal to k and returns False otherwise.
I can't use sum function in any form, nor sum the list and then at the end check whether it equals k. In addition, I have to write sums_to as a single recursive function.
So far I have this:
def sums_to(nums, k):
if nums == []:
if k == 0
return True
return 0
else:
return nums[0] + sums_to(nums[1:], k) == k
Your approach doesn't seem correct, in particular the recursive step isn't right - you have to decrease the expected k value until we reach the end of the list (and only test k at this point!), instead of adding/comparing at each point of the list. Also, the base case is missing something - what happens if k is not zero? you should return False, not 0. Better try this:
def sums_to(nums, k):
if nums == []:
return k == 0
else:
return sums_to(nums[1:], k - nums[0])
It works as expected:
sums_to([1, 2, 3, 4, 5], 14)
=> False
sums_to([1, 2, 3, 4, 5], 16)
=> False
sums_to([1, 2, 3, 4, 5], 15)
=> True
When I was struggling to do Problem 14 in Project Euler, I discovered that I could use a thing called memoization to speed up my process (I let it run for a good 15 minutes, and it still hadn't returned an answer). The thing is, how do I implement it? I've tried to, but I get a keyerror(the value being returned is invalid). This bugs me because I am positive I can apply memoization to this and get this faster.
lookup = {}
def countTerms(n):
arg = n
count = 1
while n is not 1:
count += 1
if not n%2:
n /= 2
else:
n = (n*3 + 1)
if n not in lookup:
lookup[n] = count
return lookup[n], arg
print max(countTerms(i) for i in range(500001, 1000000, 2))
Thanks.
There is also a nice recursive way to do this, which probably will be slower than poorsod's solution, but it is more similar to your initial code, so it may be easier for you to understand.
lookup = {}
def countTerms(n):
if n not in lookup:
if n == 1:
lookup[n] = 1
elif not n % 2:
lookup[n] = countTerms(n / 2)[0] + 1
else:
lookup[n] = countTerms(n*3 + 1)[0] + 1
return lookup[n], n
print max(countTerms(i) for i in range(500001, 1000000, 2))
The point of memoising, for the Collatz sequence, is to avoid calculating parts of the list that you've already done. The remainder of a sequence is fully determined by the current value. So we want to check the table as often as possible, and bail out of the rest of the calculation as soon as we can.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
l += table[n]
break
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({n: l[i:] for i, n in enumerate(l) if n not in table})
return l
Is it working? Let's spy on it to make sure the memoised elements are being used:
class NoisyDict(dict):
def __getitem__(self, item):
print("getting", item)
return dict.__getitem__(self, item)
def collatz_sequence(start, table=NoisyDict()):
# etc
In [26]: collatz_sequence(5)
Out[26]: [5, 16, 8, 4, 2, 1]
In [27]: collatz_sequence(5)
getting 5
Out[27]: [5, 16, 8, 4, 2, 1]
In [28]: collatz_sequence(32)
getting 16
Out[28]: [32, 16, 8, 4, 2, 1]
In [29]: collatz_sequence.__defaults__[0]
Out[29]:
{1: [1],
2: [2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
16: [16, 8, 4, 2, 1],
32: [32, 16, 8, 4, 2, 1]}
Edit: I knew it could be optimised! The secret is that there are two places in the function (the two return points) that we know l and table share no elements. While previously I avoided calling table.update with elements already in table by testing them, this version of the function instead exploits our knowledge of the control flow, saving lots of time.
[collatz_sequence(x) for x in range(500001, 1000000)] now times around 2 seconds on my computer, while a similar expression with #welter's version clocks in 400ms. I think this is because the functions don't actually compute the same thing - my version generates the whole sequence, while #welter's just finds its length. So I don't think I can get my implementation down to the same speed.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
table.update({x: l[i:] for i, x in enumerate(l)})
return l + table[n]
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({x: l[i:] for i, x in enumerate(l)})
return l
PS - spot the bug!
This is my solution to PE14:
memo = {1:1}
def get_collatz(n):
if n in memo : return memo[n]
if n % 2 == 0:
terms = get_collatz(n/2) + 1
else:
terms = get_collatz(3*n + 1) + 1
memo[n] = terms
return terms
compare = 0
for x in xrange(1, 999999):
if x not in memo:
ctz = get_collatz(x)
if ctz > compare:
compare = ctz
culprit = x
print culprit