longest decreasing sublist inside a given list - python

I wanted to find the longest decreasing sub sequence inside a given list for example L = [1, 2, 1, 2, 1, 2, 1, 2, 1], the result should be [2,1] however I cant seem to produce that result. Can someone tell me why it doesn't work ? The output is something [0,2,1,2,1,2,1,2,1] Nevermind the first zero but the result should produce [2,1].
Here's a code I tried
L = [1, 2, 1, 2, 1, 2, 1, 2, 1]
current = [0]
smallest = []
for i in range(len(L)):
if i < (len(L)-1):
if L[i] >= L[i+1]:
current.append(L[i])
else :
if L[i] < current[-1]:
current.append(L[i])
elif i>= (len(L)-1):
if L[-1]<L[i-1]:
current.append(L[i])
else:
current = [i]
if len(current) > len(smallest):
smallest = current
Result : [0,2,1,2,1,2,1,2,1]
Desired Result : [2,1]

There are so many ways to solve this. In Py3 - using itertools.accumulate for dynamic programming:
>>> import operator as op
>>> import itertools as it
>>> L = [1, 2, 1, 2, 1, 2, 1, 2, 1]
>>> dyn = it.accumulate(it.chain([0], zip(L, L[1:])), lambda x, y: (x+1)*(y[0]>y[1]))
>>> i, l = max(enumerate(dyn), key=op.itemgetter(1))
>>> L[i-l:i+1]
[2, 1]

when you say current = [0], it actually adds 0 to the list, maybe you want current = [L[0]].
See this:
def longest_decreasing_sublist(a):
lds, current = [], [a[0]]
for val in a[1:]:
if val < current[-1]: current.append(val)
else:
lds = current[:] if len(current) > len(lds) else lds
current = [val]
lds = current[:] if len(current) > len(lds) else lds
return lds
L = [1, 2, 1, 2, 1, 2, 1, 2, 1]
print (longest_decreasing_sublist(L))
# [2, 1]

You would be better off engineering your code for maintainability and readability up front, so that you can easily test components of your software individually rather than trying to solve the entire thing at once. This is known as functional decomposition.
Think about what you need at the topmost level.
First, you need a way to get the longest decreasing subset at a given point in the array, so that you can intelligently compare the various sequences.
You can do that with code such as:
def getNextSequence(myList, index):
# Beyond list end, return empty list.
if index > len(myList):
return []
# Construct initial list, keep adding until either
# an increase is found or no elements left.
sequence = [ myList[index] ]
addIdx = index + 1
while addIdx < len(myList) and myList[addIdx] <= myList[addIdx - 1]:
sequence.append(myList[addIdx])
addIdx += 1
# And there you have it, the sequence at a given point.
return sequence
Second, you need to be able to store the current longest one and check the lengths to see whether the current one is greater than the longest to date.
That breaks down to something like:
def getLongestSequence(myList):
# Initially no sequence, even a sequence of one
# will beat this.
longestSequence = []
# This index is where we are checking. Keep checking
# until all possibilities exhausted.
checkIndex = 0
while checkIndex < len(myList):
# Get current sequence, save it if it's longer
# than the currently longest one.
currentSequence = getNextSequence(myList, index)
if len(currentSequence) > len(longestSequence):
longestSequence = currentSequence
# Adjust index to next checking point.
checkIndex += len(currentSequence)
# All done, just return the longest sequence.
return longestSequence
The important thing there (other than readability, of course) is the changing of the index. Once you've established a decreasing sequence, you never need to look anywhere inside it since any partial sequence within it will naturally be shorter than the whole.
In other words, if you have 8, 7, 6, 5, 9, a sequence starting at 7 cannot be (by definition) longer than the sequence starting at 8. Hence you can skip straight to the 9.

Related

how to check weather an element appear only once in a list in python? [duplicate]

