Python: รายการของ dict หากมีอยู่แล้วให้เพิ่มค่า dict หากไม่มีการต่อท้าย dict ใหม่


107

ฉันอยากทำอะไรแบบนั้น

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})

ฉันจะทำอย่างไร? ฉันไม่รู้ว่าควรเอาทูเพิลไปแก้ไขหรือหาดัชนีทูเปิลดี

ความช่วยเหลือใด ๆ

คำตอบ:


207

นั่นเป็นวิธีการจัดระเบียบสิ่งของที่แปลกมาก หากคุณเก็บไว้ในพจนานุกรมสิ่งนี้ทำได้ง่าย:

# 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

รหัสสำหรับอัปเดตพจนานุกรมจำนวนนับนี้เป็น "รูปแบบ" ทั่วไปใน Python เป็นเรื่องปกติมากที่มีโครงสร้างข้อมูลพิเศษdefaultdictสร้างขึ้นเพื่อให้ง่ายยิ่งขึ้น:

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

หากคุณเข้าถึงdefaultdictโดยใช้คีย์และคีย์ไม่ได้อยู่ในไฟล์defaultdictคีย์จะถูกเพิ่มโดยอัตโนมัติด้วยค่าเริ่มต้น defaultdictเตะ callable คุณผ่านและเรียกมันว่าจะได้รับค่าเริ่มต้น ในกรณีนี้เราผ่านในชั้นเรียนint; เมื่อ Python เรียกint()มันจะส่งกลับค่าศูนย์ ดังนั้นในครั้งแรกที่คุณอ้างอิง URL จำนวนของมันจะเริ่มต้นเป็นศูนย์จากนั้นคุณจะเพิ่มหนึ่งในการนับ

แต่พจนานุกรมที่เต็มไปด้วยจำนวนนับก็เป็นรูปแบบทั่วไปเช่นกันดังนั้น Python จึงจัดเตรียมคลาสที่พร้อมใช้งาน: containers.Counter คุณเพียงแค่สร้างCounterอินสแตนซ์โดยการเรียกคลาสโดยส่งผ่านไปยังสิ่งที่ทำซ้ำได้ มันสร้างพจนานุกรมโดยที่คีย์เป็นค่าจากการทำซ้ำและค่าจะนับจำนวนครั้งที่คีย์ปรากฏในการทำซ้ำ จากนั้นตัวอย่างข้างต้นจะกลายเป็น:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

หากคุณจำเป็นต้องทำอย่างที่คุณแสดงจริงๆวิธีที่ง่ายและเร็วที่สุดคือใช้หนึ่งในสามตัวอย่างนี้จากนั้นสร้างแบบที่คุณต้องการ

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()]

หากคุณใช้ Python 2.7 หรือใหม่กว่าคุณสามารถทำได้ในซับเดียว:

from collections import Counter

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

ฉันชอบที่จะส่งไปยังเทมเพลต django ดังนั้นฉันจึงสามารถทำ: {% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim

3
คุณยังสามารถทำ {% สำหรับ url, nbr ใน urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw

161

การใช้ค่าเริ่มต้นได้ผล แต่จะทำเช่นนั้น:

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

โดยใช้.getคุณจะได้รับผลตอบแทนเริ่มต้นหากไม่มีอยู่ โดยค่าเริ่มต้นจะไม่มี แต่ในกรณีที่ฉันส่งให้คุณมันจะเป็น 0


13
อันที่จริงฉันคิดว่านี่เป็นคำตอบที่ดีที่สุดเนื่องจากไม่เชื่อเรื่องพระเจ้าในพจนานุกรมที่กำหนดซึ่งเป็นโบนัสจำนวนมาก
Bouncner

นี่เป็นวิธีการแก้ปัญหาที่ดี
Dylan Hogg

2
นี่น่าจะเป็นคำตอบ มีประสิทธิภาพสะอาดและตรงประเด็น !! ฉันหวังว่า stackoverflow จะช่วยให้ชุมชนตัดสินใจคำตอบพร้อมกับโปสเตอร์คำถามได้
mowienay

ชอบคำตอบนี้จริงๆ แต่ใช้ไม่ได้ถ้าคีย์คือไม่มี ^^ หรือดี ... ต้องการขั้นตอนเพิ่มเติม ...
เซดริก



3

จะทำในแบบของคุณหรือไม่? คุณสามารถใช้โครงสร้าง for ... else

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))

แต่มันค่อนข้างไม่สง่างาม คุณต้องจัดเก็บ URL ที่เข้าชมเป็น LIST หรือไม่? ตัวอย่างเช่นหากคุณจัดเรียงเป็นคำสั่งที่จัดทำดัชนีด้วยสตริง url จะเป็นวิธีที่สะอาดกว่า:

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)

สิ่งที่ควรทราบในตัวอย่างที่สองนั้น:

  • ดูว่าการใช้คำสั่งเพื่อurlsขจัดความจำเป็นในการurlsดูรายการทั้งหมดเมื่อทดสอบหนึ่งรายการurlรายการ แนวทางนี้จะเร็วขึ้น
  • การใช้dict( )แทนวงเล็บปีกกาทำให้โค้ดของคุณสั้นลง
  • โดยใช้list_of_urls, urlsและurlเป็นชื่อตัวแปรทำให้โค้ดค่อนข้างยากที่จะแยก มันจะดีกว่าที่จะหาสิ่งที่ชัดเจนเช่นurls_to_visit, และurls_already_visited current_urlฉันรู้ว่ามันนานกว่านั้น แต่ชัดเจนกว่า.

และแน่นอนว่าฉันคิดว่านั่นdict(url='http://www.google.fr', nbr=1)เป็นการทำให้โครงสร้างข้อมูลของคุณง่ายขึ้นเพราะมิฉะนั้นurlsอาจเป็นเพียง:

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

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

ซึ่งจะดูสง่างามมากด้วยท่าทางdefaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1

เวอร์ชันที่สองดีเนื่องจากฉันสามารถแปลงคำสั่งเป็นรายการหลังได้
Natim

3

ยกเว้นครั้งแรกทุกครั้งที่เห็นคำว่าการทดสอบคำสั่ง if ล้มเหลว หากคุณกำลังนับคำจำนวนมากหลายคำอาจเกิดขึ้นหลายครั้ง ในสถานการณ์ที่การเริ่มต้นของค่าจะเกิดขึ้นเพียงครั้งเดียวและการเพิ่มค่านั้นจะเกิดขึ้นหลายครั้งการใช้คำสั่ง try จะถูกกว่า:

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

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่ https://wiki.python.org/moin/PythonSpeed/PerformanceTips

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.