4
votes

Meilleur moyen / rapide de supprimer une colonne d'une liste Python 2D

J'ai une liste de listes de listes (toutes les listes ont la même taille) en python comme ceci:

A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]

Je souhaite supprimer certaines colonnes (i-ème éléments de toutes les listes).

Y a-t-il un moyen de faire cela sans for déclarations?


3 commentaires

avec numpy oui. Sinon, vous avez besoin d'au moins 1 for


Pourquoi voudriez-vous le faire sans une déclaration for? Bien sûr, vous pouvez remplacer les boucles for par des boucles while mais cela ne fera que rendre votre code moins lisible


Fondamentalement, même numpy utilise une implémentation de boucle. Bien être pédant, nous pouvons utiliser un while ici boucle pour le faire aussi


5 Réponses :


1
votes

Vous pouvez facilement utiliser la compréhension de liste et les tranches :

A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]
k = 1

B = [l[:k]+l[k+1:] for l in A]

print(B) # >> returns [[1, 3, 4], ['a', 'c', 'd'], [12, 14, 15]]


4 commentaires

cela a toujours un for , peut-être pouvez-vous inclure une carte?


La compréhension des listes n'est pas plus lente que la cartographie, et elles sont plus lisibles ... Et je pensais que le demandeur voulait éviter l'utilisation de la boucle for , pas d'une boucle O (1) ...


@ olinox14 Je suis d'accord que la demande de "no for loops" est un peu inutile, puisque toutes les autres approches ont des boucles quelque part cachées à l'intérieur, mais qu'est-ce qu'une "boucle O (1)"?


C'est le moyen standard de mesurer la complexité d'un algorithme, jetez un œil ici ou ici



3
votes

Je pense que vous pouvez le faire sans for si vous maîtrisez avec zip (c'est mon « hack » préféré):

B = list(map(lambda l: l[:i] + l[i + 1:], A))

Résultat (i = 2):

[[1, 2, 4], ['a', 'b', 'd'], [12, 13, 15]]

Bien sûr, la map est une alternative à la compréhension de liste:

A = [[1, 2, 3, 4], ['a', 'b', 'c', 'd'], [12, 13, 14, 15]]
B = list(zip(*A))
B.pop(i)
C = list(map(list, zip(*B)))


2 commentaires

Je suis presque sûr que les performances souffrent par rapport à la compréhension standard à cause de tous les objets temporaires créés. Mais je peux me tromper. Et map(lambda n'est pas recommandé


@ Jean-FrançoisFabre Bien sûr que vous avez raison, mais les objets zip et map devraient être comparables aux générateurs (mais pas aux listes de compréhension).



3
votes

numpy est capable de supprimer des colonnes entières:

[['1' '2' '3']
 ['a' 'b' 'c']
 ['12' '13' '14']]

[['2' '3' '4']
 ['b' 'c' 'd']
 ['13' '14' '15']]

[['1' '2' '4']
 ['a' 'b' 'd']
 ['12' '13' '15']]

résultat (par souci de simplicité: toutes les données ont été converties en chaîne, aucun dtype n'est impliqué):

import numpy

A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]

na = numpy.array(A)

print(na[:,:-1])   # remove last column
print(na[:,1:])    # remove first column

print(numpy.concatenate((na[:,:2],na[:,3:]),axis=1)) # build from 2 slices: remove third column


0 commentaires

5
votes

Comme mentionné, vous ne pouvez pas faire cela sans boucle. Cependant, en utilisant des fonctions intégrées, voici une approche fonctionnelle qui n'utilise explicitement aucune boucle:

In [41]: arr = A * 10000

In [42]: %timeit remove_col_functional(arr, 2)
8.42 ms ± 37.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [43]: %timeit remove_col_list_com(arr, 2)
23.7 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

# And if in functional approach you just return map(itg, arr)
In [47]: %timeit remove_col_functional_iterator(arr, 2)
1.48 µs ± 4.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Démo:

In [31]: def remove_col(arr, ith):
    ...:     return [[j for i,j in enumerate(sub) if i != ith] for sub in arr]

Notez qu'au lieu de list(map(list, map(itg, arr))) si vous ne renvoyez que map(itg, arr) il vous donnera le résultat attendu mais comme un itérateur d'itérateurs au lieu de liste de listes. Ce sera une approche plus optimisée en termes de mémoire et d'exécution dans ce cas.

De plus, en utilisant des boucles, voici la façon dont je procéderais:

In [26]: remove_col(A, 1)
Out[26]: [[1, 3, 4], ['a', 'c', 'd'], [12, 14, 15]]

In [27]: remove_col(A, 3)
Out[27]: [[1, 2, 3], ['a', 'b', 'c'], [12, 13, 14]]

Étonnamment (pas si vous croyez en la puissance de C :)), l'approche fonctionnelle est encore plus rapide pour les grands tableaux.

In [24]: from operator import itemgetter

In [25]: def remove_col(arr, ith):
    ...:     itg = itemgetter(*filter((ith).__ne__, range(len(arr[0]))))
    ...:     return list(map(list, map(itg, arr)))
    ...: 


3 commentaires

J'aime vraiment votre approche "utilisant des boucles" ... presque comme quelque chose que je pourrais proposer . :-P


Hm, ne m'attendais pas à ça. Bien que, lorsque je le teste, la différence n'est pas aussi dramatique. De plus, à mon humble avis, le timing de la création de l'itérateur est un peu idiot, vous ne pouvez pas vraiment les comparer.


@tobias_k Bien sûr, c'est là juste comme un prof à l'explication ci-dessus. En ce qui concerne la différence, ce n'est pas si dramatique ici aussi, et ne soyez pas surpris car c'est la puissance de C et la loi des abstractions qui fuient .



3
votes

Une autre variante utilisant une liste-compréhension, avec enumerate :

>>> A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]
>>> k = 2
>>> [[x for i, x in enumerate(a) if i != k] for a in A]
[[1, 2, 4], ['a', 'b', 'd'], [12, 13, 15]]

Et, oui, cela a le mot for cela (deux fois même!), Mais les performances ne devraient pas être différentes de celles des autres approches ( numpy pourrait être plus rapide, cependant).


0 commentaires