This question already has an answer here:
Check for unique numbers and extract index
(1 answer)
Closed 8 months ago.
How can I write a function to compute the numbers that appear only once in a list like [2, 1, 2, 5, 2, 1, 1, 3]. If more than two numbers are founded, it should return the smallest number and if there is no number with the one-time appearance it should return "Not found".
This is the code that I have written:
def one_time(nums, n):
nums.sort()
if nums[0] != nums[1]:
print(nums[0], end = " ")
for i in range(1, n - 1):
if (nums[i] != nums[i + 1] and
nums[i] != nums[i - 1]):
print( nums[i])
if nums[n - 2] != nums[n - 1]:
print(nums[n - 1], end = " ")
if __name__ == "__main__":
nums = [2, 1, 2, 5, 2, 1, 1, 3]
n = len(nums)
one_time(nums, n)
collections.Counter is the way most people determine how many of each item are in a list. Once you have counts, you can filter by those numbers with only 1 (that the if v == 1 part), sort and take the next() value. next() offers the ability to provide a default if nothing is found:
from collections import Counter
l = [2, 1, 2, 5, 1, 1, 3]
next((k for k,v in sorted(Counter(l).items()) if v == 1), "Not Found")
# 3
# with no unique elements
l = [2,2,1,1]
next((k for k,v in sorted(Counter(l).items()) if v == 1), "Not Found")
# 'Not Found'
Use collections.Counter to count the list elements, and convert into a dictionary with keys = elements of the original list lst and values = their number of occurrences in that list.
Use list comprehension to select only the elements that occurred exactly once.
Select the minimum number from that list.
from collections import Counter
lst = [2, 1, 2, 5, 2, 1, 1, 3]
cnt = dict(Counter(lst))
print(cnt)
# {2: 3, 1: 3, 5: 1, 3: 1}
try:
smallest_once = min([x for x, c in cnt.items() if c == 1])
print(smallest_once)
except ValueError:
print("Not found")
# 3
If it were me, I'd use two sets: one to store the numbers that still might be singles, and one to store the numbers I've found before. As soon as a number is in the "seen before" list, I can remove it from the singles:
def one_time(nums):
single = set(nums)
multi = set()
for n in nums:
if n in multi and n in single:
single.remove(n)
multi.add(n)
return list(single)
if __name__ == "__main__":
nums = [2, 1, 2, 5, 2, 1, 1, 3]
print(min(one_time(nums)))
You can create a set using the list with a different name (e.g. myset = set(mylist))
Then loop through the set and use the built in
.count() function on each element in the set.
Make a variable called counter or something that makes sense and initialize it to zero.
For each iteration, if there is a number that only appears once and counter is equal to zero, save thr number in counter (or a different variable if 0 is a possible number in the list)
If there is another number that appears once, check it's value against the previously saved number.
I hope this helps, and I hope it makes sense, I'm quite tired right now. Good luck.

Find smallest repeated piece of a list

