Shuffle a list recursively - python

I am trying to shuffle a list recursively, that has numbers between 1 - 13, such that each number in the even indices will be on the right side of the list and the numbers in the odd indices will be on the left side of the new list.
For example take l=[1, 13, 10, 1, 5, 2, 5, 3].
Function should return: [1, 10, 5, 5, 13, 1, 2, 3].
Here's my work:
def shuffle(arr, start, stop):
if stop == 0:
return [arr[stop-1]]
return [arr[start]] + shuffle(arr, start + 2, stop - 2) + [arr[stop-1]]
Note: start =0 , stop = len(arr). (and surely the length of the array is a power of 2).
My code output is [1, 10, 5, 5, 3, 13, 1, 2, 3], I have been trying for hours to get rid of the extra 3 in the middle, but I'm really having a hard time.
When I wrote the return, I thought about solving a smaller problem, and that's how I usually approach recursive answers, and I am taking the first even number and putting it into the left of the list, then sending the "smaller" list, then putting back the last odd number in the list.
I would really appreciate any help of how to fix this problem, I'm trying to overcome this problem for hours, and I feel like understanding how to fix this will help me understand recursion more.
Thanks in advance.
Edit: Sorry if it wasn't clear but I know how to solve it normally, my goal is to solve it recursively.

Instead of using a start and stop, try using a single index and use negative indexing to traverse the list from the right side:
l=[1, 13, 10, 1, 5, 2, 5, 3]
def shuffle(arr, index=0):
if index >= len(arr):
return []
return [arr[index]] + shuffle(arr, index + 2) + [arr[-1-index]]
print(shuffle(l))
This approach only works for lists with an even number of elements, but that should be ok based on what you've told us.

You could partition the list and only recurse for the shuffling of the left and right portions:
from random import choice
def shuffle(A,left=None,done=0):
if left is None:
A = A[::2]+A[1::2] # partition list
left = len(A[1::2]) # compute left size for recursion
if done>left:return A # all shuffled
if done<left:
rLeft = choice(range(done,left)) # shuffle left part
A[done],A[rLeft] = A[rLeft],A[done]
if left+done<len(A):
rRight = choice(range(left+done,len(A))) # shuffle right part
A[-1-done],A[rRight] = A[rRight],A[-1-done]
return shuffle(A,left,done+1) # recurse
output:
L = [1, 13, 10, 1, 5, 2, 5, 3]
print(shuffle(L))
[10, 5, 5, 1, 2, 1, 3, 13]

Feels much cleaner to me to just create a new list directly:
def shuffle(array):
return array[0::2] + array[1::2]
l = [1, 13, 10, 1, 5, 2, 5, 3]
print(shuffle(l))

Related

Function to shift a section of a Python or numpy list

