Swapping indices with values in a Python list? - python

(No, this is not a homework assignment nor a contest, even though it might look like one.)
I have a list A in Python that contains the numbers range(0, len(A)). The numbers are not in order, but all of them exist in the list.
I'm looking for a simple way to build a list B where the indices and values have been swapped, i.e. a list that, for each integer n, contains the position of n in A.
Example:
A = [0, 4, 1, 3, 2]
B = [0, 2, 4, 3, 1]
I can put the code to generate B either separately or in the code that generates A. In particular, here's how I generate A:
A = [value(i) for i in range(length)]
What would be the best way to do this?

How about assigning to the pre-allocated B:
>>> A = [0, 4, 1, 3, 2]
>>> B = [0] * len(A)
>>> for k, v in enumerate(A): B[v] = k
>>> B
[0, 2, 4, 3, 1]
That would be O(n).

Using the enumerate() function to decorate each value with their index, sorting with sorted() on the values, and then un-decorate again to extract the indices in value order:
[i for i, v in sorted(enumerate(A), key=lambda iv: iv[1])]
This has a O(NlogN) time complexity because we used sorting.
Demo:
>>> A = [0, 4, 1, 3, 2]
>>> [i for i, v in sorted(enumerate(A), key=lambda iv: iv[1])]
[0, 2, 4, 3, 1]
We can also use a pre-built list to assign indices to for a O(N) solution:
B = [0] * len(A)
for i, v in enumerate(A):
B[v] = i
Demo:
>>> B = [0] * len(A)
>>> for i, v in enumerate(A):
... B[v] = i
...
>>> B
[0, 2, 4, 3, 1]
This is probably the better option if time complexity is of a big issue; for N = 100 the sorting approach will take about 461 steps vs. 100 for the pre-built list approach.

A = [0, 4, 1, 3, 2]
B = [None]*len(A)
for i, x in enumerate(A):
B[x] = i
print B
res: [0, 2, 4, 3, 1]

This is very naive;
[ [ x[0] for x in enumerate(A) if x[1] == i][0] for i in range(len(A)) ]
This works perfect,
[A.index(A.index(i)) for i in A]
This is better;
[A.index(i[0]) for i in enumerate(A)]
This one beats the other;
[A.index(i) for i in range(len(A))]
Proof;
import random as r
for l in [ r.sample(range(5),5) for n in range(5) ]:
A = l
B = [A.index(A.index(i)) for i in A]
print "A is : ", A
print "B is : ", B
print "Are B's elements indices of A? : ", A == [B.index(B.index(i)) for i in B]
print

Related

Remove element if next element is the same from list in Python [duplicate]

