Related
Given an integer array, find all the consecutive subsequences of alternating odd and even numbers.
Also print the total number of such subsequences.
All the subsequences should be unique.
The numbers in the list may or may not be unique.
Example:
array = [1,2,5]
output1: [[1], [1,2], [2], [2,5], [5], [1,2,5]]
output2: 6
My Code:
res = []
lst = [1,2,5]
for i in range(len(lst)-1):
res.append([lst[i]])
if abs(lst[i] - lst[i+1]) % 2 == 1:
res.append([lst[i], lst[i+1]])
print(res)
Output: [[1], [1, 2], [2], [2, 5]]
How can I get the remaining subsequences?
You care about duplicates:
def alternating_sublists(xs: list[int]) -> list[list[int]]:
results = []
for i in range(len(xs)):
if [xs[i]] not in results:
results.append([xs[i]])
for j in range(i+1, len(xs)):
if (xs[j] - xs[j-1]) % 2 != 0:
if xs[i:j+1] not in results:
results.append(xs[i:j+1])
else:
break
return results
print(list(alternating_sublists([1, 2, 5])))
print(list(alternating_sublists([1, 2, 2, 2, 1])))
print(list(alternating_sublists([1, 2, 3, 2, 3, 2, 1])))
Output:
[[1], [1, 2], [1, 2, 5], [2], [2, 5], [5]]
[[1], [1, 2], [2], [2, 1]]
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 2], [1, 2, 3, 2, 3], [1, 2, 3, 2, 3, 2], [1, 2, 3, 2, 3, 2, 1], [2], [2, 3], [2, 3, 2], [2, 3, 2, 3], [2, 3, 2, 3, 2], [2, 3, 2, 3, 2, 1], [3], [3, 2], [3, 2, 3], [3, 2, 3, 2], [3, 2, 3, 2, 1], [2, 3, 2, 1], [3, 2, 1], [2, 1]]
It's not extremely efficient (there's many lookups of lists already in the result). Depending on the application you may want a more complex data structure to save expensive 'list in large list' tests.
The basic logic is this:
each sequence has to start at some index, so try sequences starting at all possible indices for i in range(len(xs)):
the sequence with length 1 always meets your rule, so add it if it wasn't there yet
the other sequences start at index i and end at index i+1 or greater for j in range(i+1, len(xs)):
break from the loop whenever the modulo is 0 for the last two items in list you're about to add, since this sequence doesn't meet the rule, and longer ones wouldn't either.
Slightly faster and shorter, using tuples internally, but essentially the same:
def alternating_sublists2(xs: list[int]) -> list[list[int]]:
results = set()
for i in range(len(xs)):
results.add((xs[i],))
for j in range(i+1, len(xs)):
if (xs[j] - xs[j-1]) % 2 != 0:
results.add(tuple(xs[i:j+1]))
else:
break
return [list(t) for t in results]
shorter as the previous if statements are now internal to set.add()
faster because looking up tuples is faster than looking up strings, and testing membership of a set is faster than testing membership of a list
not quite as fast as you might like, since it then has to convert the result back to a list of lists, to get the result you required.
However, no guarantees on the order of the sublists in the result, so this is no good if you need the sublists in the order they are first found.
Here's a recursive solution to the problem. It iterates the elements of the list, adding the results from recursing the balance of the list when there is a change from odd to even between the current element and the next:
def odd_even(list, start=None):
result = []
for i, val in enumerate(list):
if start is None or i == 0:
if [val] not in result:
result.append([val])
if len(list) > i+1 and (list[i+1] - val) % 2 == 1:
for res in odd_even(list[i+1:], val):
if [val] + res not in result:
result = result + [[val] + res]
return result
print(odd_even([1, 2, 5]))
print(odd_even([1, 2, 2, 2, 1]))
print(odd_even([1, 2, 3, 2, 3, 2, 1]))
Output:
[[1], [1, 2], [1, 2, 5], [2], [2, 5], [5]]
[[1], [1, 2], [2], [2, 1]]
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 2], [1, 2, 3, 2, 3], [1, 2, 3, 2, 3, 2], [1, 2, 3, 2, 3, 2, 1], [2], [2, 3], [2, 3, 2], [2, 3, 2, 3], [2, 3, 2, 3, 2], [2, 3, 2, 3, 2, 1], [3], [3, 2], [3, 2, 3], [3, 2, 3, 2], [3, 2, 3, 2, 1], [2, 3, 2, 1], [3, 2, 1], [2, 1]]
Your accepted output is silly, because it's obvious that every subsequence of a "good" sequence is also "good" and there's no need to enumerate them all. Let's concentrate on finding longest alternating sequences:
def split(a):
buf = [a[0]]
for i in range(1, len(a)):
if a[i] % 2 != a[i - 1] % 2:
buf.append(a[i])
else:
yield buf
buf = [a[i]]
if buf:
yield buf
test = [1, 2, 5, 7, 3, 8, 9, 9, 10, 11]
result = list(split(test))
# [[1, 2, 5], [7], [3, 8, 9], [9, 10, 11]]
To get your expected answer, take each list from the result and generate all sublists of it. This is another, much simpler task.
This looks like a gray code sequence with additional twist:
https://en.wikipedia.org/wiki/Gray_code
Code:
import math
def powerOf2(k):
if k == 0:
return 1
else:
return 2*powerOf2(k-1)
def gray_encode(n):
return n ^ n >> 1
def count_required_sequence(lst):
n = len(lst)
sequence_nr = powerOf2(n)
results = []
results_sequence = -1
for i in range(sequence_nr):
gray = gray_encode(i)
gray_r = list("{:>010b}".format(gray))[::-1]
#print(gray_r)
count = sum(el == "1" for el in gray_r)
if count > 1:
results_sequence += 1
results.append(list())
for k in range(len(gray_r)):
if k < len(gray_r)-1:
if gray_r[k] == "1" and gray_r[k+1] == "1":
if abs(lst[k] - lst[k+1]) % 2 == 1:
results[results_sequence].append(lst[k])
results[results_sequence].append(lst[k+1])
is_there_count1 = results.count(list(set(results[results_sequence])))
results[results_sequence] = list(set(results[results_sequence]))
is_there_count = results.count(results[results_sequence])
if is_there_count > 1 or is_there_count1 > 1:
index = results.index(list(set(results[results_sequence])))
results.pop(results_sequence)
results_sequence -= 1
elif count == 1 :
results_sequence += 1
results.append(list())
pos = [index for index,value in enumerate(gray_r) if value == "1" ]
results[results_sequence].append(lst[pos[0]])
results = (list(filter(lambda a: a != [], results)))
print("results: {}".format(results))
# Driver code
if __name__ == "__main__" :
# lst = [ 1, 2, 5, 6, 7];
lst = [ 1, 2, 5 ];
count_required_sequence(lst);
Output:
results: [[1], [1, 2], [2], [2, 5], [1, 2, 5], [5]]
Change 010b to a bigger number is len(lst) is bigger then 10
gray_r = list("{:>010b}".format(gray))[::-1]
Let's use a simple example: say I have a list of lists
ll = [[1, 2], [1, 3], [2, 3], [1, 2, 3], [2, 3, 4]]
and I want to find all longest lists, which means all lists that maximize the len function. Of course we can do
def func(x):
return len(x)
maxlen = func(max(ll, key=lambda x: func(x)))
res = [l for l in ll if func(l) == maxlen]
print(res)
Output
[[1, 2, 3], [2, 3, 4]]
But I wonder if there are more efficient way to do this, especially when the function is very expensive or the list is very long. Any suggestions?
From a computer science/algorithms perspective, this is a very classical "reduce" problem.
so, pseudocode. It's honestly very straightforward.
metric():= a mapping from elements to non-negative numbers
winner = []
maxmetric = 0
for element in ll:
if metric(element) larger than maxmetric:
winner = [ element ]
maxmetric = metric(element)
else if metric(element) equal to maxmetric:
append element to winner
when the function is very expensive
Note that you do compute func(x) for each element twice, first there
maxlen = func(max(ll, key=lambda x: func(x)))
then there
res = [l for l in ll if func(l) == maxlen]
so it would be beneficial to store what was already computed. functools.lru_cache allow that easily just replace
def func(x):
return len(x)
using
import functools
#functools.lru_cache(maxsize=None)
def func(x):
return len(x)
However, beware as due to way how data are stored argument(s) must be hashable, so in your example you would first need convert list e.g. to tuples i.e.
ll = [(1, 2), (1, 3), (2, 3), (1, 2, 3), (2, 3, 4)]
See descripiton in docs for further discussion
Is not OK use dictionary like below, (this is O(n))
ll = [[1, 2], [1, 3], [2, 3], [1, 2, 3], [2, 3, 4]]
from collections import defaultdict
dct = defaultdict(list)
for l in ll:
dct[len(l)].append(l)
dct[max(dct)]
Output:
[[1, 2, 3], [2, 3, 4]]
>>> dct
defaultdict(list, {2: [[1, 2], [1, 3], [2, 3]], 3: [[1, 2, 3], [2, 3, 4]]})
OR use setdefault and without defaultdict like below:
ll = [[1, 2], [1, 3], [2, 3], [1, 2, 3], [2, 3, 4]]
dct = {}
for l in ll:
dct.setdefault(len(l), []).append(l)
Output:
>>> dct
{2: [[1, 2], [1, 3], [2, 3]], 3: [[1, 2, 3], [2, 3, 4]]}
I have a list of sublists of random positive integers. This list is controlled by 3 parameters:
max_num: the maximum integer allowed in each sublist, e.g. if max_num = 3, the list will look like [[1,3], [3], [1,2,3], [1], ...];
max_length: the maximum number of intergers in each sublist;
n_gen: the number of sublists generated, i.e., the length of the list.
You can generate such list using the following code
import random
random.seed(10)
def random_numbers(length, max_num):
return [random.randint(1, max_num) for _ in range(length)]
max_num = 3
max_length = 3 # I want max_length=10
n_gen = 10 # I want n_gen=200
lst = [random_numbers(random.randint(1, max_length), max_num) for _ in range(n_gen)]
Now I want to split the list into two partitions, each partition has the same amount of each number. For example, if lst = [[1,2,3], [2,3], [1,3], [3]], one of the solution would be bipartition = [[[1,2,3], [3]], [[2,3], [1,3]]].
I managed to write the following brute-force enumeration for all possible bipartitions, which works fine for small parameters.
from itertools import product
lst1 = []
lst2 = []
for pattern in product([True, False], repeat=len(lst)):
lst1.append([x[1] for x in zip(pattern, lst) if x[0]])
lst2.append([x[1] for x in zip(pattern, lst) if not x[0]])
bipartitions = []
for l1, l2 in zip(lst1, lst2):
flat1 = [i for l in l1 for i in l]
flat2 = [i for l in l2 for i in l]
if sorted(flat1) == sorted(flat2):
bipartitions.append([l1, l2])
for bipartition in bipartitions:
print(bipartition)
Output:
[[[1, 2, 2], [1, 1, 2], [2, 3], [3, 2]], [[1], [2, 2, 1], [3], [1, 2], [3], [2, 2]]]
[[[1, 2, 2], [1, 1, 2], [3], [3], [2, 2]], [[2, 3], [1], [2, 2, 1], [1, 2], [3, 2]]]
[[[1, 2, 2], [2, 3], [1], [2, 2, 1], [3]], [[1, 1, 2], [1, 2], [3], [2, 2], [3, 2]]]
[[[1, 2, 2], [2, 3], [1], [2, 2, 1], [3]], [[1, 1, 2], [3], [1, 2], [2, 2], [3, 2]]]
[[[1, 2, 2], [2, 3], [1], [1, 2], [3, 2]], [[1, 1, 2], [2, 2, 1], [3], [3], [2, 2]]]
[[[1, 2, 2], [1], [2, 2, 1], [3], [3, 2]], [[1, 1, 2], [2, 3], [1, 2], [3], [2, 2]]]
[[[1, 2, 2], [1], [2, 2, 1], [3], [3, 2]], [[1, 1, 2], [2, 3], [3], [1, 2], [2, 2]]]
[[[1, 2, 2], [1], [3], [1, 2], [3], [2, 2]], [[1, 1, 2], [2, 3], [2, 2, 1], [3, 2]]]
[[[1, 2, 2], [2, 2, 1], [3], [1, 2], [3]], [[1, 1, 2], [2, 3], [1], [2, 2], [3, 2]]]
[[[1, 1, 2], [2, 3], [1], [2, 2], [3, 2]], [[1, 2, 2], [2, 2, 1], [3], [1, 2], [3]]]
[[[1, 1, 2], [2, 3], [2, 2, 1], [3, 2]], [[1, 2, 2], [1], [3], [1, 2], [3], [2, 2]]]
[[[1, 1, 2], [2, 3], [3], [1, 2], [2, 2]], [[1, 2, 2], [1], [2, 2, 1], [3], [3, 2]]]
[[[1, 1, 2], [2, 3], [1, 2], [3], [2, 2]], [[1, 2, 2], [1], [2, 2, 1], [3], [3, 2]]]
[[[1, 1, 2], [2, 2, 1], [3], [3], [2, 2]], [[1, 2, 2], [2, 3], [1], [1, 2], [3, 2]]]
[[[1, 1, 2], [3], [1, 2], [2, 2], [3, 2]], [[1, 2, 2], [2, 3], [1], [2, 2, 1], [3]]]
[[[1, 1, 2], [1, 2], [3], [2, 2], [3, 2]], [[1, 2, 2], [2, 3], [1], [2, 2, 1], [3]]]
[[[2, 3], [1], [2, 2, 1], [1, 2], [3, 2]], [[1, 2, 2], [1, 1, 2], [3], [3], [2, 2]]]
[[[1], [2, 2, 1], [3], [1, 2], [3], [2, 2]], [[1, 2, 2], [1, 1, 2], [2, 3], [3, 2]]]
However, when the parameters becomes larger, this becomes infeasible. Now I would like to generate random bipartitions that has same amount of each number, I guess a greedy algorithm will do. For my current task, I need to use
max_num = 3
max_length = 10
n_gen = 200
Any suggestions?
Edit: I am aware that there will be cases where such bipartition is not possible at all. My thought is when the bipartition suggested by the greedy algorithm after a maximum number of suggestions (e.g. 1000 if fast enough), we should believe there is no such bipartitions. When the parameters are large, even a check of whether such bipartition exist will be infeasible.
Holy heck this one was a doozy. First off, let me state the obvious. A greedy algorithm is deterministic, since it will always choose the optimal path. Second, the odds of actually being able to bipartition something is very, very unlikely. I also suggest that if you want to generate bipartitions, trying to find them from random sets like this is not a good idea.
Anyhow, on to the code. First, let me say that the code is not pretty, nor is it completely optimized. Towards the end there I wasn't even being Pythonic in some areas, but they are all easily fixable. I've been at this for hours, but it was a fun project. The copying of the list stands out as the prime suspect. You can re-write it and optimize it in your own time. I also can't guarantee that it's bug-free, but I'm pretty sure it is. Only exception being that you need to make sure that it at least does one "careful" search if you want any results. That brings me to the next point, the algorithm itself.
We start off by doing a pretty standard greedy algorithm. We pick an index from our partitionee and, WLOG, assign it to the left bipartition. Next we look at all possible ways of inserting all remaining lists. We choose the one that brings us closest to 0. We repeat until we hit some breakpoint, after which we switch to your exhaustive algorithm.
Now, odds are we don't find a partition for high values of your constants. I believe this is just a statistical thing, and not a problem with the algorithm, but I could be wrong.
I also implemented a rough feasibility test, and you'll see quite quickly that ~90% of all randomly generated nested lists can immediately be discarded as impossible to bipartition.
However, the addition of the greedy algorithm now allows me, on my machine, to go from testing ~15 length partitions to ~30 length ones, with good success of finding one. It also runs in less than a 10th of second with e.g. 3, 3, 40, 12 as its constants.
Finally, here is the code Note that I only made it generate one partition to test, so you might need to run it a few times before you even get a feasible one:
from itertools import product
import random
import datetime
import time
import sys
MAX_NUM = 3
MAX_LEN = 3
NUM_GEN = 200
NSWITCH = 12
random.seed(time.time())
def feasible(partitionee):
'''Does a rough test to see if it is feasible to find a partition'''
counts = [0 for _ in range(MAX_NUM)]
flat = [x for sublist in partitionee for x in sublist]
for n in flat:
counts[n-1] += 1
for n in counts:
if n % 2 != 0:
return False
return True
def random_numbers(length, max_num, n_lists):
'''Create a random list of lists of numbers.'''
lst = []
for i in range(n_lists):
sublist_length = random.randint(1, length)
lst.append([random.randint(1, max_num) for _ in range(sublist_length)])
return lst
def diff(lcounts, rcounts):
'''Calculate the difference between the counts in our dictionaries.'''
difference = 0
for i in range(MAX_NUM):
difference += abs(lcounts[i] - rcounts[i])
return difference
def assign(partition, d, sublist):
'''Assign a sublist to a partition, and update its dictionary.'''
partition.append(sublist)
for n in sublist:
d[n-1] += 1
def assign_value(d1, d2, sublist):
'''Calculates the loss of assigning sublist.'''
for n in sublist:
d1[n-1] += 1
left_score = diff(d1, d2)
for n in sublist:
d1[n-1] -= 1
d2[n-1] += 1
right_score = diff(d1, d2)
for n in sublist:
d2[n-1] -= 1
return (left_score, right_score)
def greedy_partition(left, right, lcounts, rcounts, i, partitionee):
# Assign the i:th sublist to the left partition.
assign(left, lcounts, partitionee[i])
del partitionee[i]
for _ in range(NUM_GEN - NSWITCH):
# Go through all unassigned sublists and get their loss.
value_for_index = {}
for i, sublist in enumerate(partitionee):
value = assign_value(lcounts, rcounts, sublist)
value_for_index[i] = value
# Find which choice would be closest to 0 difference.
min_value = 100000000000 # BIG NUMBER
best_index = -1
choose_left = True
for key, value in value_for_index.items():
if min(value) < min_value:
min_value = min(value)
choose_left = value[0] < value[1]
best_index = key
# Assign it to the proper list.
if choose_left:
assign(left, lcounts, partitionee[best_index])
else:
assign(right, rcounts, partitionee[best_index])
del partitionee[best_index]
return diff(lcounts, rcounts)
# Create our list to partition.
partition_me = random_numbers(MAX_LEN, MAX_NUM, NUM_GEN)
start_time = datetime.datetime.now()
# Start by seeing if it's even feasible to partition.
if not feasible(partition_me):
print('No bipartition possible!')
sys.exit()
# Go through all possible starting arrangements.
min_score_seen = 100000000000 # BIG NUMBER
best_bipartition = []
for i in range(NUM_GEN):
# Create left and right partitions, as well as maps to count how many of each
# number each partition has accumulated.
left = []
right = []
lcounts = [0 for i in range(MAX_NUM)]
rcounts = [0 for i in range(MAX_NUM)]
# Copy partitionee since it will be consumed.
partition = partition_me.copy()
# Do greedy partition.
score = greedy_partition(left, right, lcounts, rcounts, i, partition)
if score < min_score_seen:
min_score_seen = score
best_bipartition = [left] + [right]
# Now that we've been greedy and fast, we will be careful and slow.
# Consider all possible remaining arrangements.
print('Done with greedy search, starting careful search.')
left = best_bipartition[0]
right = best_bipartition[1]
for pattern in product([True, False], repeat=len(partition)):
lst1 = left + ([x[1] for x in zip(pattern, partition) if x[0]])
lst2 = right +([x[1] for x in zip(pattern, partition) if not x[0]])
left_flat = [x for sublist in lst1 for x in sublist]
right_flat = [x for sublist in lst2 for x in sublist]
if sorted(left_flat) == sorted(right_flat):
print('Found bipartition by careful search:')
print([lst1] + [lst2])
break
end_time = datetime.datetime.now()
print('Time taken: ', end='')
print(end_time - start_time)
I've been trying to convert my list
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
into this. Since the second item of those two sublists are same.
[[[1,3,6],[1,2]],[[2,4],[3,4,5]]]
This is my code
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
lst=[]
for i in range(len(alist)):
inner = []
inner1=[]
for j in range(i+1,len(alist)):
if i+1 < len(alist):
if alist[i][1] == alist[j][1]:
inner1.append(alist[i][0])
inner1.append(alist[j][0])
inner.append(inner1)
inner.append(alist[i][1])
lst.append(inner)
print(lst)
but it gives this instead
[[[1, 3, 1, 6], [1, 2], [1, 3, 1, 6], [1, 2]], [[1, 3, 1, 6], [1, 2], [1, 3, 1, 6], [1, 2]], [[2, 4], [3, 4, 5]], [[3, 6], [1, 2]]]
It works when there's only 2 elements that are the same but when there's 3 it doesn't work.
Example
[2,4],[3,4,5] #2 of the same elements from alist works
[1,3,1,6],[1,2] #3 of the same elements from alist doesn't work
Can anyone please offer a solution?
You can use a dict (an Ordered one since you have to maintain the order) to group "heads" by "tails":
alist = [[1,[1,2]],[2,[3,4,5]],[3,[1,2]],[4,[3,4,5]],[5,[5,6,7]],[6,[1,2]]]
from collections import OrderedDict
c = OrderedDict()
for head, tail in alist:
c.setdefault(tuple(tail), []).append(head)
res = [[heads, list(tail)] for tail, heads in c.items()]
print res
prints
[[[1, 3, 6], [1, 2]], [[2, 4], [3, 4, 5]], [[5], [5, 6, 7]]]
If you want to omit 5 (a group with a single "head"), add a condition to the res= line:
res = [[heads, list(tail)] for tail, heads in c.items() if len(heads) > 1]
I have a list of lists:
a = [[1, 2], [2, 3], [4, 3]]
How to get the following effect in two steps ?:
b = [[1, 2, 2, 3], [1, 2, 4, 3], [2, 3, 4, 3]]
b = [[1, 2, 3], [1, 2, 4, 3]], it means:
1.1. If the same values occur in the sub-list b[i] next to each other, then
one of these values must be deleted.
2.2. If the same values appear in a given sub-list b[i] but not next to each
other, then the entire sub-list b[i] must be deleted.
timegb is right. An elegant solution involves some amount of trickery and deception. I'll try and break down the steps.
find all 2-combinations of your input using itertools.combinations
flatten returned combinations with map and chain
for each combination, group by consecutive elements
keep only those that satisfy your condition by doing a length check.
from itertools import chain, combinations, groupby
out = []
for r in map(lambda x: list(chain.from_iterable(x)), combinations(a, 2)):
j = [i for i, _ in groupby(r)]
if len(j) <= len(set(r)):
out.append(j)
print(out)
[[1, 2, 3], [1, 2, 4, 3]]
If you need only the first part, just find combinations and flatten:
out = list(map(lambda x: list(chain.from_iterable(x)), combinations(a, 2)))
print(out)
[[1, 2, 2, 3], [1, 2, 4, 3], [2, 3, 4, 3]]