Given a sorted list such as:
[1, 2, 2, 3, 3, 4]
My goal is to check if there are any numbers repeated, and if so, shift the element and all the numbers before it by one to the left as such:
[1, 2, 2, 3, 3, 4]
[0, 1, 2, 3, 3, 4]
[-1, 0, 1, 2, 3, 4]
right now this is my approach:
def shifting(data):
i = 0
while i< len(data)-1:
if data[i]==data[i+1]:
j=i
while j>=0:
data[j]-=1
j-=1
i+=1
return data
But this is an O(n^2) algorithm and takes a lot of time to run with very long lists. I want to find a more efficient approach. Any ideas?
I would agree with Stef. The main idea is that you don't really need to have this nested while loop. All you need is a single pass to pinpoint where the duplications occur and apply a shift accordingly.
I'll propose something a little bit more complex but might be more compact:
import numpy as np
input_list = [1, 2, 2, 3, 3, 4]
# Convert to array for easier indexing
output_ary = np.array(input_list)
# Pinpoint the location at which duplications occur
duplication_indicator = output_ary[:-1] - output_ary[1:] == 0
# Compute the corresponding shift
shift = np.cumsum(duplication_indicator[::-1])[::-1]
# Apply the shift
output_ary[:-1] -= shift
# Convert back to list
output_list = output_ary.tolist()
The main idea is that after you've pinpointed the duplication locations, you can compute the corresponding shift by looking at how many more duplications occur to the right. This could be done by simply doing a reversed cumulative sum (summing from the right to left). Applying this shift to the original list then gives the desired output.
Iterate on the data from right to left. Keep a counter decrement that tells you how many duplicates you've encountered so far, and thus, by how much you want to decrement every element you see.
This is linear instead of quadratic: you only iterate on the data once.
When writing python code, I strongly suggest using for-loops rather than while-loops whenever you can, and in particular when you know the length of the loop by advance.
In your code, i = 0; while i < len(data) - 1: i += 1 can be replaced by for i in range(len(data)-1):.
To iterate from right to left: for i in range(len(data)-1, -1, -1):
The logic is no fully clear, but IIUC is seems easy to remove the duplicates with np.unique, then to left fill the array with a range to go to the initial length:
a2 = np.unique(a)
out = np.r_[np.arange(-(a.shape[0]-a2.shape[0]), 0)+1, a2]
output:
array([-1, 0, 1, 2, 3, 4])
on a = np.array([1,2,2,6,6,7]):
array([-1, 0, 1, 2, 6, 7])

Iterative list with "in range" function?

I'm still pretty new to this website and to python. So, please forgive my question possibly not being specific enough and the possibility of there already being an answer that I haven't found yet.
Basically, I am having trouble with for loops in python.
I want to create lists using the in range() function. Each successive list should set with the next number. For example
[1,2,3,4,5]
[2,3,4,5,6]
[3,4,5,6,7]
I want also the lists that are produced to be of equal length: for example, 5 numbers, 6 numbers or 7 numbers, etc.
l=[]
for i in range (1,10):
for a in range (i):
l.append (i)
print (l[:5])
result is:
[1]
[1, 2]
[1, 2, 2]
[1, 2, 2, 3]
[1, 2, 2, 3, 3]
and then it just repeats with 3. However, I want the next line two start with a new number, so 2 then the next line 3, etc.
I would greatly appreciate your help or at least a hint in the right direction !
If this question has already been answered, I would also appreciate having the link for the respective article.
Start over. Code one or two lines at a time. Print out variables to trace their values. Do not write more code until you know that the current code does what you need. A significant portion of your problem is that you tried to write too much code in one pass, more than you (yet) know how to handle.
Let's start here:
l=[]
for i in range (1,10):
...
Although you clearly state that you want a new starting point at each iteration, you initialize l only once. This means that it will accumulate all of the values you append to it for the entire run of the program, rather than giving you a new list each time.
for a in range (i):
How does this support anything that you're trying to do? You will iterate once, then twice, then 3 times ... what part of your program is repeated in this fashion? You clearly state that you want each list to be of the same length, but then you go out of your way to ensure that you accumulate an ever-increasing list on each completion of this inner loop.
Does this get you moving?
Hey Christopher try this.
for i in range (0,4):
l=[]
for j in range (1+i,6+i):
l.append (j)
print(l)
I think what you're trying to do probably looks something like this:
my_lists = []
for i in range(10):
some_list = []
for j in range(5):
some_list.append(i + j)
print(some_list)
my_lists.append(some_list)
# or if you're being fancy,
# my_lists = [list(range(i, i + 5)) for i in range(10)]
I wouldn't worry too much about the ways to shorten and/or optimise the various
bits of it for now. This gives you a nice list my_lists of all the smaller
lists generated. Does that help you? I'd make sure you understand what's going
on in each loop, and why everything's structured the way it is.
(side note: the sum of the first three values of each list
is given by a fairly straightforward arithmetic progression, so if you need to
calculate it efficiently you'd be better off looking for an algebraic closed
form.)
You could write a function like this one:
def makelists(start, end, n):
sequence = [i for i in range(start, end + 1)]
if n == 1:
print(sequence)
else:
print(sequence)
for i in range(1, n):
sequence = list(map(lambda x: x + 1, sequence))
print(sequence)
makelists(1, 5, 3)
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
For other inputs:
makelists(3, 10, 7)
[3, 4, 5, 6, 7, 8, 9, 10]
[4, 5, 6, 7, 8, 9, 10, 11]
[5, 6, 7, 8, 9, 10, 11, 12]
[6, 7, 8, 9, 10, 11, 12, 13]
[7, 8, 9, 10, 11, 12, 13, 14]
[8, 9, 10, 11, 12, 13, 14, 15]
[9, 10, 11, 12, 13, 14, 15, 16]