I've got some list with integers like:
l1 = [8,9,8,9,8,9,8],
l2 = [3,4,2,4,3]
My purpose to slice it into the smallest repeated piece. So:
output_l1 = [8,9]
output_l2 = [3,4,2,4]
Biggest problem that the sequences not fully finished every time. So not
'abcabcabc'
just
'abcabcab'.
def shortest_repeating_sequence(inp):
for i in range(1, len(inp)):
if all(inp[j] == inp[j % i] for j in range(i, len(inp))):
return inp[:i]
# inp doesn't have a repeating pattern if we got this far
return inp[:]
This code is O(n^2). The worst case is one element repeated a lot of times followed by something that breaks the pattern at the end, for example [1, 1, 1, 1, 1, 1, 1, 1, 1, 8].
You start with 1, and then iterate over the entire list checking that each inp[i] is equal to inp[i % 1]. Any number % 1 is equal to 0, so you're checking if each item in the input is equal to the first item in the input. If all items are equal to the first element then the repeating pattern is a list with just the first element so we return inp[:1].
If at some point you hit an element that isn't equal to the first element (all() stops as soon as it finds a False), you try with 2. So now you're checking if each element at an even index is equal to the first element (4 % 2 is 0) and if every odd index is equal to the second item (5 % 2 is 1). If you get all the way through this, the pattern is the first two elements so return inp[:2], otherwise try again with 3 and so on.
You could do range(1, len(inp)+1) and then the for loop will handle the case where inp doesn't contain a repeating pattern, but then you have to needlessly iterate over the entire inp at the end. And you'd still have to have to have return [] at the end to handle inp being the empty list.
I return a copy of the list (inp[:]) instead of the list to have consistent behavior. If I returned the original list with return inp and someone called that function on a list that didn't have a repeating pattern (ie their repeating pattern is the original list) and then did something with the repeating pattern, it would modify their original list as well.
shortest_repeating_sequence([4, 2, 7, 4, 6]) # no pattern
[4, 2, 7, 4, 6]
shortest_repeating_sequence([2, 3, 1, 2, 3]) # pattern doesn't repeat fully
[2, 3, 1]
shortest_repeating_sequence([2, 3, 1, 2]) # pattern doesn't repeat fully
[2, 3, 1]
shortest_repeating_sequence([8, 9, 8, 9, 8, 9, 8])
[8, 9]
shortest_repeating_sequence([1, 1, 1, 1, 1])
[1]
shortest_repeating_sequence([])
[]
The following code is a rework of your solution that addresses some issues:
Your solution as posted doesn't handle your own 'abcabcab' example.
Your solution keeps processing even after it's found a valid result, and then filters through both the valid and non-valid results. Instead, once a valid result is found, we process and return it. Additional valid results, and non-valid results, are simply ignored.
#Boris' issue regarding returning the input if there is no repeating pattern.
CODE
def repeated_piece(target):
target = list(target)
length = len(target)
for final in range(1, length):
result = []
while len(result) < length:
for i in target[:final]:
result.append(i)
if result[:length] == target:
return result[:final]
return target
l1 = [8, 9, 8, 9, 8, 9, 8]
l2 = [3, 4, 2, 4, 3]
l3 = 'abcabcab'
l4 = [1, 2, 3]
print(*repeated_piece(l1), sep='')
print(*repeated_piece(l2), sep='')
print(*repeated_piece(l3), sep='')
print(*repeated_piece(l4), sep='')
OUTPUT
% python3 test.py
89
3424
abc
123
%
You can still use:
print(''.join(map(str, repeated_piece(l1))))
if you're uncomfortable with the simpler Python 3 idiom:
print(*repeated_piece(l1), sep='')
SOLUTION
target = [8,9,8,9,8,9,8]
length = len(target)
result = []
results = [] * length
for j in range(1, length):
result = []
while len(result) < length:
for i in target[:j]:
result.append(i)
results.append(result)
final = []
for i in range(0, len(results)):
if results[i][:length] == target:
final.append(1)
else:
final.append(0)
if 1 in final:
solution = results[final.index(1)][:final.index(1)+1]
else:
solution = target
int(''.join(map(str, solution)))
'result: [8, 9]'.
Simple Solution:
def get_unique_items_list(some_list):
new_list = []
for i in range(len(some_list)):
if not some_list[i] in new_list:
new_list.append(some_list[i])
return new_list
l1 = [8,9,8,9,8,9,8]
l2 = [3,4,2,4,3]
print(get_unique_items_list(l1))
print(get_unique_items_list(l2))
#### Output ####
# [8, 9]
# [3, 4, 2]

Python - finding next and previous values in list with different criteria

