Python: Liste der Diktate, falls vorhanden, erhöhen Sie einen Diktatwert, wenn Sie kein neues Diktat anhängen

107

Ich würde gerne so etwas machen.

list_of_urls = ['http://www.google.fr/', 'http://www.google.fr/', 
                'http://www.google.cn/', 'http://www.google.com/', 
                'http://www.google.fr/', 'http://www.google.fr/', 
                'http://www.google.fr/', 'http://www.google.com/', 
                'http://www.google.fr/', 'http://www.google.com/', 
                'http://www.google.cn/']

urls = [{'url': 'http://www.google.fr/', 'nbr': 1}]

for url in list_of_urls:
    if url in [f['url'] for f in urls]:
         urls[??]['nbr'] += 1
    else:
         urls.append({'url': url, 'nbr': 1})

Wie kann ich ? Ich weiß nicht, ob ich das Tupel nehmen soll, um es zu bearbeiten oder die Tupelindizes herauszufinden?

Irgendeine Hilfe ?

Natim
quelle

Antworten:

207

Das ist eine sehr seltsame Art, Dinge zu organisieren. Wenn Sie in einem Wörterbuch gespeichert haben, ist dies einfach:

# This example should work in any version of Python.
# urls_d will contain URL keys, with counts as values, like: {'http://www.google.fr/' : 1 }
urls_d = {}
for url in list_of_urls:
    if not url in urls_d:
        urls_d[url] = 1
    else:
        urls_d[url] += 1

Dieser Code zum Aktualisieren eines Wörterbuchs der Zählungen ist ein allgemeines "Muster" in Python. Es ist so üblich, dass es eine spezielle Datenstruktur gibt defaultdict, die erstellt wurde, um dies noch einfacher zu machen:

from collections import defaultdict  # available in Python 2.5 and newer

urls_d = defaultdict(int)
for url in list_of_urls:
    urls_d[url] += 1

Wenn Sie defaultdictmit einem Schlüssel auf das zugreifen und der Schlüssel noch nicht in der vorhanden ist defaultdict, wird der Schlüssel automatisch mit einem Standardwert hinzugefügt. Das defaultdictnimmt das von Ihnen übergebene Callable und ruft es auf, um den Standardwert zu erhalten. In diesem Fall haben wir in der Klasse bestanden int; Wenn Python aufruft int(), wird ein Nullwert zurückgegeben. Wenn Sie also zum ersten Mal auf eine URL verweisen, wird deren Anzahl auf Null initialisiert, und dann fügen Sie der Anzahl eine hinzu.

Ein Wörterbuch voller Zählungen ist jedoch auch ein gängiges Muster. Daher bietet Python eine gebrauchsfertige Klasse: containers.Counter Sie erstellen einfach eine CounterInstanz, indem Sie die Klasse aufrufen und eine beliebige iterierbare Klasse übergeben. Es wird ein Wörterbuch erstellt, in dem die Schlüssel Werte aus der Iterable sind und die Werte angeben, wie oft der Schlüssel in der Iterable angezeigt wurde. Das obige Beispiel wird dann:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Wenn Sie es wirklich so machen müssen, wie Sie es gezeigt haben, ist es am einfachsten und schnellsten, eines dieser drei Beispiele zu verwenden und dann das zu erstellen, das Sie benötigen.

from collections import defaultdict  # available in Python 2.5 and newer

urls_d = defaultdict(int)
for url in list_of_urls:
    urls_d[url] += 1

urls = [{"url": key, "nbr": value} for key, value in urls_d.items()]

Wenn Sie Python 2.7 oder höher verwenden, können Sie dies in einem Einzeiler tun:

from collections import Counter

