Obtaining non decreasing subsequence from a python list efficiently - python

I wrote a code to obtain non decreasing sub-sequence from a python list
lis = [3, 6, 3, 8, 6, 4, 2, 9, 5]
ind = 0
newlis = []
while ind < len(lis):
minele = min(lis[ind:])
newlis.append(minele)
ind = lis.index(minele) + 1
print(newlis)
Though it seems to be working fine with the testcases I tried, is there a more efficient way to do this, because the worst cast time complexity of this code is O(n^2) for the case the list is already sorted, assuming the built-in min method uses linear search.
To be more precise, I want longest possible non decreasing sub-list and the sub-list should start with the minimum element of the list. And by sub-list, I mean that the elements need not be in a contiguous stretch in the given original list (lis).

I'm almost convinced it can run in linear time. You just need to keep trying to build a longest sequence during one scan and either store them all as I did or keep currently longest one.
lis = [9, 1, 3, 8, 9, 6, 9, 8, 2, 3, 4, 5]
newlis = [[]]
minele = min(lis)
ind = lis.index(minele)
currmin = minele
seq = 0
longest = 0
longest_idx = None
while ind < len(lis):
if lis[ind] >= currmin:
newlis[seq].append(lis[ind])
currmin = lis[ind]
ind += 1
else:
if len(newlis[seq]) > longest:
longest = len(newlis[seq])
longest_idx = seq
newlis.append([minele])
currmin = minele
seq += 1
if len(newlis[seq]) > longest:
longest = len(newlis[seq])
longest_idx = seq
print(newlis)
print(newlis[longest_idx])

Related

Checking sum of items in a list if equals target value

I am trying to make a program that checks whether which items are equal to a target value in a list and then output their indexes.
E.g.
li = [2, 5, 7, 9, 3]
target = 16
output: [2, 3]
li = [2, 5, 7, 9, 3]
target = 7
output: [0, 1]
Another way, assuming you can sort the list is the following
original_l = [1,2,6,4,9,3]
my_l = [ [index, item] for item,index in zip(original_l, range(0,len(original_l)))]
my_l_sort = sorted(my_l, key=lambda x: x[1])
start_i = 0
end_i = len(my_l_sort)-1
result = []
target = 7
while start_i < end_i:
if my_l_sort[start_i][1] + my_l_sort[end_i][1] == target:
result.append([my_l_sort[start_i][0], my_l_sort[end_i][0]])
break
elif my_l_sort[start_i][1] + my_l_sort[end_i][1] < target:
start_i+=1
else:
end_i-=1
if len(result) != 0:
print(f"Match for indices {result[0]}")
else:
print("No match")
The indices 0 and 1 of result[0] are respectively the 2 positions, given as a 2 element string, in original_l that holds the values that summed give the target.
is this a homework?
Anyways, here is the answer you are looking for
def check_sum(l, target):
for i in range(len(l)):
sum_temp = 0
for j in range(i, len(l)):
if sum_temp == target:
return [i, j-1]
else:
sum_temp += l[j]
return None
print(check_sum([2, 5, 7, 9, 3], 16))
"""
check_sum([2, 5, 7, 9, 3], 16)
>>> [2, 3]
check_sum([2, 5, 7, 9, 3], 7)
>>> [0, 1]
check_sum([2, 5, 7, 9, 3], 99)
>>> None
"""
The code is self-explanatory and does not require extra commenting. It simply iterates over the list of integers you have as an input and tries to find a sequence of values that add up to your target.
If you dont worry about stack explosion, for smaller input.
We divide solutions containing an index and not containing index and merge all those solution. It returns indices of all possible solutions.
It is O(2^n) solutions. Similar ones
def solve(residual_sum, original_list, present_index):
'''Returns list of list of indices where sum gives residual_sum'''
if present_index == len(original_list)-1:
# If at end of list
if residual_sum == original_list[-1]:
# if residual sum if equal to present element
# then this index is part of solution
return [[present_index]]
if residual_sum == 0:
# 0 sum, empty solution
return [[]]
# Reaching here would mean list at caller side can not
# lead to desired sum, so there is no solution possible
return []
all_sols = []
# Get all solutions which contain i
# since i is part of solution,
# so we only need to find for residual_sum-original_list[present_index]
solutions_with_i = solve(residual_sum-original_list[present_index], original_list, present_index+1)
if solutions_with_i:
# Add solutions containing i
all_sols.extend([[present_index] + x for x in solutions_with_i])
# solution dont contain i, so use same residual sum
solutions_without_i = solve(residual_sum, original_list, present_index+1)
if solutions_without_i:
all_sols.extend(solutions_without_i)
return all_sols
print(solve(16, [2, 5, 7, 9, 3], 0))
Indices
[[0, 1, 3], [2, 3]]

How to find the longest alternating increasing subsequence by alternating between two arrays