Why is the range loop in bubble sort reversed?

I am new to Python and learning data structure in Python. I am trying to implement a bubble sort algorithm in python and I did well but I was not getting a correct result. Then I found some tutorial and there I saw that they are first setting a base range for checking.
So the syntax of range in python is:
range([start], stop[, step])
And the bubble sort algorithm is:
def bubbleSort(alist):
for i in range(len(alist) - 1, 0, -1):
for j in range(i):
if alist[j] > alist[j+1]:
temp = alist[j]
alist[j] = alist[j+1]
alist[j+1] = temp
return alist
print(bubbleSort([5, 1, 2, 3, 9, 8, 0]))
I understood all the other logic of the algorithm but I am not able to get why the loop is starting from the end of the list and going till first element of the list:
for i in range(len(alist) - 1, 0, -1):
Why is this traversing the list in reverse? The main purpose of this loop is setting the range condition only so why can't we traverse from the first element to len(list) - 1 like this:
for i in range(0, len(alist) - 1, 1):
In your code, the index i is the largest index that the inner loop will consider when swapping the elements. The way bubble sort works is by swapping sibling elements to move the largest element to the right.
This means that after the first outer iteration (or the first full cycle of the inner loop), the largest element of your list is positioned at the far end of the list. So it’s already in its correct place and does not need to be considered again. That’s why for the next iteration, i is one less to skip the last element and only look at the items 0..len(lst)-1.
Then in the next iteration, the last two elements will be sorted correctly, so it only needs to look at the item 0..len(lst)-2, and so on.
So you want to decrement i since more and more elements at the end of the list will be already in its correct position and don’t need to be looked at any longer. You don’t have to do that; you could also just always have the inner loop go up to the very end but you don’t need to, so you can skip a few iterations by not doing it.
I asked why we are going reverse in the list like len(list)-1,0. Why are we not going forward way like 0,len(list)-1?
I was hoping that the above explanation would already cover that but let’s go into detail. Try adding a print(i, alist) at the end of the outer loop. So you get the result for every iteration of i:
>>> bubbleSort([5, 1, 3, 9, 2, 8, 0])
6 [1, 3, 5, 2, 8, 0, 9]
5 [1, 3, 2, 5, 0, 8, 9]
4 [1, 2, 3, 0, 5, 8, 9]
3 [1, 2, 0, 3, 5, 8, 9]
2 [1, 0, 2, 3, 5, 8, 9]
1 [0, 1, 2, 3, 5, 8, 9]
As you can see, the list will be sorted from the right to the left. This works well for our index i which will limit how far the inner loop will go: For i = 4 for example, we already have 3 sorted elements at the end, so the inner loop will only have to look at the first 4 elements.
Now, let’s try changing the range to go in the other direction. The loop will be for i in range(0, len(alist)). Then we get this result:
>>> bubbleSort([5, 1, 3, 9, 2, 8, 0])
0 [5, 1, 3, 9, 2, 8, 0]
1 [1, 5, 3, 9, 2, 8, 0]
2 [1, 3, 5, 9, 2, 8, 0]
3 [1, 3, 5, 9, 2, 8, 0]
4 [1, 3, 5, 2, 9, 8, 0]
5 [1, 3, 2, 5, 8, 9, 0]
6 [1, 2, 3, 5, 8, 0, 9]
As you can see, this is not sorted at all. But why? i still limits how far the inner loop will go, so at i = 1, the loop will only look at the first pair and sort that; the rest will stay the same. At i = 2, the loop will look at the first two pairs and swap those (once!); the rest will stay the same. And so on. By the time the inner loop can reach the last element (which is only on the final iteration), there aren’t enough iterations left to swap the zero (which also happens to be the smallest element) to the very left.
This is again because bubble sort works by sorting the largest elements to the rightmost side first. So we have to start the algorithm by making the inner loop be able to reach that right side completely. Only when we are certain that those elements are in the right position, we can stop going that far.
There is one way to use a incrementing outer loop: By sorting the smallest elements first. But this also means that we have to start the inner loop on the far right side to make sure that we check all elements as we look for the smallest element. So we really have to make those loops go in the opposite directions.
It's because when you bubble from the start of the list to the end, the final result is that the last item in the list will be sorted (you've bubbled the largest item to the end). As a result, you don't want to include the last item in the list when you do the next bubble (you know it's already in the right place). This means the list you need to sort gets shorter, starting at the end and going down towards the start. In this code, i is always the length of the remaining unsorted list.
You can use this for:
for i in range(0,len(alist)-1,1):
but consequently you should change your second iteration:
for j in range(0,len(alist)-i,1):
I think the purpose of using reverse iteration in the first line is to simplify the second iteration. This is the advantage of using python
as #Jeremy McGibbon's answer, the logic behind bubble sort is to avoid j reach the "sorted part" in the behind of list. When using the example code, j range will be decreased as the value of i decrease. When you change i to increasing, you should handle j iteration differently
You can write the code as follow
lst = [9,6,5,7,8,3,2,1,0,4]
lengthOfArray = len(lst) - 1
for i in range(lengthOfArray):
for j in range(lengthOfArray - i):
if lst[j] > lst[j + 1]:
lst[j], lst[j + 1] = lst[j + 1], lst[j]
print(lst)