urls = [{"url": key, "nbr": value} for key, value in Counter(list_of_urls).items()]
steveha
quelle
Ich mag es, wenn ich es an eine Django-Vorlage sende, damit ich Folgendes tun kann: `{% für dich in URLs%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim
3
Sie können immer noch {% für URL, nbr in urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
160

Die Verwendung der Standardeinstellung funktioniert, aber auch:

urls[url] = urls.get(url, 0) + 1

Mit .getkönnen Sie eine Standardrückgabe erhalten, wenn diese nicht vorhanden ist. Standardmäßig ist es Keine, aber in dem Fall, dass ich Sie gesendet habe, wäre es 0.

mikelikespie
quelle
12
Eigentlich denke ich, dass dies die beste Antwort ist, da es für das gegebene Wörterbuch agnostisch ist, was ein großer Bonus imo ist.
Bouncner
Dies ist eine schöne saubere Lösung.
Dylan Hogg
1
Dies sollte die Antwort sein. Effizient, sauber und auf den Punkt !! Ich hoffe, dass der Stackoverflow es der Community ermöglicht, die Antwort zusammen mit dem Fragenplakat zu entscheiden.
Mowienay
Wirklich wie diese Antwort funktioniert einfach nicht, wenn der Schlüssel Keine ist ^^ Oder gut ... Benötigt noch ein paar Schritte ...
Cedric
25

Verwenden Sie defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Greg Hewgill
quelle
17

Das funktioniert bei mir immer gut:

for url in list_of_urls:
    urls.setdefault(url, 0)
    urls[url] += 1
Mossplix
quelle
3

Um es genau so zu machen wie du es willst? Sie können die for ... else-Struktur verwenden

for url in list_of_urls:
    for url_dict in urls:
        if url_dict['url'] == url:
            url_dict['nbr'] += 1
            break
    else:
        urls.append(dict(url=url, nbr=1))

Aber es ist ziemlich unelegant. Müssen Sie die besuchten URLs wirklich als LISTE speichern? Wenn Sie es als Diktat sortieren, das beispielsweise durch eine URL-Zeichenfolge indiziert ist, wäre es viel sauberer:

urls = {'http://www.google.fr/': dict(url='http://www.google.fr/', nbr=1)}

for url in list_of_urls:
    if url in urls:
        urls[url]['nbr'] += 1
    else:
        urls[url] = dict(url=url, nbr=1)

In diesem zweiten Beispiel sind einige Dinge zu beachten:

  • Sehen Sie, wie Sie mit einem Diktat urlsdie gesamte urlsListe durchgehen müssen, wenn Sie nach einem einzigen testen url. Dieser Ansatz wird schneller sein.
  • Die Verwendung dict( )anstelle von geschweiften Klammern verkürzt Ihren Code
  • mit list_of_urls, urlsund urlals Variablennamen der Code sehr schwer zu analysieren machen. Es ist besser , etwas klarer, wie zu finden urls_to_visit, urls_already_visitedund current_url. Ich weiß, es ist länger. Aber es ist klarer.

Und natürlich gehe ich davon aus, dass dies dict(url='http://www.google.fr', nbr=1)eine Vereinfachung Ihrer eigenen Datenstruktur ist, denn sonst urlskönnte es einfach sein:

urls = {'http://www.google.fr':1}

for url in list_of_urls:
    if url in urls:
        urls[url] += 1
    else:
        urls[url] = 1

Was mit der Standardeinstellung sehr elegant werden kann :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Nicolas Dumazet
quelle
Die zweite Version ist gut, da ich das Diktat danach als Liste konvertieren kann.
Natim
3

Mit Ausnahme des ersten Males schlägt der Test der if-Anweisung jedes Mal fehl, wenn ein Wort angezeigt wird. Wenn Sie eine große Anzahl von Wörtern zählen, werden viele wahrscheinlich mehrmals vorkommen. In einer Situation, in der die Initialisierung eines Werts nur einmal erfolgt und die Erhöhung dieses Werts um ein Vielfaches erfolgt, ist es billiger, eine try-Anweisung zu verwenden:

urls_d = {}
for url in list_of_urls:
    try:
        urls_d[url] += 1
    except KeyError:
        urls_d[url] = 1

Weitere Informationen hierzu finden Sie unter: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Pilatipus
quelle