10
votes

Y a-t-il une meilleure façon pythonique de faire cela?

Ceci est mon premier programme Python -

Exigence: Lisez un fichier composé de {userid de l'ADID} dans chaque ligne. Pour chaque ADID, imprimez le nombre d'userids uniques.

Voici mon code, placez-la de la lecture des documents Python. Pourriez-vous me donner des commentaires sur la façon dont je peux écrire cela en plus de python-ish?

code: xxx

merci.


2 commentaires

+1 J'aime l'intention derrière votre message - vous apprendrez également beaucoup en parcourant d'autres questions sous la balise Python ici au débordement de la pile.


@Mark - Je reçois beaucoup ça, généralement quelqu'un me demande si mon nom d'utilisateur doit être prononcé comme Chicago. M'a pris un moment pour l'obtenir pour la première fois que quelqu'un m'a cependant demandé. Mais non, mon prénom commence par un nom S et Mylast est Chitti, prononcé Cheeeee.


8 Réponses :


7
votes

Vous pouvez raccourcir la boucle pour cela:

for row in reader:
  adDict.setdefault(row[0], set()).add(row[1])


2 commentaires

+1 - Nice! J'aurais aimé savoir la méthode SetDefault plus tôt!


Cool - Seul SetDefault (rangée [0], Set ()) renvoie une instance de jeu, puis nous faisons une ajout sur cela.



1
votes

Les seules modifications que je ferais sont extraites de plusieurs éléments du lecteur à la fois et à l'aide de formatage de chaîne pour les instructions d'impression.

import csv

adDict = {}
reader = csv.reader(open("some.csv"), delimiter=' ')
# Can extract multiple elements from a list in the iteration statement:
for adId, userId in reader: 
    if ( adId in adDict ):
        adDict[adId].add(userId)
    else:
        adDict[adId] = set(userId)

for key, value in adDict.items():
    # I believe this gives you more control over how things are formatted:
    print ("%s, %d" % (key, len(value)))


1 commentaires

+1 - Bien que Notez que Python organisera une exception si le fichier CSV adopté n'a pas exactement 2 colonnes dans chaque rangée.



18
votes

Félicitations, votre code est très gentil. Il y a quelques petites astuces que vous pourriez utiliser pour le rendre plus court / plus simple.

Il y a un type d'objet Nifty appelé DablierDict fourni par le module Collections. Au lieu de devoir vérifier si l'addict a une clé ADID, vous pouvez configurer un indice de défaillance qui agit comme une dicte régulière, sauf qu'il vous fournit automatiquement un ensemble vide () lorsqu'il n'y a pas de clé. Vous pouvez donc changer p> xxx pré>

à simplement p> xxx pré>

aussi, au lieu de p> xxx Pré>

Vous pouvez raccourcir cela à p>

#!/usr/bin/env python
import csv
from collections import defaultdict

adDict = defaultdict(set)
reader = csv.reader(open("some.csv"), delimiter=' ')
for adId,userId in reader:
    adDict[adId].add(userId)
for key,value in adDict.iteritems():
    print (key, ',' , len(value))


8 commentaires

Cool - Je vais lire des collections pour des astuces plus astucieuses. En outre, pourquoi dirions-nous «Importer CSV» mais avoir à dire «des collections importer des idées de défaillance»?


@Schitti - en utilisant à partir d'un raccourci. Cela signifie que vous pouvez taper defaultdict plutôt que Collections.defaultDict. Il est totalement facultatif, cependant, en important le symbole defaultDict dans l'espace de noms du module, il y a d'autres effets utiles qu'un programmeur avancé pourrait aimer.


@Schitti - une autre forme fonctionnera. Si vous souhaitez simplement utiliser l'instruction Importer , vous pouvez dire importer des collections.defaultDict - mais à chaque fois que vous l'utilisez, vous devez y référer comme Collections.DefaultDict . Au lieu de cela, comme vous pouvez le constater dans la réponse de ~ Unutbu, vous devez maintenant seulement référence à celui-ci comme defaultDict . Vous pouvez également modifier le nom d'un module lorsque vous l'importaez; Par exemple, importer des collections.defaultDict comme défdi ou à partir de collections Importation de défautDict comme défdi - Vous pouvez ensuite utiliser défdi au lieu de defaultdict < / code>


pour la clé, la valeur dans d.items () est une bien meilleure pratique que pour la clé en D: valeur = D [clé] . La première version visit chaque élément exactement une fois. La deuxième version visit chaque élément une fois pour obtenir la liste des touches, puis doit ensuite rechercher chaque clé de la dicte. C'est O (n) versus O (n * journal n). Je pense que dans ce cas particulier, l'argument de la lisibilité pourrait aller dans les deux sens, mais la différence de performance est suffisamment importante (en particulier pour les grands dicts) que la première version est le gagnant défini.


@Parker, merci pour votre commentaire intéressant! Tu m'avais convaincu, mais j'ai essayé d'écrire du code de démonstration. À ma surprise, il semble que clé en d peut être plus rapide que la touche , valeur dans d.items () . Le code est le code que j'ai testé: Coller.ubuntu.com/341327 . Python -MtimeIT -S "Test d'importation" "Test.key_in_d ()" a pris 409 ms par boucle, python -mtime -s "test d'importation" "test.key_in_d_items ()" a pris 631 ms par boucle. Qu'est-ce que tu penses?


~ ~ Unutbu, quelques choses. Je suppose que vous êtes sur Python2 où articles () renvoie une liste. Je pensais à Python3 où il retourne un générateur. Pour obtenir le même comportement dans Python2, vous devez utiliser les iTeritems quelque peu plus laids (). Deuxièmement, vous ne faites rien avec valeur à l'intérieur de la boucle. C'est peut-être que Python était assez intelligent pour simplement optimiser les parties de celui-ci. Essayez peut-être de créer une liste de touches, de valoriser les tuples simplement pour vous assurer que les deux parties sont utilisées.


@ ~ Unutbu, je viens de le tester ici. "Pour la clé en D" était de 335ms, "pour la clé, la valeur en D.Items ()" était en effet plus élevée à 561 ms, mais "pour la clé, la valeur de D.iteritems ()" était le gagnant défini à 206 ms. De toute évidence, pour un si grand dict, la création d'une liste des paires de la valeur clé a pris assez de temps et de mémoire. :)


@Parker: merveilleux. Merci d'avoir souligné cela. Je monte ma réponse en conséquence.



1
votes

Quelques morceaux et quelques morceaux:

pour extraire la liste des lignes dans des variables: p> xxx pré>

La déclaration IF n'a pas besoin d'accolades: P>

try:
    adDict[adId].add(userId)
except KeyError:
    adDict[adId] = set(userId)


1 commentaires

Oui, bien que peut-être l'inconvénient est que la logique basée sur des exceptions est plus difficile à comprendre.



3
votes

au lieu de: xxx pré>

Utiliser une séquence automatique de la séquence Déballage: p> xxx pré>

in: p> xxx pré >

Vous n'avez pas besoin de parenthèses. p>

au lieu de: p> xxx pré>

Utilisez defaultdict code>: P> xxx pré>

ou, si vous n'êtes pas autorisé à utiliser d'autres modules par votre professeur, utilisez setingdefault () code>: p> xxx pré >

Lors de l'impression: p> xxx pré>

à l'aide de la formatage de chaîne peut être plus facile à formater: p> xxx pré>

ou, si vous 'RE UTILISATION PYTHON 3: P>

print ("{0},{1}".format (key, len(value)))


2 commentaires

Merci John! Je suis dans l'industrie codant dans c au cours des 5 dernières années, donc aucun professeur


Ah désolé; Ce type de problème est souvent assigné que les devoirs pour commencer les étudiants.



3
votes

Étant donné que vous n'avez qu'un fichier space délimité, je ferais:

from __future__ import with_statement
from collections import defaultdict

ads = defaultdict(set)
with open("some.csv") as f:
    for ad, user in (line.split(" ") for line in f):
        ads[ad].add(user)

for ad in ads:
    print "%s, %s" % (ad, len(ads[ad]))


3 commentaires

Merci. Cela évite de créer une nouvelle classe de lecteur que je présume. Impossible de vous uppoter depuis ma réputation <15.


J'aime ça. J'allais juste vous recommander d'utiliser defaultDict (défini) . Cependant, ce code décompose chaque ligne dans un tuple, en supposant que chaque ligne ne comporte que deux éléments. Le code de l'OP n'a pas fait cette hypothèse.


@HUGHDBROWN: Vous avez raison sur l'hypothèse que chaque ligne n'a que deux éléments. Cependant, la question de l'OP affirme explicitement que son dossier n'a que deux éléments sur chaque ligne. Dans ce cas, je pense qu'il est acceptable de simplement échouer (avec une erreur de déballage de tuple) s'il existe un nombre différent d'éléments sur une ligne. Évidemment, dans un script plus durci de production, vous voudrez coder plus de manière défensive.



10
votes

La ligne de code: xxx

est peu susceptible de faire ce que vous voulez - il traitera de la chaîne userid comme une séquence de lettres, donc par exemple si userID était AleAx Vous obtiendriez un ensemble avec quatre articles, comme, par exemple, Set (['a', 'L', 'E', 'E', 'x']) . Plus tard, un .add (userid) Lorsque userid est AleAx ajoutera à nouveau un cinquième élément, la chaîne 'Aleax' , parce que .add (différemment de l'initialiseur de jeu, qui prend un appareil itable comme argument) prend un seul élément comme argument.

Pour faire un ensemble avec un seul Article, utilisez SET ([ID utilisateur]) à la place.

C'est un bug raisonnablement fréquent pour que je voulais donc l'expliquer clairement. Qui étant dit, defaultDict comme suggéré dans d'autres réponses est clairement la bonne approche (évitez setingdefault , qui n'a jamais été une bonne conception et n'a pas de bonnes performances non plus, non plus, comme étant jolie trouble).

Je voudrais également éviter le kenta-overkill de csv en faveur d'une boucle simple avec un .split et .strip sur chaque ligne ... < / p>


1 commentaires

Oui à Collections.DefaultDict (SET) .



3
votes

Il y a de bonnes réponses ici.

Un astuce que j'aime particulièrement est de rendre mon code plus facile à réutiliser à l'avenir, comme xxx

maintenant que vous pouvez importer votre analyseur CSV d'un autre module et obtenez un accès programmatique à Adict.


0 commentaires