Mysterious interaction between Python's slice bounds and "stride"

I understand that given an iterable such as
>>> it = [1, 2, 3, 4, 5, 6, 7, 8, 9]
I can turn it into a list and slice off the ends at arbitrary points with, for example
>>> it[1:-2]
[2, 3, 4, 5, 6, 7]
or reverse it with
>>> it[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1]
or combine the two with
>>> it[1:-2][::-1]
[7, 6, 5, 4, 3, 2]
However, trying to accomplish this in a single operation produces in some results that puzzle me:
>>> it[1:-2:-1]
[]
>>>> it[-1:2:-1]
[9, 8, 7, 6, 5, 4]
>>>> it[-2:1:-1]
[8, 7, 6, 5, 4, 3]
Only after much trial and error, do I get what I'm looking for:
>>> it[-3:0:-1]
[7, 6, 5, 4, 3, 2]
This makes my head hurt (and can't help readers of my code):
>>> it[-3:0:-1] == it[1:-2][::-1]
True
How can I make sense of this? Should I even be pondering such things?
FWYW, my code does a lot of truncating, reversing, and listifying of iterables, and I was looking for something that was faster and clearer (yes, don't laugh) than list(reversed(it[1:-2])).
This is because in a slice like -
list[start:stop:step]
start is inclusive, resultant list starts at index start.
stop is exclusive, that is the resultant list only contains elements till stop - 1 (and not the element at stop).
So for your caseit[1:-2] - the 1 is inclusive , that means the slice result starts at index 1 , whereas the -2 is exclusive , hence the last element of the slice index would be from index -3.
Hence, if you want the reversed of that, you would have to do it[-3:0:-1] - only then -3 would be included in the sliced result, and the sliced result would go upto 1 index.
The important things to understand in your slices are
Start will be included in the slice
Stop will NOT be included in the slice
If you want to slice backwards, the step value should be a negative value.
Basically the range which you specify is a half-open (half-closed) range.
When you say it[-3:0:-1] you are actually starting from the third element from the back, till we reach 0 (not including zero), step one element at a time backwards.
>>> it[-3:0:-1]
[7, 6, 5, 4, 3, 2]
Instead, you can realize the start value like this
>>> it[len(it)-3 : 0 : -1]
[7, 6, 5, 4, 3, 2]
I think the other two answers disambiguate the usage of slicing and give a clearer image of how its parameters work.
But, since your question also involves readability -- which, let's not forget, is a big factor especially in Python -- I'd like to point out how you can improve it slightly by assigning slice() objects to variables thus removing all those hardcoded : separated numbers.
Your truncate and reverse slice object could, alternatively, be coded with a usage implying name :
rev_slice = slice(-3, 0, -1)
In some other config-like file. You could then use it in its named glory within slicing operations to make this a bit more easy on the eyes :
it[rev_slice] # [7, 6, 5, 4, 3, 2]
This might be a trivial thing to mention, but I think it's probably worth it.
Why not create a function for readability:
def listify(it, start=0, stop=None, rev=False):
if stop is None:
the_list = it[start:]
else:
the_list = it[start:stop]
if rev:
return the_list[::-1]
else:
return the_list
listify(it, start=1, stop=-2) # [2, 3, 4, 5, 6, 7]
listify(it, start=1, stop=-2, rev=True) # [7, 6, 5, 4, 3, 2]
A good way to intuitively understand the Python slicing syntax is to see how it maps to the corresponding C for loop.
A slice like
x[a:b:c]
gives you the same elements as
for (int i = a; i < b; i += c) {
...
}
The special cases are just default values:
a defaults to 0
b defaults to len(x)
c defaults to 1
Plus one more special case:
if c is negative, then a and b are swapped and the < is inverted to a >

iterating over a sublist in python

Say I have the following list:
my_list = [2, 3, 4, 1, 44, 222, 43, 22]
How can I assign a constant value to all the elements in a sublist without the use of for loop? something like:
my_list[0:5:1] = 1 # Assign 1 to first 5 elements. This code is wrong since list requires an iterator
Specifically, I would like to assign a constant value to all the elements, starting from an index, i till the end of the list i.e. say
my_list[i:end] = 1 # What I would like to do. The code itself is wrong
Any suggestions on how to do it in the cleanest way in python?
Sometimes a 2-liner is better than a 1-liner.
for i in range(0,5):
my_list[i] = 0
If you really want to make something obfuscated:
Here's a one-liner which does the job without changing the original array.
new_list = [0 if i in range(0,5) else x for x, i in zip(my_list, xrange(len(my_list)))]
Which would you rather come across in unknown code? Or your own code 6 months later.
The following should work:
my_list[i:end] = [1]*(end-i)
Examples:
>>> def test(my_list, i, end):
... my_list[i:end] = [1]*(end-i)
... return my_list
>>> test(range(10), 0, 5)
[1, 1, 1, 1, 1, 5, 6, 7, 8, 9]
>>> test(range(10), 5, 10)
[0, 1, 2, 3, 4, 1, 1, 1, 1, 1]
>>> test(range(10), 3, 8)
[0, 1, 2, 1, 1, 1, 1, 1, 8, 9]
One possibility is stuff [0:5] = [1] * 5. However, you should be cautious since this will change the size of the list if you get the number of replacing elements wrong. If you do stuff [0:5] = [1] * 6, you will increase the size of the list (i.e., putting six 1s where there used to be five original values).
For going to the end of the list, just omit the end of the slice. Then, you can repeat a list to assign to it:
my_list[i:] = [1] * (len(my_list) - i)
How about this?
from itertools import repeat
my_list[start:end] = repeat( 1, end - start )
Would this work:
my_list = my_list[:i] + [1 for x in xrange(len(my_list) - i)]

Categories