Related
Given an array of integers, I want to determine the number of distinct groups of integers wherein the values ascend.
Given array myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
There are 4 distinct groups of integers wherein the values ascend. i.e.
[1, 2, 3, 4], [2, 2, 3], [1, 2] and [2]
Can some with experience guide me on how to achieve this in python?
Another possible answer (also assuming [1, 4] is supposed to be there instead of [2]):
In [14]: def find_ascending_groups(my_list):
...: groups = []
...: current_group = [my_list[0]]
...: for i in range(1, len(my_list)):
...: if current_group[-1] <= my_list[i]:
...: current_group.append(my_list[i])
...: else:
...: if len(current_group) > 1:
...: groups.append(current_group)
...: current_group = [my_list[i]]
...: if len(current_group) > 1:
...: groups.append(current_group)
...: print(groups)
...:
In [15]: find_ascending_groups(myList)
[[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]]
Here is one way to do it:
group = []
list_of_groups = []
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
for i in range(len(myList)):
if i == len(myList)-1:
if len(group) > 0:
group.append(myList[i])
list_of_groups.append(group)
else:
if myList[i] <= myList[i+1]:
group.append(myList[i])
else:
group.append(myList[i])
if len(group) > 1:
list_of_groups.append(group)
group = []
print(list_of_groups)
Result with different test cases -
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
Output: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 5]
Output: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4, 5]]
myList = [1, 2]
Output: [[1, 2]]
myList = [1, 2, 3, 4]
Output: [[1, 2, 3, 4]]
myList = [5, 2, 3, 4]
Output: [[2, 3, 4]]
myList = [5, 2]
Output: []
Assuming [1, 4] is supposed to be there instead of [2], you can do something like this:
n [48]: ascending_ints = []
...: t = []
...: start_idx = 0
...: while start_idx < len(myList) - 1:
...: first = True
...: for end_idx in range(start_idx + 1, len(myList), 1):
...: if myList[end_idx] >= myList[start_idx]:
...: if first:
...: t.append(myList[start_idx])
...: first = False
...: t.append(myList[end_idx])
...: else:
...: if t:
...: ascending_ints.append(t)
...: t = []
...: start_idx = end_idx
...: break
...: start_idx += 1
...:
In [49]: ascending_ints
Out[49]: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]]
This just makes the start_idx start at 0, end_idx begin at 1 and increment both. If myList[end_idx] >= myList[start_idx] then it appends it to t. If it's the first time it's happened for this iteration, it needs to also include the value at start_idx. Once the condition is no longer true, check if anything is in t, if it is, append it to ascending_ints, reset t, set start_idx = end_idx and break the for-loop.
Here is another solution that is more efficient in Python.
def find_ascending_groups(l):
groups = []
last_ascending = (l[0] <= l[1])
l.append(l[-1] - 1) # Add an additional element to make sure the last group is appended
start = 0
for i in range(1, len(l)):
is_ascending = (l[i - 1] <= l[i])
if is_ascending != last_ascending:
if is_ascending:
start = i - 1 # l[i - 1] is a local minimum, set group start
else:
groups.append(l[start:i]) # l[i - 1] is a local maximum, generate group
last_ascending = is_ascending
l.pop() # Remove added element
return groups
EDIT
I wrote a small split_when function which was recently merged into the more-itertools library. split_when takes an iterable and a function. The function takes all pairs of consecutive elements and returns whether the iterable should be splitted between those elements:
>>> myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
>>> import more_itertools
>>> list(more_itertools.split_when(myList, lambda x, y: x > y))
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
# 4>3 3>2 3>1 2>1 4>2
PREVIOUS ANSWER
For the record, you can use a fold, that is functools.reduce in Python:
>>> myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
>>> import functools
>>> functools.reduce(lambda acc, x: acc[:-1] + [acc[-1]+[x]] if acc and x >= acc[-1][-1] else acc + [[x]], myList, [])
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
Not very readable though, but if you write the lambda as a distinct function, you get a better idea of what's happening:
>>> def f(acc, x):
... if acc: # there is at least one closed group
... *prev, cur = acc # closed groups and current group
... *_, y = cur # y is the last element of the current group
... if x >= y: # if ascending
... return prev + [cur + [x]] # return with x added to the current group
...
... return acc + [[x]] # otherwise, return with x in a new group
...
>>> functools.reduce(f, myList, [])
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
The below code returns all possible permutations of the numbers provided.
class Solution:
def permute(self, numbers, start, result):
if start == len(numbers):
print(numbers)
result.append(numbers[:])
return
for i in range(start, len(numbers)):
numbers[start], numbers[i] = numbers[i], numbers[start]
self.permute(numbers, start + 1, result)
numbers[start], numbers[i] = numbers[i], numbers[start]
def solution(self, numbers):
result = []
if not numbers or len(numbers) == 0:
return numbers
self.permute(numbers, 0, result)
return result
res1 = Solution().solution([1, 2, 3])
print(res1)
The final output for this instance will be
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]]
but when I slightly modify the permute function, the output is completely different
def permute(self, numbers, start, result):
if start == len(numbers):
print(numbers)
result.append(numbers) #changing this line
return
for i in range(start, len(numbers)):
numbers[start], numbers[i] = numbers[i], numbers[start]
self.permute(numbers, start + 1, result)
numbers[start], numbers[i] = numbers[i], numbers[start]
which gives the output
[[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
The program works when I use
result.append([x for x in numbers])
or
result.append(numbers[:])
but not when I use
result.append(numbers)
Can someone help me understand as to why this is happening ?
If you don't create a copy of the object with the techniques you've described, you put the same object in the list again and again.
Here's a short example to illustrate the issue:
>>> a=[1,2]
>>> b=[a,a]
>>> a.append(3)
>>> b # [[1, 2, 3], [1, 2, 3]]
I didn't manage to correct a code I thought it would work for sure. Any advice to make the code functional is accepted.
Expected outputs of the following code is a list containing a cyclic permuation of the list
l = [1,2,3,4] (i.e : [[4, 1, 2, 3],[3, 4, 1, 2],[2, 3, 4, 1],[1, 2, 3, 4]])
Although what I get is : [[2, 3, 4, 1]]
The code :
def cycGen(l):
L=[]
while not(l in L) :
L.append(l)
for i in range(len(l)):
if l[i] == len(l) :
l[i]=1
else :
l[i] = 1 + l[i]
return L
print(cycGen([1,2,3,4]))
Another variation of the solution is to consider the following code wich seems unfortunatly not working either :
def cycGen(l):
L=[]
for k in range(len(l)):
L.append(l)
for i in range(len(l)):
if l[i] == len(l) :
l[i]=1
else :
l[i] = 1 + l[i]
return L
Help me with your generous knowlege sharing please.
You can use collections.deque:
from collections import deque
a = [1, 2, 3, 4]
d = deque(a)
for _ in range(len(a)):
d.rotate()
print(list(d))
Which gives you the output:
[4, 1, 2, 3]
[3, 4, 1, 2]
[2, 3, 4, 1]
[1, 2, 3, 4]
As mentioned in Efficient way to shift a list in python
An easy way is just:
In [12]: x = [1,2,3,4]
In [13]: [x[i:]+x[:i] for i in range(len(x))]
Out[13]: [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
In your first code sample, the line L.append(l) appends a "reference" (loosely speaking) to the list l to the end of L, rather than appending a copy as you seem to be expecting. Thus, when l is later modified, the reference to it contained in L is modified as well, and so when l in L is tested, l will equal the reference to itself in L, and so the loop will end. The same basic problem causes your second code sample to return multiples of the same list rather than several different lists.
To store a copy of l at the current point in time in L instead, use L.append(l[:]).
Here is an easy way:
>>> def cycGen(l):
size = len(l)
return [[l[(i+j)%size] for i in range(size)] for j in range(size)]
>>> l = [1,2,3,4]
>>> print cycGen(l)
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
You could do this using a generator too:
a = [1, 2, 3, 4]
def next_pos(max):
i = 0
while True:
for n in xrange(max):
yield n + i
i += 1
pos = next_pos(len(a))
b = []
for i in xrange(len(a)):
n = []
for j in xrange(len(a)):
m = pos.next()
if m >= len(a):
m -= len(a)
n.append(a[m])
b.append(n)
print b
output:
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
I am very much a beginner. I am trying to write a program that, given the number of elements (1-9) in the list as a parameter, will then output all permutations of the list in lexicographical order. In the program it is adding on each permutation as a list into a larger list that contains all the permutations in order. Although the program is not working as expected in general, one main problem I'm having is with this while loop In line 10, I want the list to stop compiling once the final permutation has been added to the list. For example, if my input parameter is n = 4, the last permutation/element should be [4,3,2,1]. However, when I run this program, that element is in the list three times at the end. I don't know how this is so when it should terminate that while loop once it has been added.
def ourPermutations(n):
x=list(range(1,n+1))
permList = []
permList+=[x]
xcopy = x[:]
finalPerm = xcopy[::-1]
while x != finalPerm:
istar = n-2
while x[istar] > x[istar+1]:
istar -= 1
jstar = n-1
while x[jstar] < x[istar]:
jstar -= 1
x[istar],x[jstar] = x[jstar],x[istar]
if istar+1 == n-1:
x = x[:]
else:
a = x[istar+1:]
a = a[::-1]
x = x[:istar+1] + a
permList += [x]
return permList
That is my main question; however, this program is still missing elements when I run it. It isn't quite working, so if you see a spot where something is obviously wrong, feel free to tell me that particular line is what is causing my problems. If it helps, this is based on this identical (and correct) version written in Mathematica 8:
ourpermutations[n_] := (
ourlist = {x=Range[1,n]};
While[
x != Reverse[Range[1,n]],
istar = n-1;
While[x[[istar]] > x[[istar+1, istar--];
jstar = n; While[x[[jstar]] < x[[istar]], jstar--];
x[[{istar, jstar}]] = x[[{jstar, istar}]];
AppendTo[ourlist, x = Join[Take[x,istar], Reverse[Drop[x,istar]]]]
];
ourlist
)
So this is what my Python code should be doing; I just can't get it to do so just yet. Thanks for any of your time and effort.
It looks like you're running into a problem because you aren't copying x soon enough and so you're sometimes modifying x after it's been added to permList. This can be solved by adding x = x[:] at the start of your while loop:
def ourPermutations(n):
x=list(range(1,n+1))
permList = []
permList+=[x]
xcopy = x[:]
finalPerm = xcopy[::-1]
while x != finalPerm:
x = x[:]
istar = n-2
while x[istar] > x[istar+1]:
istar -= 1
jstar = n-1
while x[jstar] < x[istar]:
jstar -= 1
x[istar],x[jstar] = x[jstar],x[istar]
if istar+1 == n-1:
x = x[:]
else:
a = x[istar+1:]
a = a[::-1]
x = x[:istar+1] + a
permList += [x]
return permList
>>> ourPermutations(3)
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
>>> ourPermutations(4)
[[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3, 2], [2, 1, 3, 4], [2, 1, 4, 3], [2, 3, 1, 4], [2, 3, 4, 1], [2, 4, 1, 3], [2, 4, 3, 1], [3, 1, 2, 4], [3, 1, 4, 2], [3, 2, 1, 4], [3, 2, 4, 1], [3, 4, 1, 2], [3, 4, 2, 1], [
4, 1, 2, 3], [4, 1, 3, 2], [4, 2, 1, 3], [4, 2, 3, 1], [4, 3, 1, 2], [4, 3, 2, 1]]
A slightly more "pythonic" version might look like:
def our_permutations(n):
x = list(range(1, n+1))
perm_list = [x]
final_perm = x[::-1]
while x != final_perm:
x = x[:]
istar = n-2
while x[istar] > x[istar+1]:
istar -= 1
jstar = n-1
while x[jstar] < x[istar]:
jstar -= 1
x[istar],x[jstar] = x[jstar],x[istar]
if istar+1 != n-1:
a = x[istar+1:]
a = a[::-1]
x = x[:istar+1] + a
perm_list += [x]
return perm_list
Checking these functions against itertools.permutation shows that they produce the same answers, so it looks like your algorithm was correct except for that small mistake.
What's the most efficient (in time) way of checking if two relatively short (about 3-8 elements) lists are shifted copies of one another? And if so, determine and return the offset?
Here is the example code and output I'd like:
>>> def is_shifted_copy(list_one, list_two):
>>> # TODO
>>>
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [3, 2, 1])
None
>>> is_shifted_copy([1, 2, 3], [1])
None
>>> is_shifted_copy([1, 1, 2], [2, 1, 1])
1
Lists may have duplicate entries. If more than one offset is valid, return any offset.
here's a simple iterator version that does the job in 2n iterations (n being the length of list)
import itertools
def is_shifted_copy(list1, list2):
if len(list1) != len(list2):
return False
iterator = iter(list2)
for i, item in enumerate(itertools.chain(list1, list1)):
try:
if item == iterator.next():
continue
else:
iterator = iter(list2)
except StopIteration:
return i - len(list2)
else:
return False
print is_shifted_copy([1, 2, 3], [1, 2, 3]) #0
print is_shifted_copy([1, 2, 3], [3, 1, 2]) #2
print is_shifted_copy([1, 2, 3], [3, 2, 1]) #False
print is_shifted_copy([1, 2, 3], [2, 3, 1]) #1
print is_shifted_copy([1, 1, 2], [2, 1, 1]) #2
print is_shifted_copy([1, 2, 3], [1]) #False
print is_shifted_copy([1, 2, 1], [2, 1, 1]) #1
print is_shifted_copy([1, 1, 1], [1, 1, 1]) #0
and from your specification,
shouldn't is_shifted_copy([1, 1, 2], [2, 1, 1]) return 2?
Searching two copies of the first list allows us to avoid performing excessive concatenation:
def is_shifted_copy(l1, l2):
l1l1 = l1 * 2
n = len(l1)
return next((i for i in range(n) if l1l1[i:i + n] == l2), None)
Here is a solution based on indexes and slicing:
>>> def is_shifted_copy(l1, l2):
try:
return [l1[-i:] + l1[:-i] for i in range(len(l1))].index(l2)
except ValueError:
return None
The result is as expected:
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [2, 1, 3])
None
Below is a modified version of NPE's solution, which checks for all possible match positions. It compares favorably to thkang's (and ecatmur's) clever iterator solutions:
import itertools as IT
def is_shifted_copy_thkang(list1, list2):
N = len(list1)
if N != len(list2):
return None
elif N == 0:
return 0
next_item = iter(list2).next
for i, item in enumerate(IT.chain(list1, list1)):
try:
if item == next_item():
continue
else:
next_item = iter(list2).next
except StopIteration:
return -i % N
else:
return None
def is_shifted_copy_NPE(list1, list2):
N = len(list1)
if N != len(list2):
return None
elif N == 0:
return 0
pos = -1
first = list1[0]
while pos < N:
try:
pos = list2.index(first, pos+1)
except ValueError:
break
if (list2 + list2)[pos:pos+N] == list1:
return pos
return None
def is_shifted_copy_ecatmur(l1, l2):
l1l1 = l1 * 2
n = len(l1)
return next((-i % n for i in range(n) if l1l1[i:i + n] == l2), None)
tests = [
# ([], [], 0),
([1, 2, 3], [1, 2, 3], 0),
([1, 2, 3], [3, 1, 2], 1),
([1, 2, 3], [2, 3, 1], 2),
([1, 2, 3], [3, 2, 1], None),
([1, 2, 3], [1], None),
([1, 1, 2], [2, 1, 1], 1),
([1,2,3,1,3,2], [1,3,2,1,2,3], 3)
]
for list1, list2, answer in tests:
print(list1, list2, answer)
assert is_shifted_copy_thkang(list1, list2) == answer
assert is_shifted_copy_NPE(list1, list2) == answer
assert is_shifted_copy_ecatmur(list1, list2) == answer
In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.5 us per loop
In [377]: %timeit is_shifted_copy_ecatmur([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 2.37 us per loop
In [379]: %timeit is_shifted_copy_NPE([1, 2, 3], [3, 1, 2])
1000000 loops, best of 3: 1.13 us per loop
Note: I changed the return value in is_shifted_copy_thkang and is_shifted_copy_ecatmur so that all three versions would pass the tests in the original post.
I benchmarked the functions with and without the change and found it does not significantly affect the performance of the functions.
For example, with return i:
In [384]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.38 us per loop
With return -i % N:
In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.5 us per loop
Flawed solution
Unfortunately, the solution by thkang and the modified version from unutbu fail for surprisingly simple inputs.
For example, we (correctly) get an integer result for the lists [1, 2, 1] and [1, 1, 2]:
>>> is_shifted_copy([1, 2, 1], [1, 1, 2])
2
However, when swapping the arguments, we get the (incorrect) result False:
>>> is_shifted_copy([1, 1, 2], [1, 2, 1])
False
Similarly, other lists that are shifted copies aren't treated correctly:
>>> is_shifted_copy([1, 2, 2], [2, 1, 2])
False
>>> is_shifted_copy([1, 1, 2, 1], [1, 2, 1, 1])
False
>>> is_shifted_copy([1, 2, 1, 3, 3], [3, 1, 2, 1, 3])
False
To understand the source of this problem, let me reproduce the current version of thkang's solution (with a modified call to next for Python 3):
import itertools
def is_shifted_copy(list1, list2):
if len(list1) != len(list2):
return False
iterator = iter(list2)
for i, item in enumerate(itertools.chain(list1, list1)):
try:
if item == next(iterator):
continue
else:
iterator = iter(list2) # Reset iterator
except StopIteration:
return i - len(list2)
else:
return False
Now, the following happens for a call like is_shifted_copy([1, 2, 2], [2, 1, 2]):
i
item
next(iterator)
Result
0
1
2^
reset iterator
1
2
2^
continue
2
2
1
reset iterator
3
1
2^
reset iterator
4
2
2^
continue
5
2
1
reset iterator, return False
^ First element of list2.
As we can see, the repeated elements in the input lists cause the iterator itertools.chain(list1, list1) to be exhausted before we even have the chance to get to the final element of list2.
Possible fix
If we insist on using iterators (to prevent copying of potentially large input lists, for example), we'd have to ensure the iterator for the first list isn't exhausted before we can make a comparison with the elements from the second list. The only approach I can currently think of that guarantees a non-exhausted first iterator is creating a new iterator for all possible shifted versions of list1:
import itertools
def is_shifted_copy(list1, list2):
length = len(list1)
if len(list2) != length:
return
for i in range(length):
iterator1 = itertools.islice(itertools.cycle(list1), i, i + length + 1)
iterator2 = iter(list2)
for item in iterator1:
try:
if item != next(iterator2):
break
except StopIteration:
return -i % length
return None
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [3, 2, 1])
None
>>> is_shifted_copy([1, 2, 3], [1])
None
>>> is_shifted_copy([1, 1, 2], [2, 1, 1])
1
>>> is_shifted_copy([1, 2, 1], [1, 1, 2])
1
>>> is_shifted_copy([1, 1, 2], [1, 2, 1])
2
>>> is_shifted_copy([1, 2, 2], [2, 1, 2])
1
>>> is_shifted_copy([1, 1, 2, 1], [1, 2, 1, 1])
3
>>> is_shifted_copy([1, 2, 1, 3, 3], [3, 1, 2, 1, 3])
1
At best, the above algorithm finishes in O(n) steps. The algorithm performs worst, namely O(n^2) in time, for input lists with mostly duplicate elements.