I was curios about the question: Eliminate consecutive duplicates of list elements, and how it should be implemented in Python.
What I came up with is this:
list = [1,1,1,1,1,1,2,3,4,4,5,1,2]
i = 0
while i < len(list)-1:
if list[i] == list[i+1]:
del list[i]
else:
i = i+1
Output:
[1, 2, 3, 4, 5, 1, 2]
Which I guess is ok.
So I got curious, and wanted to see if I could delete the elements that had consecutive duplicates and get this output:
[2, 3, 5, 1, 2]
For that I did this:
list = [1,1,1,1,1,1,2,3,4,4,5,1,2]
i = 0
dupe = False
while i < len(list)-1:
if list[i] == list[i+1]:
del list[i]
dupe = True
elif dupe:
del list[i]
dupe = False
else:
i += 1
But it seems sort of clumsy and not pythonic, do you have any smarter / more elegant / more efficient way to implement this?
>>> L = [1,1,1,1,1,1,2,3,4,4,5,1,2]
>>> from itertools import groupby
>>> [key for key, _group in groupby(L)]
[1, 2, 3, 4, 5, 1, 2]
For the second part
>>> [k for k, g in groupby(L) if len(list(g)) < 2]
[2, 3, 5, 1, 2]
If you don't want to create the temporary list just to take the length, you can use sum over a generator expression
>>> [k for k, g in groupby(L) if sum(1 for i in g) < 2]
[2, 3, 5, 1, 2]
Oneliner in pure Python
[v for i, v in enumerate(your_list) if i == 0 or v != your_list[i-1]]
If you use Python 3.8+, you can use assignment expression :=:
list1 = [1, 2, 3, 3, 4, 3, 5, 5]
prev = object()
list1 = [prev:=v for v in list1 if prev!=v]
print(list1)
Prints:
[1, 2, 3, 4, 3, 5]
A "lazy" approach would be to use itertools.groupby.
import itertools
list1 = [1, 2, 3, 3, 4, 3, 5, 5]
list1 = [g for g, _ in itertools.groupby(list1)]
print(list1)
outputs
[1, 2, 3, 4, 3, 5]
You can do this by using zip_longest() + list comprehension.
from itertools import zip_longest
list1 = [1, 2, 3, 3, 4, 3, 5, 5].
# using zip_longest()+ list comprehension
res = [i for i, j in zip_longest(list1, list1[1:])
if i != j]
print ("List after removing consecutive duplicates : " + str(res))
Here is a solution without dependence on outside packages:
list = [1,1,1,1,1,1,2,3,4,4,5,1,2]
L = list + [999] # append a unique dummy element to properly handle -1 index
[l for i, l in enumerate(L) if l != L[i - 1]][:-1] # drop the dummy element
Then I noted that Ulf Aslak's similar solution is cleaner :)
To Eliminate consecutive duplicates of list elements; as an alternative, you may use itertools.zip_longest() with list comprehension as:
>>> from itertools import zip_longest
>>> my_list = [1,1,1,1,1,1,2,3,4,4,5,1,2]
>>> [i for i, j in zip_longest(my_list, my_list[1:]) if i!=j]
[1, 2, 3, 4, 5, 1, 2]
Plenty of better/more pythonic answers above, however one could also accomplish this task using list.pop():
my_list = [1, 2, 3, 3, 4, 3, 5, 5]
for x in my_list[:-1]:
next_index = my_list.index(x) + 1
if my_list[next_index] == x:
my_list.pop(next_index)
outputs
[1, 2, 3, 4, 3, 5]
Another possible one-liner, using functools.reduce (excluding the import) - with the downside that string and list require slightly different implementations:
>>> from functools import reduce
>>> reduce(lambda a, b: a if a[-1:] == [b] else a + [b], [1,1,2,3,4,4,5,1,2], [])
[1, 2, 3, 4, 5, 1, 2]
>>> reduce(lambda a, b: a if a[-1:] == b else a+b, 'aa bbb cc')
'a b c'

How to add the current item with the previous item in a list using python

