Given a list, I want to generate a permutation of the list that is alternating: the first element must be greater than the second, which must be lower than the third, which must be greater than the second, etc.
I have tried the following code:
li=[10,2,11,13,21,12,6,7,8,9]
# condition = a1>a2<a3>a4.....>an
li = sorted(li)
print(li)
for i in range(len(li)-1):
li[i], li[i+1] = li[i+1], li[i]
print(li[i],li[i+1])
print(li)
But it doesn't satisfy the condition. It gives the Output as below- 👇
'[2, 6, 7, 8, 9, 10, 11, 12, 13, 21]'
'[6, 7, 8, 9, 10, 11, 12, 13, 21, 2]'
This problem can be solved rather efficiently with only a few steps:
>>> li=[10,2,11,13,21,12,6,7,8,9]
>>> # sort from smallest to largest
>>> li.sort()
>>> result = [None] * len(li)
>>> # use first/smallest half for a2, a4, a6, ...
>>> result[1::2] = li[:len(li)//2]
>>> # use second/largest half for a1, a3, a5, ...
>>> result[::2] = li[len(li)//2:]
>>> result
[10, 2, 11, 6, 12, 7, 13, 8, 21, 9]
The [::2] and [1::2] are a "slice from 0/1 with step 2", i.e. the odd/even positions.
Now why does this work?
Let's look at a small case first:
a1 > a2 < a3 > a4
We can rewrite this to separate criteria for each element:
a1 > a2
a3 > a2 and a3 > a4
a2 < a1 and a2 < a3
a4 < a3
These are only between each odd element and several even elements, and likewise between each even element and several odd elements. There are no relations between odd elements and no relations between even elements. This means there are several possible solutions and we can pick the one that works easiest:
Instead of worrying about the many triples of elements – like a2 a3 a4 – and how they relate to each other, it would be simpler if we could look at the two even/odd element groups at once.
We can do this by extending our relation between each odd element to all even elements and vice versa:
a1 > a2 and a1 > a4
a3 > a2 and a3 > a4
a2 < a1 and a2 < a3
a4 < a1 and a4 < a3
This still satisfies the initial criteria, it just excludes some possible solution.
Now, if we compare the rules for all odd elements, we can see that they are exactly the same and likewise for all even elements:
ai > a2 and ai > a4 for all i in {1, 3}
aj < a1 and aj < a3 for all i in {2, 4}
In fact, both are just a single rule for all elements:
ai > aj for all i in {1, 3} for all i in {2, 4}
At this point, it should be obvious1 that this does not just apply to the case of 4 elements but in fact to every number of elements:
ai > aj for all i in {1, 3, ...} for all i in {2, 4, ...}
In words, this means a possible solution is to have all odd elements larger than all even elements. This can be achieved by splitting the sorted input to get the largest/smallest elements, then assigning each of these groups to the even/odd positions.
1The proof is left as an exercise to the reader.
Related
I have two lists J1 and A1. I have another list J2 with some elements from J1. I want to print corresponding values from A1 using A2. I present the current and expected output.
J1 = [1, 7, 9, 11]
A1 = [2.1,6.9,7.3,5.4]
J2 = [1, 9]
J2,A2=map(list, zip(*((a, b) for a, b in zip(J2,A1))))
print(A2)
The current output is
[2.1, 6.9]
The expected output is
[2.1, 7.3]
Another variation, closer to the original:
A2 = [a for a,j in zip(A1,J1) if j in J2]
Define a dict using the keys in J1 and the values in A, then use the values in J2 as keys to look up in the new dict. operator.itemgetter will be useful.
>>> from operator import itemgetter
>>> d = dict(zip(J1, A1))
>>> A2 = list(itemgetter(*J2)(d))
>>> A2
[2.1, 7.3]
J1 = [1, 7, 9, 11]
A1 = [2.1,6.9,7.3,5.4]
J2 = [1, 9]
A2 = [A1[J1.index(a)] for a in J2]
print(A2)
I want to get the values that appear in one of the lists but not in the others. I even tried using '<>', it says invalid syntax. I am trying using list comprehensions.
com_list = []
a1 = [1,2,3,4,5]
b1 = [6,4,2,1]
come_list = [a for a in a1 for b in b1 if a != b ]
Output:
[1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5]
My expected output would be `[3, 5, 6]
What you want is called symmetric difference, you can do:
a1 = [1,2,3,4,5]
b1 = [6,4,2,1]
set(a1).symmetric_difference(b1)
# {3, 5, 6}
which you can also write as:
set(a1) ^ set(b1)
If you really want a list in the end, just convert it:
list(set(a1) ^ set(b1))
# [3, 5, 6]
a1 = [1,2,3,4,5]
b1 = [6,4,2,1]
If you really want to do that using list comprehensions, well, here it is, but it's really not the right thing to do here.
A totally inefficient version:
# Don't do that !
sym_diff = [x for x in a1+b1 if x in a1 and x not in b1 or x in b1 and x not in a1]
print(sym_diff)
# [3, 5, 6]
It would be a bit better using sets to test membership efficiently:
# Don't do that either
a1 = set([1,2,3,4,5])
b1 = set([6,4,2,1])
sym_diff = [x for x in a1|b1 if x in a1 and x not in b1 or x in b1 and x not in a1]
print(sym_diff)
# [3, 5, 6]
But if you start using sets, which is the right thing to do here, use them all the way properly and use symmetric_difference.
You can do
come_list =[i for i in list((set(a1) - set(b1))) + list((set(b1) - set(a1)))]
print(come_list)
Output
[3, 5, 6]
This new list contains all unique numbers for both of the lists together.
the problem with this line come_list = [a for a in a1 for b in b1 if a != b ] is that the items iterating over each item in the first list over all the items in the second list to check if it's inited but it's not giving unique numbers between both.
When I am doing the slicing, an unexpected thing happened that seems the first to be view but the second is copy.
First
First slice of row, then slice of column. It seems is a view.
>>> a = np.arange(12).reshape(3, 4)
>>> a[0:3:2, :][:, [0, 2]] = 100
>>> a
array([[100, 1, 100, 3],
[ 4, 5, 6, 7],
[100, 9, 100, 11]])
Second
But if I first slice of column, then slice of row, it seems a copy:
>>> a[:, [0, 2]][0:3:2, :] = 0
>>> a
array([[100, 1, 100, 3],
[ 4, 5, 6, 7],
[100, 9, 100, 11]])
I am confused because the two methods finally will cause seem position to change, but why the second actually doesn't change the number?
The accepted answer by John Zwinck is actually false (I just figured this out the hard way!).
The problem in the question is a combination of doing "l-value indexing" with numpy's fancy indexing.
The following doc explains exactly this case
https://scipy-cookbook.readthedocs.io/items/ViewsVsCopies.html
in the section "But fancy indexing does seem to return views sometimes, doesn't it?"
Edit:
To summarize the above link:
Whether a view or a copy is created is determined by whether the indexing can be represented as a slice.
Exception: If one does "fancy indexing" then always a copy is created. Fancy indexing is something like a[[1,2]].
Exception to the exception: If one does l-value indexing (i.e. the indexing happens left of the = sign), then the rule for when a view or a copy are created doesn't apply anymore (though see below for a further exception). The python interpreter will directly assign the values to the left hand side without creating a copy or a view.
To prove that a copy is created in both cases, you can do the operation in two steps:
>>> a = np.arange(12).reshape(3, 4)
>>> b = a[0:3:2, :][:, [0, 2]]
>>> b[:] = 100
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
and
>>> b = a[:, [0, 2]][0:3:2, :]
>>> b[:] = 0
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
Just as an aside, the question by the original poster is the exact problem stated at the end of the scipy-cookbook link above. There is no solution given in the book. The tricky thing about the question is that there are two indexing operations done in a row.
Exception to the exception to the exception: If there are two indexing operations done in a row on the left hand side (as is the case in this question), the direct assignment in l-value indexing only works if the first indexing operation can be represented as a slice. Otherwise a copy has to be created even though it is l-value indexing.
All that matters is whether you slice by rows or by columns. Slicing by rows can return a view because it is a contiguous segment of the original array. Slicing by column must return a copy because it is not a contiguous segment. For example:
A1 A2 A3
B1 B2 B3
C1 C2 C3
By default, it is stored in memory this way:
A1 A2 A3 B1 B2 B3 C1 C2 C3
So if you want to choose every second row, it is:
[A1 A2 A3] B1 B2 B3 [C1 C2 C3]
That can be described as {start: 0, size: 3, stride: 6}.
But if you want to choose every second column:
[A1] A2 [A3 B1] B2 [B3 C1] C2 [C3]
And there is no way to describe that using a single start, size, and stride. So there is no way to construct such a view.
If you want to be able to view every second column instead of every second row, you can construct your array in column-major aka Fortran order instead:
np.array(a, order='F')
Then it will be stored as such:
A1 B1 C1 A2 B2 C2 A3 B3 C3
This is my understanding, for your reference
a[0:3:2, :] # basic indexing, a view
... = a[0:3:2, :][:, [0, 2]] # getitme version, a copy,
# because you use advanced
# indexing [:,[0,2]]
a[0:3:2, :][:, [0, 2]] = ... # howver setitem version is
# like a view, setitem version
# is different from getitem version,
# this is not c++
a[:, [0, 2]] # getitem version, a copy,
# because you use advanced indexing
a[:, [0, 2]][0:3:2, :] = 0 # the copy is modied,
# but a keeps unchanged.
If I have any misunderstanding, please point it out.
This has caused some serious headache today.
Suppose I have two instances of my object, instance A and instance B. These come with properties is the form of a list. Say the two properties for A are
a1 = [1, 2, 3, 4, 5]
a2 = [10, 20, 30, 40, 50]
and those for B:
b1 = [5, 7, 3, 1]
b2 = [50, 20, 30, 20]
What I want is to simply find the indices in b1 and b2, where a pair equals the values in a1 and a2. So in this example this would be the indices 0 and 2 since for those we have
b1[0] = 5 and b2[0] = 50
which we find in a1 and a2 as the last entries. Same for index 2 for which we find (3, 30) in (b1, b2) which is also in (a1, a2).
Note here, that the lists a1 and a2 have always the same length as well as b1 and b2.
Any help? 😊
You can use a combination of zip, set and enumerate:
>>> a1 = [1, 2, 3, 4, 5]
>>> a2 = [10, 20, 30, 40, 50]
>>> b1 = [5, 7, 3, 1]
>>> b2 = [50, 20, 30, 20]
>>> a12 = set(zip(a1, a2))
>>> [i for i, e in enumerate(zip(b1, b2)) if e in a12]
[0, 2]
With zip, you group the pairs together, and with set you turn them into a set, as order does not matter and set have faster lookup. Then, enumerate gives you pairs of indices and elements, and using the list-comprehension you get those indices from b12 whose elements are in a12.
I think another structure would be better?
a tuple, or a key set ...
a = [(1,10),(2,20)] and so on
edit
well... tobias_k shows you how :)
Try this
In [38]: [b1.index(i[0]) for i in zip(a1,a2) for j in zip(b1,b2) if i==j]
Out[38]: [2, 0]
There is also the possibility to check for each element in (a1, a2) whether it is in (b1, b2) and it will return all matches in a list and will take care of duplicates:
a1 = [1, 2, 3, 4, 5]
a2 = [10, 20, 30, 40, 50]
b1 = [5, 7, 3, 1, 5]
b2 = [50, 20, 30, 20, 50]
# Construct list of tuples for easier matching
pair_a = [(i, k) for i, k in zip(a1, a2)]
pair_b = [(i, k) for i, k in zip(b1, b2)]
# Get matching indices (for each entry in pair_a get the indices in pair_b)
indices = [[i for i, j in enumerate(pair_b) if j == k] for k in pair_a]
gives
[[], [], [2], [], [0, 4]]
I know how to shuffle a simple List in Python by using the shuffle function from the random library shuffle(myList)
But how do I shuffle a multidimensional list?
myList[][]
shuffle(myList) didn't work.
You have to shuffle the top level list, then you can map the shuffle function to the sub lists like this in Python 2
from random import shuffle
foo = []
foo.append([1, 2, 3])
foo.append([4, 5, 6])
foo.append([7, 8, 9])
shuffle(foo)
map(shuffle, foo)
print foo
See python 2 fiddle
And like this in python 3
from random import shuffle
foo = []
foo.append([1, 2, 3])
foo.append([4, 5, 6])
foo.append([7, 8, 9])
shuffle(foo)
for ii, sublist in enumerate(foo):
shuffle(foo[ii])
print(foo)
see python 3 fiddle
Using only standard packages, the most efficient way I can think of is to unravel the array into a single list, shuffle, then reshape into a list of lists again.
import random
def shuffle2d(arr2d, rand=random):
"""Shuffes entries of 2-d array arr2d, preserving shape."""
reshape = []
data = []
iend = 0
for row in arr2d:
data.extend(row)
istart, iend = iend, iend+len(row)
reshape.append((istart, iend))
rand.shuffle(data)
return [data[istart:iend] for (istart,iend) in reshape]
def show(arr2d):
"""Shows rows of matrix (of strings) as space-separated rows."""
print ("\n".join(" ".join(row) for row in arr2d))
# Generate some ragged data (5 rows):
arr2d = []
for i,a in enumerate("ABCDE"):
n = random.randint(3,7)
arr2d.append([a+str(j) for j in range(1,n+1)])
# display original and shuffled data
print ("Original...")
show(arr2d)
print ("Shuffled...")
show(shuffle2d(arr2d))
print ("Again...")
show(shuffle2d(arr2d))
Sample output (Python 2.7):
Original...
A1 A2 A3
B1 B2 B3 B4 B5
C1 C2 C3
D1 D2 D3
E1 E2 E3 E4 E5
Shuffled...
A3 C1 E5
C3 D3 A2 E1 D1
A1 E2 C2
B5 B4 B2
B1 D2 E4 E3 B3
Again...
B2 C2 C3
B1 D2 E5 A3 D1
A1 E3 A2
B5 D3 C1
B4 E4 E1 B3 E2
The above works equally well on Python 3.4, by the way. The only version-dependent code features are print and range, and they are used agnostically.
I had this issue with trying to randomise questions and answers while maintaining the parring within the 2D list. At the time of writing this post, I have been learning python for only 3 weeks. My noob-solution was to create a random number, then use this number to pick from the list:
a_list = [["a4", 4], ["a3", 3],["a2", 2], ["a1", 1]]
a_pick = random.randint(0,3)
a_score = (a_list[a_pick])
This method allowed me to get a random selection from my list while keep the pairings I had written
First, import the numpy module:
import numpy as np
numpy has a method called called random.shuffle(). This method allwos you to shuffle a multi-dimensional list.
To shuffle a list using this method, you need to convert a list to a ndarray class, then pass it to random.shuffle().
>>> foo = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> foo = np.array(foo)
>>> foo
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Now, shuffle it.
foo = np.random.shuffle(foo)
The value of foo will be something like this:
array([[1, 2, 3],
[7, 8, 9],
[4, 5, 6]])