I'm looking for a way to iterate through a list of numbers in Python to find the index of a particular element and then find the nearest elements to it that meet certain criteria. I can't seem to find any built in functions that will hold my place in a list so that I can find previous and next items with different search criteria. Does anything like this exist in Python?
I have a long list of numbers in which I'm trying to find a particular recurring pattern.
For example:
L = [1, 1, 3, 5, 7, 5, 1, 2, 1, 1, 1, 8, 9, 1, 1, 1]
Say I want to find the peaks by looking for the index of the first number in the list >4, and then the indices of the nearest numbers <2 on either side. Then I want to find the next peak and do the same thing. (The actual pattern is more complicated than this.)
So the eventual output I'm looking for in this example is 1:6, 10:13.
I'm using this to find the first value:
a = next(i for i, v in enumerate(L) if v > 4)
Or this to find all values > 4 to later group them:
indexes = [i for i, v in enumerate(L) if v > 4]
I've tried next, iter, generators, many kinds of for loops and more without success. I've looked at islice also, but it seems like overkill to slice the list in two for every index found and then do forward and reverse searches on the two pieces. There must be a less convoluted way?
Any help would be much appreciated.
I would use a generator function and track the indices matching your conditions as you iterate over the input:
L = [1, 1, 3, 5, 7, 5, 1, 2, 1, 1, 1, 8, 9, 1, 1, 1]
def peak_groups(l):
start_i = 0
peak_i = None
for i,x in enumerate(l):
if peak_i is None:
# look for start of peak group, or peak itself
if x < 2:
start_i = i
elif x > 6:
peak_i = i
else:
# look for end of peak group
if x < 2:
yield (start_i, peak_i, i)
start_i = i
peak_i = None
# finally check edge condition if we reached the end of the list
if peak_i is not None:
yield (start_i, peak_i, i)
for group in peak_groups(L):
print group
Results in:
(1, 4, 6)
(10, 11, 13)
The nice thing is you're only iterating over the input once. Though it might not be so simple with your real world grouping conditions.
You'll have to think about what should happen if multiple "peak groups" overlap, and this currently doesn't find the greatest peak in the group, but it should be a starting point.
This finds the peaks as you stated, but requires the initial index of the list to determine where to search:
def findpeaks(lst, index, v1, v2):
l = lst[index:]
ele = next(i for (i, v) in enumerate(l) if v > v1)
idx = ele - next(i for (i, v) in enumerate(l[:ele+1][::-1]) if v < v2)
jdx = ele + next(i for (i, v) in enumerate(l[ele:]) if v < v2)
# Returns a tuple containing:
#
# the index of the element > v1
# the index of the element < v2 (found before the element > v1),
# the index of the element < v2 (found after the element > v1).
return (ele + index, idx + index, jdx + index)
This works by:
Finding the element matching the first value's criterion (> 4, in your example)
Finding the element before this index that matches that criterion of the second value (< 2). It does this by creating a slice of the list from where it finds the index from part 1, and reversing it. The index you find then has to be subtracted from where the index of part 1 is.
Search forward by creating a slice of the original list, and looking from there.
The end result has to take into account the starting index, so add that to the results. And, that's it.
For example:
L = [1, 1, 3, 5, 7, 5, 1, 2, 1, 1, 1, 8, 9, 1, 1, 1]
print findpeaks(L, 0, 4, 2) # prints (3, 1, 6)
print findpeaks(L, 6, 4, 2) # prints (11, 10, 13)
The next obvious step is to find all the elements that meet this criterion. One suggestion would be to make this recursive - but you can do that on your own.

Python generator with external break condition

I need to iterate over ascending sequences x of n (= 5, f.i.) integers, finding all sequences for which a function f(*x) returns True.
Assume that if f_n(*y) is False for a particular y, then f_n(*z) id False for any z with z_i >= y_i. So f_n is monotonic in all its arguments.
This kind of generator function could be used in the following way to determine all ascending sequences of integers that have a sum of squares < 100
for sequence in generate_sequences(5):
if sum_squares_is_at_least(sequence, 100):
# some code to trigger the breaking of the generator loop
else:
print sequence
Clarification:
The problem here is that we need to iterate of n elements individually. Initially, we iterate [1,1,1,1,1] to [1,1,1,1,x], and then we have to continue with [1,1,1,2,2] to [1,1,1,2,y], eventually ending with [a,b,c,d,e]. It seems that the generator should look something like this, but needs some code to break out of the for and/or while loops if necessary (determined externally):
def generate_sequences(length, minimum = 1):
if length == []:
yield []
else:
element = minimum
while True:
for sequence in generate_sequences(length - 1, element):
yield element + [sequence]
element += 1
Example:
For n = 3, and sum of squares no larger than 20, the following sequences would be generated:
[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 1, 4], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3]
Note that in the general case, I cannot use the information that 4 is the upper bound for each element. This would also seriously impact the running time for larger examples.
Are you looking for itertools.takewhile?
>>> from itertools import takewhile
>>> def gen(): #infinite generator
... i=0
... while True:
... yield range(i,i+5)
... i = i+1
...
>>> [ x for x in takewhile( lambda x:sum(x)<20, gen() ) ]
[[0, 1, 2, 3, 4], [1, 2, 3, 4, 5]]
>>>
import itertools as it
it.takewhile(lambda x: sum_squares_is_at_least(x, 100), generate_sequences(5))
If you are now sure about the 5 in the generate_sequences, then just let it yield the numbers as long as it is called:
def generate_sequences():
i = 0 # or anything
while True:
yield [i, i] # or anything
i = i + 1 # or anything
Then use it this way:
it.takewhile(lambda x: sum_squares_is_at_least(x, 100), generate_sequences())
I would solve it with recursion by starting with a given list then appending another number (with logic to prevent going over sum of squares target)
def makegen(N): #make a generator with max sumSquares: N
def gen(l=[]): #empty list is valid with sum == 0
yield l
if l:
i = l[-1] #keep it sorted to only include combinations not permutations
else:
i = 1 #only first iteration
sumsquare = sum(x*x for x in l) #find out how much more we can add
while sumsquare + i*i < N: #increase the appended number until we exceed target
for x in gen(l+[i]): #recurse with appended list
yield x
i += 1
return gen
calling our generator generator (tee hee :D) in the following fashion allows us to have any maximum sum of squares we desire
for x in makegen(26)():
print x