How to add the current list item with the previous 1st list item using python.
>>> l = [4,2,1,3]
If I perform the below I get cumulative sum
>>> new_l = np.cumsum(l)
But I want to add the current list only with the previous 1st list, the desired output should be like this one.
>>> new_l
[4, 6, 3, 4]
If you don't want to use plain python (you'd better go the vectorization approach, in most cases, numpy is more performant), you could use np.roll:
In[12]: np.roll(l, 1) + l
Out[12]: array([7, 6, 3, 4])
With that, you can simply achieve your result with two lines:
In [14]: new_l = np.roll(l, 1) + l
In [15]: new_l[0] = l[0]
In [16]: new_l
Out[16]: array([4, 6, 3, 4])
If you don't mind sticking to plain python:
>>> [sum(l[max(i-1, 0):i+1]) for i in range(len(l))]
[4, 6, 3, 4]
First item is as in l then iterate with zip.
l[:1] + [i+j for i, j in zip(l, l[1:])]
You can achieve this using plain python code. Using zip to yield consecutive elements and using the first element of list unchanged
>>> l = [4,2,1,3]
>>> l[:1] + [i+j for i,j in zip(l,l[1:])]
>>> [4, 6, 3, 4]
Or another one using map and lambda
>>> l[:1] + list(map(lambda x,y: x+y, l, l[1:]))
>>> [4, 6, 3, 4]
A couple ways you can do this with numpy:
Use np.cumsum, subtract out the previous terms
l=[4,2,1,3]
c = np.cumsum(l)
c[2:] -= c[:-2]
c
Out: array([4, 6, 3, 4], dtype=int32)
Pad with 0 and do broadcast addition:
l = [4,2,1,3]
l_ = np.r_[0, l]
l_[1:] + l_[:-1]
Out: array([4, 6, 3, 4])
or do boradcast addition and then pad with the initial value
l = np.array([4,2,1,3])
np.r_[l[0], l[1:] + l[:-1]]
Out: array([4, 6, 3, 4])
All of these will be much faster for large datasets than doing list comprehension
If you don't necessarily want to use numpy then you can use zip
new_list.append(old_list[0]) # append first value
for i, j in zip(old_list, old_list[1:]): # iterate over each consecutive pair
new_list.append(i + j)
and if you are working with large list, consider using
itertools.izip(old_list, old_list[1:])
here is a vanilla Python implementation in one line:
l=[4,2,1,3]
new_l = [l[i] + l[i - 1] if i > 0 else l[i] for i in range(len(l))]
You can do this in plain python using a simple for loop
a = [4, 2, 1, 3]
b = []
for i in range(len(a)):
if i == 0:
b.append(a[i])
else:
b.append(a[i] + a[i-1])
print(b)
Output
[4, 6, 3, 4]

List comprehension with for loop

