Как оптимизировать поиск 10 наиболее часто встречающихся слов внутри объекта данных json? - PullRequest
0 голосов
/ 26 декабря 2018

Я ищу способы сделать код более эффективным (время выполнения и сложность памяти). Должен ли я использовать что-то вроде Max-Heap?Не плохая производительность из-за конкатенации строк или сортировки словаря не на месте или что-то еще? Редактировать: Я заменил объект словаря / карты на применение метода Counter в списке всех найденных имен (с дубликатами)

минимальный запрос: скрипт должен занимать меньшезатем 30 секунд текущее время выполнения: это занимает 54 секунды

   # Try to implement the program efficiently (running the script should take less then 30 seconds)
import requests

# Requests is an elegant and simple HTTP library for Python, built for human beings.
# Requests is the only Non-GMO HTTP library for Python, safe for human consumption.
# Requests is not a built in module (does not come with the default python installation), so you will have to install it:
# http://docs.python-requests.org/en/v2.9.1/
# installing it for pyCharm is not so easy and takes a lot of troubleshooting (problems with pip's main version)
# use conda/pip install requests instead

import json

# dict subclass for counting hashable objects
from collections import Counter

#import heapq

import datetime

url = 'https://api.namefake.com'
# a "global" list object. TODO: try to make it "static" (local to the file)
words = []

#####################################################################################
# Calls the site http://www.namefake.com  100 times and retrieves random names
# Examples for the format of the names from this site:
# Dr. Willis Lang IV
# Lily Purdy Jr.
# Dameon Bogisich
# Ms. Zora Padberg V
# Luther Krajcik Sr.
# Prof. Helmer Schaden            etc....
#####################################################################################

requests.packages.urllib3.disable_warnings()

t = datetime.datetime.now()

for x in range(100):
    # for each name, break it to first and last name
    # no need for authentication
    # http://docs.python-requests.org/en/v2.3.0/user/quickstart/#make-a-request
    responseObj = requests.get(url, verify=False)

    # Decoding JSON data from returned response object text
    # Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
    #    containing a JSON document) to a Python object.
    jsonData = json.loads(responseObj.text)
    x = jsonData['name']

    newName = ""
    for full_name in x:
        # make a string from the decoded python object concatenation
        newName += str(full_name)

    # split by whitespaces
    y = newName.split()

    # parse the first name (check first if header exists (Prof. , Dr. , Mr. , Miss)
    if "." in y[0] or "Miss" in y[0]:
        words.append(y[2])
    else:
        words.append(y[0])

    words.append(y[1])

# Return the top 10 words that appear most frequently, together with the number of times, each word appeared.
# Output example: ['Weber', 'Kris', 'Wyman', 'Rice', 'Quigley', 'Goodwin', 'Lebsack', 'Feeney', 'West', 'Marlen']
# (We don't care whether the word was a first or a last name)

# list of tuples
top_ten =Counter(words).most_common(10)

top_names_list = [name[0] for name in top_ten ]

print((datetime.datetime.now()-t).total_seconds())

print(top_names_list)

1 Ответ

0 голосов
/ 26 декабря 2018

Вы вызываете конечную точку API, который генерирует фиктивную информацию один человек за раз - это занимает значительное количество времени.

Остальная часть кода почти не занимает времени.

Измените конечную точку, которую вы используете (на том, который вы используете, сбор больших имен не производится) или используйте встроенные фиктивные данные, предоставляемые модулями python.


Вы можетеясно видеть, что «подсчет и обработка имен» здесь не является узким местом:

from faker import Faker          # python module that generates dummy data
from collections import Counter
import datetime
fake = Faker()
c = Counter()

# get 10.000 names, split them and add 1st part
t = datetime.datetime.now() 
c.update( (fake.name().split()[0] for _ in range(10000)) )

print(c.most_common(10))
print((datetime.datetime.now()-t).total_seconds())

Вывод для 10000 имен:

[('Michael', 222), ('David', 160), ('James', 140), ('Jennifer', 134), 
 ('Christopher', 125), ('Robert', 124), ('John', 120), ('William', 111), 
 ('Matthew', 111), ('Lisa', 101)]

in

1.886564 # seconds

Общие рекомендации по оптимизации кода: сначала измерьте , а затем оптимизируйте узких мест .

Если вам нужен просмотр кода , вы можете проверить https://codereview.stackexchange.com/help/on-topic и посмотреть, соответствует ли ваш код требованиям для сайта обмена стеками просмотра кода.Как и в случае с SO, вначале нужно приложить некоторые усилия, то есть проанализировать , где тратит большую часть вашего времени.


Редактировать - с измерениями производительности:

import requests
import json
from collections import defaultdict
import datetime


# defaultdict is (in this case) better then Counter because you add 1 name at a time
# Counter is superiour if you update whole iterables of names at a time 
d = defaultdict(int)

def insertToDict(n):
    d[n] += 1

url = 'https://api.namefake.com'
api_times = []
process_times = []
requests.packages.urllib3.disable_warnings()
for x in range(10):
    # for each name, break it to first and last name
    try:
        t = datetime.datetime.now()      # start time for API call
        # no need for authentication
        responseObj = requests.get(url, verify=False)
        jsonData = json.loads(responseObj.text)

        # end time for API call
        api_times.append( (datetime.datetime.now()-t).total_seconds() )
        x = jsonData['name']

        t = datetime.datetime.now()      # start time for name processing
        newName = ""
        for name_char in x:
            # make a string from the decoded python object concatenation
            newName = newName + str(name_char)

        # split by whitespaces
        y = newName.split()

        # parse the first name (check first if header exists (Prof. , Dr. , Mr. , Miss)
        if "." in y[0] or "Miss" in y[0]:
            insertToDict(y[2])
        else:
            insertToDict(y[0])
        insertToDict(y[1])

        # end time for name processing
        process_times.append( (datetime.datetime.now()-t).total_seconds() )
    except:
        continue

newA = sorted(d, key=d.get, reverse=True)[:10]
print(newA)
print(sum(api_times))
print(sum( process_times )) 

Вывод:

['Ruecker', 'Clare', 'Darryl', 'Edgardo', 'Konopelski', 'Nettie', 'Price',
 'Isobel', 'Bashirian', 'Ben']
6.533625
0.000206

Вы можете сделать синтаксический анализ лучше .. Я этого не сделал, потому что этоне имеет значения.


Лучше использовать timeit для тестирования производительности (он вызывает код несколько раз и в среднем, сглаживать артефакты из-за кэширования / задержки / ...) (thx @ bruno desthuilliers ) - в этом случае я не использовал timeit, потому что я не хочу вызывать API 100000 раз для усреднения результатов

...