Getting the indices of the X largest numbers in a list

Please no built-ins besides len() or range(). I'm studying for a final exam.
Here's an example of what I mean.
def find_numbers(x, lst):
lst = [3, 8, 1, 2, 0, 4, 8, 5]
find_numbers(3, lst) # this should return -> (1, 6, 7)
I tried this not fully....couldn't figure out the best way of going about it:
def find_K_highest(lst, k):
newlst = [0] * k
maxvalue = lst[0]
for i in range(len(lst)):
if lst[i] > maxvalue:
maxvalue = lst[i]
newlst[0] = i
Take the first 3 (x) numbers from the list. The minimum value for the maximum are these. In your case: 3, 8, 1. Their index is (0, 1, 2). Build pairs of them ((3,0), (8,1), (1,2)).
Now sort them by size of the maximum value: ((8,1), (3,0), (1,2)).
With this initial List, you can traverse the rest of the list recursively. Compare the smallest value (1, _) with the next element in the list (2, 3). If that is larger (it is), sort it into the list ((8,1), (3,0), (2,3)) and throw away the smallest.
In the beginning you have many changes in the top 3, but later on, they get rare. Of course you have to keep book about the last position (3, 4, 5, ...) too, when traversing.
An insertion sort for the top N elements should be pretty performant.
Here is a similar problem in Scala but without the need to report the indexes.
I dont know is it good to post a solution, but this seems to work:
def find_K_highest(lst, k):
# escape index error
if k>len(lst):
k=len(lst)
# the output array
idxs = [None]*k
to_watch = range(len(lst))
# do it k times
for i in range(k):
# guess that max value is at least at idx '0' of to_watch
to_del=0
idx = to_watch[to_del]
max_val = lst[idx]
# search through the list for bigger value and its index
for jj in range(len(to_watch)):
j=to_watch[jj]
val = lst[j]
# check that its bigger that previously finded max
if val > max_val:
idx = j
max_val = val
to_del=jj
# append it
idxs[i] = idx
del to_watch[to_del]
# return answer
return idxs
PS I tried to explain every line of code.
Can you use list methods? (e.g. append, sort, index?). If so, this should work (I think...)
def find_numbers(n,lst):
ll=lst[:]
ll.sort()
biggest=ll[-n:]
idx=[lst.index(i) for i in biggest] #This has the indices already, but we could have trouble if one of the numbers appeared twice
idx.sort()
#check for duplicates. Duplicates will always be next to each other since we sorted.
for i in range(1,len(idx)):
if(idx[i-1]==idx[i]):
idx[i]=idx[i]+lst[idx[i]+1:].index(lst[idx[i]]) #found a duplicate, chop up the input list and find the new index of that number
idx.sort()
return idx
lst = [3, 8, 1, 2, 0, 4, 8, 5]
print find_numbers(3, lst)
Dude. You have two ways you can go with this.
First way is to be clever. Phyc your teacher out. What she is looking for is recursion. You can write this with NO recursion and NO built in functions or methods:
#!/usr/bin/python
lst = [3, 8, 1, 2, 0, 4, 8, 5]
minval=-2**64
largest=[]
def enum(lst):
for i in range(len(lst)):
yield i,lst[i]
for x in range(3):
m=minval
m_index=None
for i,j in enum(lst):
if j>m:
m=j
m_index=i
if m_index:
largest=largest+[m_index]
lst[m_index]=minval
print largest
This works. It is clever. Take that teacher!!! BUT, you will get a C or lower...
OR -- you can be the teacher's pet. Write it the way she wants. You will need a recursive max of a list. The rest is easy!
def max_of_l(l):
if len(l) <= 1:
if not l:
raise ValueError("Max() arg is an empty sequence")
else:
return l[0]
else:
m = max_of_l(l[1:])
return m if m > l[0] else l[0]
print max_of_l([3, 8, 1, 2, 0, 4, 8, 5])

Categories