Given two arrays of numbers, how do I find the longest increasing subsequence by alternating between the elements of the two arrays?
for example
A = [4, 1, 10, 5, 9]
B = [4, 5, 7, 14]
so the output must be [1,4,5,7,9,14]
It HAS to be in the format of element of a , element of b , element of a , element of b ... so on.
I tried finding a solution but I couldn't think of anything, I tried the LIS approach but since that code requires one array only that didnt make sense to me either sorry
(just in case , to make it more clear = [ 1(A), 4(B), 5(A), 7(B), 9(A), 14(B)] also, note how the order cannot be changed)
Thanks and sorry if I've not phrased it properly
The simplest way would just be to do:
# lias: Longest Increasing Alternating Subsequence
def lias(curr, next, seq):
good, elem = False, None
while not good:
if not curr: return seq
elem = min(curr); curr.remove(elem)
good = False if elem in seq or elem < max(seq, default=0) else True
seq.append(elem)
return lias(next, curr, seq)
if __name__ == '__main__':
A = [4, 1, 10, 5, 9]
B = [4, 5, 7, 14]
print(lias(A,B, []))
You can define a recursive solution that builds the sequence one element at a time based on the previous element and the remaining elements of the list it came from. Each recursion can swap the parameter order so that the logic only needs to care about one of the lists.
This can be further optimized by carrying a length target based on the longest sequence found so far.
For example:
def longAltSeq(A,B,prev=None,minLen=0):
result = longAltSeq(B,A,min(B)-1) if prev is None else [] # inverted params
for i,a in enumerate(A): # Start with an item from A
if prev is not None and a<=prev: continue # increasing from previous
if 2*min(len(A)-i,len(B)+1)<minLen: break # not enough items left
seq = [a]+longAltSeq(B,A[i+1:],a,len(result)) # alternate A<->B
if len(seq)>len(result): result = seq # track longest
return result
ouput:
A = [4, 1, 10, 5, 9]
B = [4, 5, 7, 14]
print(longAltSeq(A,B))
[1, 4, 5, 7, 9, 14]
The solution starts with the inverted parameters so that the initial order of the lists doesn't matter
Here's a solution in JavaScript that'll work. You should be able to replicate that logic in Python or whatever language you need. Cheers, and welcome to the community!
var A = [4, 1, 10, 5, 9];
var B = [4, 5, 7, 14];
run(A,B);
function getSmallest(list, limit){
var results = [];
list.forEach(number => {
if (number > limit)
results.push(number);
});
results.sort(function(a, b) {
return a - b;
});
return results[0];
}
function run(list_a,list_b){
var result = [];
var current_list = 1;
var current_limit = Number.NEGATIVE_INFINITY;
while (current_limit != undefined) {
current_limit = getSmallest((current_list) ? list_a : list_b, current_limit);
current_list = !current_list;
result.push(current_limit);
}
result.pop();
console.log(result);
}

Find 4 values in a list that are close together