How can I double the first n odd numbers in a list using list comprehension?
Here is my solution:
>>> n = 2
>>> lst = [1, 2, 3, 4, 5, 6]
>>> lst = [num for num in lst if num % 2 == 1] + [num for num in lst if num % 2 == 0]
>>> lst = [num * 2 for num in lst[:n]] + lst[n:]
>>> print(lst)
[2, 6, 5, 2, 4, 6]
You can see that I can't keep the same order of lst anymore...
More example:
n = 2
lst = [2, 2, 2, 2, 1, 2, 3]
output: lst = [2, 2, 2, 2, 2, 2, 6]
Solution for the original requirement to *“double the first n numbers in a list if it’s odd”:
Since you do not want to remove any items from your original list, you cannot use the filter of the list comprehension syntax (the if after the for). So what you need to do instead is simply transform the item you are putting into the target list.
Your logic is something like this for an element x at index i:
def transform(x, i, n):
if i < n:
if x % 2 == 1:
return x * 2
return x
So you can use that exact function and use it in your list comprehension:
>>> n = 2
>>> lst = [1, 2, 3, 4, 5, 6]
>>> [transform(x, i, n) for i, x in enumerate(lst)]
[2, 2, 3, 4, 5, 6]
And of course, you can put this also inline into the list comprehension:
>>> [x * 2 if i < n and x % 2 == 1 else x for i, x in enumerate(lst)]
[2, 2, 3, 4, 5, 6]
First n odd numbers:
If you want to find the first n odd numbers, you cannot solve this like this. In order to solve this, you need to actually remember how many odd numbers you encountered before while going through the list. This means that you need to have some kind of “memory”. Such a thing is not a good fit for a list comprehension since list comprehensions are supposed to transform one item at a time without having side effects.
So instead, you would simply do this the straightforward way:
n = 2
lst = [2, 2, 2, 2, 1, 2, 3]
result = []
for x in lst:
if x % 2 == 1 and n > 0:
result.append(x * 2)
n -= 1
else:
result.append(x)
print(result) # [2, 2, 2, 2, 2, 2, 6]
For this to work, you'll need to keep count of odd numbers that you've already seen. For example, you could instantiate the itertools.count generator and advance it each time the odd number is encountered:
from itertools import count
def f(l, n):
odd = count()
return [x * 2 if x % 2 and next(odd) < n else x for x in l]
>>> f([1, 2, 3, 4, 5, 6], 2)
[2, 2, 6, 4, 5, 6]
>>> f([2, 2, 2, 2, 1, 2, 3], 2)
[2, 2, 2, 2, 2, 2, 6]
Use the ternary operator.
lst = [1, 2, 3, 4, 5, 6]
lst = [x * 2 if x % 2 == 1 and i <= n else x for i, x in enumerate(lst)]
or
lst[:n] = [x * 2 if x % 2 == 1 else x for x in lst[:n]]
Update: Under the new requirement of doubling first n odd integers:
lst = [1, 2, 3, 4, 5, 6]
class Doubler:
def __init__(self, n):
self.n = n
def proc(self, x):
if self.n > 0 and x % 2:
self.n -= 1
return 2 * x
return x
# Double first 2 odd elements
d = Doubler(n=2)
res = [d.proc(x) for x in lst]
print(res)
# [2, 2, 6, 4, 5, 6]
Name things with specificity, and the logic is exposed.
How can I double the first n odd numbers in a list using list comprehension?
We have odd numbers: v for v in l if n%2. This is a filter.
We can take the first n of them using islice(odds, n). We call this a slice, other languages might call it "take". And doubling them is a per item operation, so a map. Join these operations and we arrive at one answer to your question:
[v*2 for v in islice((v for v in l if n%2), n)]
However, that isn't what you wanted. The issue is specificity; your question doesn't say what to do with other items than the first n odd ones, so I have just ignored them.
So what do do if we want a replication of all the items your question did not mention? This means we have three groups: early odds, late odds, and evens, all processed distinctly. The latter may be mixed in arbitrarily, while we know late odds come after early odds. It's impractical to split them in individual streams, as you've shown, since that doesn't preserve their relative order.
I'll apply a few more itertools functions to solve this problem.
from itertools import repeat, chain
oddfactors = chain(repeat(2, n), repeat(1))
outlist = [v*next(oddfactors) if v%2 else v
for v in inlist]
Note that the iterator oddfactors is read for each odd item, not even items, because the if-else expression doesn't evaluate the expression if it's not being used. The iterator is consumed and you need to create another to perform the work again.
It is possible to place the oddfactors iterator's creation (and entire scope) within the list comprehension, but the first way I can think of is incredibly ugly:
from itertools import repeat, chain
outlist = [v*next(oddfactors) if v%2 else v
for v,oddfactors in zip(
inlist,
repeat(chain(repeat(2, n), repeat(1)))
)]
The trick here is to ensure we create the chained iterator only once, then feed it into each mapping operation. This exercise sure didn't help readability or performance. Using a nested comprehension would make it a bit cleaner but there's still only the one iterator, so it's a misleading hack.
outlist = [v*next(oddfactors) if v%2 else v
for oddfactors in [chain(repeat(2, n), repeat(1))]
for v in inlist]
How about this?
n = 2
lst = [1, 2, 3, 4, 5, 6]
for i in range(n):
lst[i]= lst[i]*2
[num if num%2 else 2*num for num in list]. num if a if b else c will return a if b is true, otherwise c.

How to add two nested lists in parallel and append result to a new list in python

