More 'Pythonic' Way to alternate list shuffle - python

Trying to 'shuffle' a list with even number of items. Splitting the list, L in half and alternating taking an element from each.
I've tried pop, but that approach was unable to get me to a one-liner. (while loop) and I know there is likely some more succinct way to move through it.
The shuffle from random isn't exactly what I need, either – because that randomizes the entire order instead of alternating between the split list.
If a one-liner isn't possible, is that because it's more readable in a while loop?
def shuffle(L):
'''
I apologize in advance for how wet the following code is...
Example:
>>> shuffle([1, 2, 3, 4, 5, 6])
[1, 4, 2, 5, 3, 6]
'''
return [L[:(len(L)//2)][0], L[(len(L)//2):][0], L[:(len(L)//2)][1], L[(len(L)//2):][1], L[:(len(L)//2)][2], L[(len(L)//2):][2]]
other attempt:
def shuffle(L):
x, L_first, L_next, L = len(L), L[:(len(L)//2)], L[(len(L)//2):], []
while len(L) != x:
L.extend([L_first.pop(0), L_next.pop(0)])
return L

Use slice assignment with a step:
def shuffle(l):
result = [None] * len(l)
# Put the first half of l in the even indices of result
result[::2] = l[:len(l)//2]
# Put the second half of l in the odd indices of result
result[1::2] = l[len(l)//2:]
return result

If I understand correctly, you could also opt for itertools.chain.from_iterable after zipping to get the alternating effect.
from itertools import chain
def shuff(l):
return list(chain.from_iterable(zip(l[:len(l)//2], l[len(l)//2:])))
Demo
>>> shuff(list(range(1, 7))
[1, 4, 2, 5, 3, 6]

One possibility (requires an external library but the recipe can also be found in the itertools-recipes section) is:
from iteration_utilities import roundrobin
def shuffle(L):
return list(roundrobin(L[:len(L)//2], L[len(L)//2:]))
This is probably slower than list assignment but it also works for arbitary amounts of iterables without problems and it doesn't require odd-sized-input handling:
>>> shuffle([1, 2, 3, 4, 5, 6, 7])
[1, 4, 2, 5, 3, 6, 7]
>>> shuffle([1, 2, 3, 4, 5, 6])
[1, 4, 2, 5, 3, 6]
I did some timings and #user2357112 definetly has the fastest solution but my solution is at least on the second place (note that this graph is in log-log, that means the difference in absolute terms may seem smaller than it really is!):
Disclaimer: I'm the author of that iteration_utilities library.

list comprehension with index calculation using modulo and floor division
[ L[(i + (i % 2)*len(L))//2] for i in range(len(L)) ] # for case of even len(L)
still one line for the general case
[ L[i//2 + (i % 2)*len(L)//2] for i in range(2*(len(L)//2)) ] + [L[-1]]*(len(L) % 2)
the index calc (i + (i % 2)*len(L))//2
can be parsed as adding
i//2 which gives 0, 0, 1, 1, 2, 2 ...
and
(i % 2)*len(L)//2 where (i % 2) alternates 0, 1 for even/odd i
0, len(L)//2, 0, len(L)//2, 0, len(L)//2 ...
sum:
0, len(L)//2, 1, 1 + len(L)//2, 2, 2 + len(L)//2 ...

Found two solutions. First one is very unpythonic (using python 2.7)
a = [1, 2, 3, 4, 5, 6] # intial array
Method One (using string magic):
[int(p) for p in ' '.join([str(x) + ' ' + str(y) for x, y in zip(a[:len(a) / 2], a[len(a) / 2:])]).split(' ')]
Method Two:
[i for Tuple in zip(a[:len(a) / 2], a[len(a) / 2:]) for i in Tuple]

Related

IndexError when reversing an array in Python 3

I tried to reverse an array in Python, but I got an IndexError: list index out of range. Can anyone help me find out what's my problem?
Here's my code:
def reverseArray(a,n):
b = []
for i in range(n + 1):
b.insert(0, a[i])
return b
Error:
Traceback (most recent call last):
File "/tmp/submission/20220709/03/24/hackerrank-6dc9d7d54d580ec91f519f2a6455587b/code/Solution.py", line 33, in <module>
res = reverseArray(arr,arr_count)
File "/tmp/submission/20220709/03/24/hackerrank-6dc9d7d54d580ec91f519f2a6455587b/code/Solution.py", line 21, in reverseArray
b.insert(0, a[i])
IndexError: list index out of range
Since reverseArray is the call which caused the issue, let's fix it first
Refactoring your code we get :
def reverseArray(a, n): # rather unpythonic
b = []
for i in range(n - 1, -1, -1):
b.append(a[i])
return b
Which works like this taking two arguments :
>>> foo = [1, 2, 3]
>>> reverseArray(foo, 3)
[3, 2, 1]
But that is unpythonic and can be alot better, something like this :
def reversed_array(a):
return [*reversed(a)]
Which is exactly the same thing except it takes one less argument of length
Example use :
>>> foo = [1, 2, 3]
>>> reversed_array(foo)
[3, 2, 1]
Since the list "b" doesn't contain any element , so b.insert(0,a[i]) won't work .
What you have to do instead is , use b.append(a[i]) , which in your case , will get the job done.
If you are using it like this:
reverseArray([], 0)
reverseArray([1, 2, 3], 3)
The actually problem is:
range(n + 1)
Cause range creates sequence from 0 to n - 1:
range(0) # empty seq
range(3) # 0, 1, 2
So you simply need do it like this:
def reverseArray(a: list, n: int):
b = []
for i in range(n):
b.insert(0, a[i])
return b
But there is shorter, faster and more pythonic way to do it:
a = [1, 2, 3]
reversed_a = a[::-1]
>>> reversed_a
[3, 2, 1]
Actually there's a quick and "pythonic" way to reverse an array in Python. You can use the Array Slicing. Put a pair of brackets after your array just like calling an element. For example, list1 = [1, 2, 3, 4, 5, 6]. You can use the array slicing to reverse it like this: list = list[::-1]. This is a very use ful skill in Python, and it's capable for all iterable items. For more informations about slicing in Python, see here.
Array slicing is just a simple way to reverse. Now let's get back to our question. The goal is to reverse the given array. Instead of the normal hard way, we can think the problem reversely. In your original code you were enumerating the array from start to end, which is a hard way to control the index of the array. Now let's enumerate it from back to start. So for i in range(n + 1) can be replaced with for i in range(n - 1, -1, -1). This line of code enumerates from -1 to 0. Inside the for-loop, you can directly add a[i] in b (Because is in reverse order). After that, the code for this method will be:
def reverse_array(array): # Follow PEP8!
res_array = []
for i in range(len(array) - 1, -1, -1):
res_array.append(array[i])
return res_array
And here's an example:
>>> example = [1, 2, 3, 4, 5, 6]
>>> reverse_array(example)
[6, 5, 4, 3, 2, 1]

Rotate array in Python

There is this question, 189 Rotate array on Leetcode. enter link description here Its statement is "Given an array, rotate the array to the right by k steps, where k is non-negative."
To understand it better, here's an example.
enter image description here
So, My code for this is
for _ in range(k):
j = nums[-1]
nums.remove(nums[-1])
nums.insert(0, j)
It cannot pass some of the test cases in it.
In the discussion panel, I found a code claiming it got submitted successfully that went like
for _ in range(k):
nums.insert(0, nums.pop(-1))
I would like to know, what is the difference between these two and why my code isn't able to pass some of the test cases.
If you do this on python shell [].remove.__doc__, you'll see the purpose of list.remove is to:
Remove first occurrence of value. Raises ValueError if the value is
not present.
In your code nums.remove(nums[-1]) does not remove the last item, but first occurrence of the value of your last item.
E.g.
If you have a list with values nums = [2, 4, 8, 3, 4] and if you do nums.remove(nums[-1]) the content of nums becomes [2, 8, 3, 4] not [2, 4, 8, 3] that you're expecting.
Just use slicing:
>>> def rotate(l, n):
... return l[-n:] + l[:-n]
...
>>> lst = [1, 2, 3, 4, 5, 6, 7]
>>> rotate(lst, 1)
[7, 1, 2, 3, 4, 5, 6]
>>> rotate(lst, 2)
[6, 7, 1, 2, 3, 4, 5]
>>> rotate(lst, 3)
[5, 6, 7, 1, 2, 3, 4]
In your code j = nums[-1] and you are trying to insert(0, nums[-1]).
In
for _ in range(k):
nums.insert(0, nums.pop(-1))
inserting other number - (0, nums.pop(-1))
Answer given by cesarv is good and simple, but on large arrays numpy will definitely perform better. Consider:
np.roll(lst, n)
The remove() method takes a single element as an argument and removes it's first occurence from the list.
The pop() method takes a single argument (index). The argument passed to the method is optional. If not passed, the default index -1 is passed as an argument (index of the last item).
If the test case has the same item before the last index then test case will fail.
Hence to correct your code replace remove with pop
for _ in range(k):
poppedElement = nums.pop()
nums.insert(0, poppedElement)
or to make it even concise -
for _ in range(k):
nums.insert(0, nums.pop())

How to calculate a cumulative product of a list using list comprehension

I'm trying my hand at converting the following loop to a comprehension.
Problem is given an input_list = [1, 2, 3, 4, 5]
return a list with each element as multiple of all elements till that index starting from left to right.
Hence return list would be [1, 2, 6, 24, 120].
The normal loop I have (and it's working):
l2r = list()
for i in range(lst_len):
if i == 0:
l2r.append(lst_num[i])
else:
l2r.append(lst_num[i] * l2r[i-1])
Python 3.8+ solution:
:= Assignment Expressions
lst = [1, 2, 3, 4, 5]
curr = 1
out = [(curr:=curr*v) for v in lst]
print(out)
Prints:
[1, 2, 6, 24, 120]
Other solution (with itertools.accumulate):
from itertools import accumulate
out = [*accumulate(lst, lambda a, b: a*b)]
print(out)
Well, you could do it like this(a):
import math
orig = [1, 2, 3, 4, 5]
print([math.prod(orig[:pos]) for pos in range(1, len(orig) + 1)])
This generates what you wanted:
[1, 2, 6, 24, 120]
and basically works by running a counter from 1 to the size of the list, at each point working out the product of all terms before that position:
pos values prod
=== ========= ====
1 1 1
2 1,2 2
3 1,2,3 6
4 1,2,3,4 24
5 1,2,3,4,5 120
(a) Just keep in mind that's less efficient at runtime since it calculates the full product for every single element (rather than caching the most recently obtained product). You can avoid that while still making your code more compact (often the reason for using list comprehensions), with something like:
def listToListOfProds(orig):
curr = 1
newList = []
for item in orig:
curr *= item
newList.append(curr)
return newList
print(listToListOfProds([1, 2, 3, 4, 5]))
That's obviously not a list comprehension but still has the advantages in that it doesn't clutter up your code where you need to calculate it.
People seem to often discount the function solution in Python, simply because the language is so expressive and allows things like list comprehensions to do a lot of work in minimal source code.
But, other than the function itself, this solution has the same advantages of a one-line list comprehension in that it, well, takes up one line :-)
In addition, you're free to change the function whenever you want (if you find a better way in a later Python version, for example), without having to change all the different places in the code that call it.
This should not be made into a list comprehension if one iteration depends on the state of an earlier one!
If the goal is a one-liner, then there are lots of solutions with #AndrejKesely's itertools.accumulate() being an excellent one (+1). Here's mine that abuses functools.reduce():
from functools import reduce
lst = [1, 2, 3, 4, 5]
print(reduce(lambda x, y: x + [x[-1] * y], lst, [lst.pop(0)]))
But as far as list comprehensions go, #AndrejKesely's assignment-expression-based solution is the wrong thing to do (-1). Here's a more self contained comprehension that doesn't leak into the surrounding scope:
lst = [1, 2, 3, 4, 5]
seq = [a.append(a[-1] * b) or a.pop(0) for a in [[lst.pop(0)]] for b in [*lst, 1]]
print(seq)
But it's still the wrong thing to do! This is based on a similar problem that also got upvoted for the wrong reasons.
A recursive function could help.
input_list = [ 1, 2, 3, 4, 5]
def cumprod(ls, i=None):
i = len(ls)-1 if i is None else i
if i == 0:
return 1
return ls[i] * cumprod(ls, i-1)
output_list = [cumprod(input_list, i) for i in range(len(input_list))]
output_list has value [1, 2, 6, 24, 120]
This method can be compressed in python3.8 using the walrus operator
input_list = [ 1, 2, 3, 4, 5]
def cumprod_inline(ls, i=None):
return 1 if (i := len(ls)-1 if i is None else i) == 0 else ls[i] * cumprod_inline(ls, i-1)
output_list = [cumprod_inline(input_list, i) for i in range(len(input_list))]
output_list has value [1, 2, 6, 24, 120]
Because you plan to use this in list comprehension, there's no need to provide a default for the i argument. This removes the need to check if i is None.
input_list = [ 1, 2, 3, 4, 5]
def cumprod_inline_nodefault(ls, i):
return 1 if i == 0 else ls[i] * cumprod_inline_nodefault(ls, i-1)
output_list = [cumprod_inline_nodefault(input_list, i) for i in range(len(input_list))]
output_list has value [1, 2, 6, 24, 120]
Finally, if you really wanted to keep it to a single , self-contained list comprehension line, you can follow the approach note here to use recursive lambda calls
input_list = [ 1, 2, 3, 4, 5]
output_list = [(lambda func, x, y: func(func,x,y))(lambda func, ls, i: 1 if i == 0 else ls[i] * func(func, ls, i-1),input_list,i) for i in range(len(input_list))]
output_list has value [1, 2, 6, 24, 120]
It's entirely over-engineered, and barely legible, but hey! it works and its just for fun.
For your list, it might not be intentional that the numbers are consecutive, starting from 1. But for cases that that pattern is intentional, you can use the built in method, factorial():
from math import factorial
input_list = [1, 2, 3, 4, 5]
l2r = [factorial(i) for i in input_list]
print(l2r)
Output:
[1, 2, 6, 24, 120]
The package numpy has a number of fast implementations of list comprehensions built into it. To obtain, for example, a cumulative product:
>>> import numpy as np
>>> np.cumprod([1, 2, 3, 4, 5])
array([ 1, 2, 6, 24, 120])
The above returns a numpy array. If you are not familiar with numpy, you may prefer to obtain just a normal python list:
>>> list(np.cumprod([1, 2, 3, 4, 5]))
[1, 2, 6, 24, 120]
using itertools and operators:
from itertools import accumulate
import operator as op
ip_lst = [1,2,3,4,5]
print(list(accumulate(ip_lst, func=op.mul)))

Python 3.5.1 Extending a list

sorry to repost (I've just joined stack overflow 30 mins ago).
I don't think I explained in my previous post of my function.
def GetExtendedKeyword(message, newkeyword):
while True:
difference = len(message) - len(newkeyword)
if difference > 0:
newkeyword.extend(newkeyword[:difference]
return newkeyword
elif difference <= 0:
return newkeyword
What I have are two lists, a message and keyword list. The program calculates the difference between them and if the keyword is shorter than the message list, the program will repeat the keywordlist by that difference.
For example the original keywordlist is [0,1,5,2,5] and the difference is 3, the end result should be [0,1,5,2,5,0,1,5]. The program doesn't like my code when it comes to longer keyword or message lists. Please help!
If the amount can be larger than the list length, you can do this:
def extend(lst, n):
nfull = n / len(lst) + 1
nrem = n % len(lst)
return lst*nfull+lst[:nrem]
lst = [0,1,5,4,2,9]
print extend(lst, 3)
# [0, 1, 5, 4, 2, 9, 0, 1, 5]
print extend(lst, 7)
# [0, 1, 5, 4, 2, 9, 0, 1, 5, 4, 2, 9, 0]
list.extend(L) Extend the list by appending all the items in the
given list; equivalent to a[len(a):] = L.
It's sentence from Python Documentation. I think it is the best place where you should looking for a help. Acording to documentation, your code should look like this:
listOne = [0,1,5,4,2,9]
listOne.extend(listOne[:n])
I wonder to help you.
Try it like this
l = [0, 1, 5, 4, 2, 9]
l.extend(l[:n]) #extend l with the first n elements of l
This only works for n < len(l) ...

concatenate an arbitrary number of lists in a function in Python

I hope to write the join_lists function to take an arbitrary number of lists and concatenate them. For example, if the inputs are
m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]
then we I call print join_lists(m, n, o), it will return [1, 2, 3, 4, 5, 6, 7, 8, 9]. I realize I should use *args as the argument in join_lists, but not sure how to concatenate an arbitrary number of lists. Thanks.
Although you can use something which invokes __add__ sequentially, that is very much the wrong thing (for starters you end up creating as many new lists as there are lists in your input, which ends up having quadratic complexity).
The standard tool is itertools.chain:
def concatenate(*lists):
return itertools.chain(*lists)
or
def concatenate(*lists):
return itertools.chain.from_iterable(lists)
This will return a generator which yields each element of the lists in sequence. If you need it as a list, use list: list(itertools.chain.from_iterable(lists))
If you insist on doing this "by hand", then use extend:
def concatenate(*lists):
newlist = []
for l in lists: newlist.extend(l)
return newlist
Actually, don't use extend like that - it's still inefficient, because it has to keep extending the original list. The "right" way (it's still really the wrong way):
def concatenate(*lists):
lengths = map(len,lists)
newlen = sum(lengths)
newlist = [None]*newlen
start = 0
end = 0
for l,n in zip(lists,lengths):
end+=n
newlist[start:end] = list
start+=n
return newlist
http://ideone.com/Mi3UyL
You'll note that this still ends up doing as many copy operations as there are total slots in the lists. So, this isn't any better than using list(chain.from_iterable(lists)), and is probably worse, because list can make use of optimisations at the C level.
Finally, here's a version using extend (suboptimal) in one line, using reduce:
concatenate = lambda *lists: reduce((lambda a,b: a.extend(b) or a),lists,[])
One way would be this (using reduce) because I currently feel functional:
import operator
from functools import reduce
def concatenate(*lists):
return reduce(operator.add, lists)
However, a better functional method is given in Marcin's answer:
from itertools import chain
def concatenate(*lists):
return chain(*lists)
although you might as well use itertools.chain(*iterable_of_lists) directly.
A procedural way:
def concatenate(*lists):
new_list = []
for i in lists:
new_list.extend(i)
return new_list
A golfed version: j=lambda*x:sum(x,[]) (do not actually use this).
You can use sum() with an empty list as the start argument:
def join_lists(*lists):
return sum(lists, [])
For example:
>>> join_lists([1, 2, 3], [4, 5, 6])
[1, 2, 3, 4, 5, 6]
Another way:
>>> m = [1, 2, 3]
>>> n = [4, 5, 6]
>>> o = [7, 8, 9]
>>> p = []
>>> for (i, j, k) in (m, n, o):
... p.append(i)
... p.append(j)
... p.append(k)
...
>>> p
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
This seems to work just fine:
def join_lists(*args):
output = []
for lst in args:
output += lst
return output
It returns a new list with all the items of the previous lists. Is using + not appropriate for this kind of list processing?
Or you could be logical instead, making a variable (here 'z') equal to the first list passed to the 'join_lists' function
then assigning the items in the list (not the list itself) to a new list to which you'll then be able add the elements of the other lists:
m = [1, 2, 3]
n = [4, 5, 6]
o = [7, 8, 9]
def join_lists(*x):
z = [x[0]]
for i in range(len(z)):
new_list = z[i]
for item in x:
if item != z:
new_list += (item)
return new_list
then
print (join_lists(m, n ,o)
would output:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Categories

Resources