I am trying to find the 4 closest value in a given list within a defined value for the difference. The list can be of any length and is sorted in increasing order. Below is what i have tried:
holdlist=[]
m=[]
nlist = []
t = 1
q = [2,3,5,6,7,8]
for i in range(len(q)-1):
for j in range(i+1,len(q)):
if abs(q[i]-q[j])<=1:
holdlist.append(i)
holdlist.append(j)
t=t+1
break
else:
if t != 4:
holdlist=[]
t=1
elif t == 4:
nlist = holdlist
holdlist=[]
t=1
nlist = list(dict.fromkeys(nlist))
for num in nlist:
m.append(q[num])
The defined difference value here is 1. Where "q" is the list and i am trying to get the result in "m" to be [5,6,7,8]. but it turns out to be an empty list.
This works only if the list "q" is [5,6,7,8,10,11]. My guess is after comparing the last value, the for loop ends and the result does not go into "holdlist".
Is there a more elegant way of writing the code?
Thank you.
One solution would be to sort the input list and find the smallest window of four elements. Given the example input, this is
min([sorted(q)[i:i+4] for i in range(len(q) - 3)],
key=lambda w: w[3] - w[0])
But given a different input this will still return a value if the smallest window has a bigger spacing than 1. But I'd still use this solution, with a bit of error handling:
assert len(q) > 4
answer = min([sorted(q)[i:i+4] for i in range(len(q) - 3)], key=lambda w: w[3] - w[0])
assert answer[3] - answer[0] < 4
Written out and annotated:
sorted_q = sorted(q)
if len(q) < 4:
raise RuntimeError("Need at least four members in the list!")
windows = [sorted_q[i:i+4] for i in range(len(q) - 3)] # All the chunks of four elements
def size(window):
"""The size of the window."""
return window[3] - window[0]
answer = min(windows, key=size) # The smallest window, by size
if answer[3] - answer[0] > 3:
return "No group of four elements has a maximum distance of 1"
return answer
This would be one easy approach to find four closest numbers in list
# Lets have a list of numbers. It have to be at least 4 numbers long
numbers = [10, 4, 9, 1,7,12,25,26,28,29,30,77,92]
numbers.sort()
#now we have sorted list
delta = numbers[4]-numbers[0] # Lets see how close first four numbers in sorted list are from each others.
idx = 0 # Let's save our starting index
for i in range(len(numbers)-4):
d = numbers[i+4]-numbers[i]
if d < delta:
# if some sequence are closer together we save that value and index where they were found
delta = d
idx = i
if numbers[idx:idx+4] == 4:
print ("closest numbers are {}".format(numbers[idx:idx+4]))
else:
print ("Sequence with defined difference didn't found")
Here is my jab at the issue for OP's reference, as #kojiro and #ex4 have already supplied answers that deserve credit.
def find_neighbor(nums, dist, k=4):
res = []
nums.sort()
for i in range(len(nums) - k):
if nums[i + k - 1] - nums[i] <= dist * k:
res.append(nums[i: i + k])
return res
Here is the function in action:
>>> nums = [10, 11, 5, 6, 7, 8, 9] # slightly modified input for better demo
>>> find_neighbor(nums, 1)
[[5, 6, 7, 8], [6, 7, 8, 9], [7, 8, 9, 10]]
Assuming sorting is legal in tackling this problem, we first sort the input array. (I decided to sort in-place for marginal performance gain, but we can also use sorted(nums) as well.) Then, we essentially create a window of size k and check if the difference between the first and last element within that window are lesser or equal to dist * k. In the provided example, for instance, we would expect the difference between the two elements to be lesser or equal to 1 * 4 = 4. If there exists such window, we append that subarray to res, which we return in the end.
If the goal is to find a window instead of all windows, we could simply return the subarray without appending it to res.
You can do this in a generic fashion (i.e. for any size of delta or resulting largest group) using the zip function:
def deltaGroups(aList,maxDiff):
sList = sorted(aList)
diffs = [ (b-a)<=maxDiff for a,b in zip(sList,sList[1:]) ]
breaks = [ i for i,(d0,d1) in enumerate(zip(diffs,diffs[1:]),1) if d0!=d1 ]
groups = [ sList[s:e+1] for s,e in zip([0]+breaks,breaks+[len(sList)]) if diffs[s] ]
return groups
Here's how it works:
Sort the list in order to have each number next to the closest other numbers
Identify positions where the next number is within the allowed distance (diffs)
Get the index positions where compliance with the allowed distance changes (breaks) from eligible to non-eligible and from non-eligible to eligible
This corresponds to start and end of segments of the sorted list that have consecutive eligible pairs.
Extract subsets of the the sorted list based on the start/end positions of consecutive eligible differences (groups)
The deltaGroups function returns a list of groups with at least 2 values that are within the distance constraints. You can use it to find the largest group using the max() function.
output:
q = [10,11,5,6,7,8]
m = deltaGroups(q,1)
print(q)
print(m)
print(max(m,key=len))
# [10, 11, 5, 6, 7, 8]
# [[5, 6, 7, 8], [10, 11]]
# [5, 6, 7, 8]
q = [15,1,9,3,6,16,8]
m = deltaGroups(q,2)
print(q)
print(m)
print(max(m,key=len))
# [15, 1, 9, 3, 6, 16, 8]
# [[1, 3], [6, 8, 9], [15, 16]]
# [6, 8, 9]
m = deltaGroups(q,3)
print(m)
print(max(m,key=len))
# [[1, 3, 6, 8, 9], [15, 16]]
# [1, 3, 6, 8, 9]

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]

Divide and conquer to find the max sum sublist of a list

Given a list L, which two items that are adjacent in the list cannot both be picked in the sublist S, and list L does not contain repeated values. I want to design an algorithm using divide-and-conquer approach that outputs a sublist S that maximises the sum of its elements. For instance, ifL = [1, 0, 5, 3, 2, 7, 9, 15, 6, 4, 13], then S = [1, 5, 7, 15, 13].
The following codes I wrote are not working and I think it's not a divide-and-conquer approach.
def bestsublist(l):
sublist = []
n = len(l)
totalsum = [None] * (n + 1)
totalsum[n] = 0
for i in range(n-1,-1,-1):
totalsum[i] = max(l[i] + totalsum[min(i+2,n)],totalsum[min(i+1,n)])
if l[i] + totalsum[min(i+2,n)] > totalsum[min(i+1,n)]:
sublist.append(l[l[i] + totalsum[min(i+2,n)] - 1])
else:
sublist.append(l[totalsum[min(i+1,n)] - 1])
return sublist
Your solution is almost correct. The only thing wrong with it is how you build the solution sublist.
The problem is that you append to it before you finish traversing the entire list, so you don't know yet if you're going to use the element or not.
So to fix it just run through the list again and build the sublist. Here's how it would look:
....
for i in range(n-1,-1,-1):
totalsum[i] = max(l[i] + totalsum[min(i+2,n)],totalsum[min(i+1,n)])
i = 0
while i < n:
if l[i] + totalsum[min(i+2,n)] > totalsum[min(i+1,n)]:
sublist.append(l[i])
i += 2
else:
i += 1
return sublist
P.s. Your solution is dynamic programming, not divide-and-conquer.

Categories