I'm trying to add all the elements of two unequal nested lists in parallel and append the result back to another new list, i've written a little hacky code that could add them but there's a lot of things wrong with the code, first i tried to make the pairs equal by appending 0's to the end of the list but the code still runs into the problems since the length of the first pair is 3 and the length of the second pair is 4, i also tried using map but i couldn't add an integer and a NoneType,
import pdb
import itertools
x = [[2,3,3], [5,0,3]]
y = [[0,3], [2,3,3,3]]
for idx, (a, b) in enumerate(itertools.zip_longest(x, y)):
while len(a) < len(b):
x[idx].append(0)
while len(b) < len(a):
y[idx].append(0)
print(x, y)
new_list = list()
for i in zip(x, y):
for idx, j in enumerate(i):
for ind, a in enumerate(j):
val = x[idx][ind] + y[idx][ind]
new_list.append(val)
print(new_list)
the final result should be like this
[2, 6, 3, 7, 3, 6, 3]
You can simply use itertools.zip_longest and fill-in with 0, like this
>>> from itertools import zip_longest as zip
>>> x = [[2, 3, 3], [5, 0, 3]]
>>> y = [[0, 3], [2, 3, 3, 3]]
>>> [k + l for i, j in zip(x, y, fillvalue=[0]) for k, l in zip(i, j, fillvalue=0)]
[2, 6, 3, 7, 3, 6, 3]
This would would work even if x and y have unequal number of elements,
>>> from itertools import zip_longest as zip
>>> x = [[2, 3, 3], [5, 0, 3], [1]]
>>> y = [[0, 3], [2, 3, 3, 3]]
>>> [k + l for i, j in zip(x, y, fillvalue=[0]) for k, l in zip(i, j, fillvalue=0)]
[2, 6, 3, 7, 3, 6, 3, 1]
Note that, when we zip x and y, we use [0] as fillvalue. And when we zip i and j we use 0 as the fillvalue.
So, if the number of lists in x and y are not equal, then [0] will be used fill-in and when the number of elements in i and j are not equal, 0 will be used as the fill-in.
You can use fillvalue=0 in izip_longest to get ride of checking for validations then use map function for sum the zipped items:
from itertools import chain,zip_longest
list(chain.from_iterable(map(sum,zip_longest(i,j,fillvalue=0)) for i,j in zip_longest(x, y)))
[2, 6, 3, 7, 3, 6, 3]
Note that if you want to iterate over the result you don't have to use list (its just for demonstrating the result).
zip_longest is very helpful here:
x = [[2,3,3], [5,0,3]]
y = [[0,3], [2,3,3,3]]
from itertools import zip_longest
res = []
for list1, list2 in zip_longest(x, y, fillvalue=[0]):
for value1, value2 in zip_longest(list1, list2, fillvalue=0):
res.append(value1 + value2)
The fill value pads the list or sublist with the given value. In our case a new list with [0] for the outer loop and 0 for the inner loop.
Writing this a nested list comprehension does the same but may take more time to read and understand the code. Using more lines can make reading and understanding faster. Of course, this depends very much on the person reading the code.
from itertools import zip_longest
new_list = [a + b
for listpair in zip(x, y)
for a, b in zip_longest(*listpair, fillvalue=0)]

Sum one number to every element in a list (or array) in Python

Here I go with my basic questions again, but please bear with me.
In Matlab, is fairly simple to add a number to elements in a list:
a = [1,1,1,1,1]
b = a + 1
b then is [2,2,2,2,2]
In python this doesn't seem to work, at least on a list.
Is there a simple fast way to add up a single number to the entire list.
Thanks
if you want to operate with list of numbers it is better to use NumPy arrays:
import numpy
a = [1, 1, 1 ,1, 1]
ar = numpy.array(a)
print ar + 2
gives
[3, 3, 3, 3, 3]
using List Comprehension:
>>> L = [1]*5
>>> [x+1 for x in L]
[2, 2, 2, 2, 2]
>>>
which roughly translates to using a for loop:
>>> newL = []
>>> for x in L:
... newL+=[x+1]
...
>>> newL
[2, 2, 2, 2, 2]
or using map:
>>> map(lambda x:x+1, L)
[2, 2, 2, 2, 2]
>>>
You can also use map:
a = [1, 1, 1, 1, 1]
b = 1
list(map(lambda x: x + b, a))
It gives:
[2, 2, 2, 2, 2]
try this. (I modified the example on the purpose of making it non trivial)
import operator
import numpy as np
n=10
a = list(range(n))
a1 = [1]*len(a)
an = np.array(a)
operator.add is almost more than two times faster
%timeit map(operator.add, a, a1)
than adding with numpy
%timeit an+1
If you don't want list comprehensions:
a = [1,1,1,1,1]
b = []
for i in a:
b.append(i+1)

Categories

Resources