Как улучшить временную сложность этого кода? - PullRequest
0 голосов
/ 28 мая 2020

Мой код пытается отсортировать элементы по 2 словарям. Если они похожи или одинаковы, я создаю новый список, чтобы добавить их.

Тестовый файл относительно большой, около 2000 данных в каждом словаре.

Меня беспокоит, что мне нужно поддерживать линейная временная сложность кода, и я не уверен, что мой код имеет линейную сложность.

Итак, я хотел бы узнать мнение о сложности этого кода. Это линейно? Если нет, есть ли способ его улучшить?

Заранее спасибо!

import pandas as pd
import string
import textdistance as td
import time
start_time = time.time()


amazon =  pd.read_csv('amazon.csv')
google = pd.read_csv('google.csv')

title1 = amazon['title']
title2 = google['name']
id1 = amazon['idAmazon']
id2 = google['id']

out_amazon = {}
out_google = {}

list_a =[]
list_b =[]
list_c =[]
list_d =[]
list_e =[]
list_f =[]
list_g =[]
list_h =[]
list_i =[]
list_j =[]
list_k =[]
list_l =[]
list_m =[]
list_n =[]
list_o =[]
list_p =[]
list_q =[]
list_r =[]
list_s =[]
list_t =[]
list_u =[]
list_v =[]
list_w =[]
list_x =[]
list_y =[]
list_z =[]
list_unknown = []
duplicate_list = []

amazon_labeled = ([(name, 'Amazon') for name in title1])
google_labeled = ([(name, 'Google') for name in title2])

amazon_dict = dict(zip(amazon.idAmazon, amazon_labeled))
google_dict = dict(zip(google.id, google_labeled))

z = {**amazon_dict, **google_dict}
keys = sorted((z.values()))

i = 0
while i < (len(keys)) - 1:
    if (keys[i][0][0]) == 'a':
        list_a.append(keys[i])
    elif (keys[i][0][0]) == 'b':
        list_b.append(keys[i])
    elif (keys[i][0][0]) == 'c':
        list_c.append(keys[i])
    elif (keys[i][0][0]) == 'd':
        list_d.append(keys[i])
    elif (keys[i][0][0]) == 'e':
        list_e.append(keys[i])
    elif (keys[i][0][0]) == 'f':
        list_f.append(keys[i])
    elif (keys[i][0][0]) == 'g':
        list_g.append(keys[i])
    elif (keys[i][0][0]) == 'h':
        list_h.append(keys[i])
    elif (keys[i][0][0]) == 'i':
        list_i.append(keys[i])
    elif (keys[i][0][0]) == 'j':
        list_j.append(keys[i])
    elif (keys[i][0][0]) == 'k':
        list_k.append(keys[i])
    elif (keys[i][0][0]) == 'l':
        list_l.append(keys[i])
    elif (keys[i][0][0]) == 'm':
        list_m.append(keys[i])
    elif (keys[i][0][0]) == 'n':
        list_n.append(keys[i])
    elif (keys[i][0][0]) == 'o':
        list_o.append(keys[i])
    elif (keys[i][0][0]) == 'p':
        list_p.append(keys[i])
    elif (keys[i][0][0]) == 'q':
        list_q.append(keys[i])
    elif (keys[i][0][0]) == 'r':
        list_r.append(keys[i])
    elif (keys[i][0][0]) == 's':
        list_s.append(keys[i])
    elif (keys[i][0][0]) == 't':
        list_t.append(keys[i])
    elif (keys[i][0][0]) == 'u':
        list_u.append(keys[i])
    elif (keys[i][0][0]) == 'v':
        list_v.append(keys[i])
    elif (keys[i][0][0]) == 'w':
        list_w.append(keys[i])
    elif (keys[i][0][0]) == 'x':
        list_x.append(keys[i])
    elif (keys[i][0][0]) == 'y':
        list_y.append(keys[i])
    elif (keys[i][0][0]) == 'z':
        list_z.append(keys[i])
    else:
        list_unknown.append(keys[i])
    i += 1

def check_based_alphabet(alphabetList, k = 0, j = 0):
    while k < len(alphabetList) - 1:
        if alphabetList[k][1] != alphabetList[j][1]:
            distance = td.jaccard(alphabetList[k][0], alphabetList[j][0])
            if distance > 0.7:
                duplicate_list.append([alphabetList[k][0], alphabetList[j][0]])
            j += 1
        else:
            j += 1
        if j == len(alphabetList):
            j = 1
            k += 1            

check_based_alphabet(list_a)
check_based_alphabet(list_b)
check_based_alphabet(list_c)
check_based_alphabet(list_d)
check_based_alphabet(list_e)
check_based_alphabet(list_f)
check_based_alphabet(list_g)
check_based_alphabet(list_h)
check_based_alphabet(list_i)
check_based_alphabet(list_j)
check_based_alphabet(list_k)
check_based_alphabet(list_l)
check_based_alphabet(list_m)
check_based_alphabet(list_n)
check_based_alphabet(list_o)
check_based_alphabet(list_p)
check_based_alphabet(list_q)
check_based_alphabet(list_r)
check_based_alphabet(list_s)
check_based_alphabet(list_t)
check_based_alphabet(list_u)
check_based_alphabet(list_v)
check_based_alphabet(list_w)
check_based_alphabet(list_x)
check_based_alphabet(list_y)
check_based_alphabet(list_z)

1 Ответ

1 голос
/ 30 мая 2020

Вы спрашиваете конкретно о временной сложности, но найти временную сложность легче, когда код чистый. Итак, я: 1. почищу код; 2. Найдите временную сложность и обсудите возможные улучшения.

Очистите код

Во-первых, вместо создания list_a, list_b, ..., создайте dict list_by_first_letter. Нам не нужно беспокоиться об индексах:

for k in keys:
    letter = k[0][0]
    if not('a' <= letter <= 'z'):
        letter = 'UNK'
    list_by_first_letter.setdefault(letter, []).append(k)

setdefault проверит, является ли letter уже ключом в dict, а иначе создаст его и сопоставит со значением по умолчанию ( пустой список [] здесь).

А затем, чтобы найти дубликаты:

for alphabet_list in list_by_first_letter.values():
    check_based_alphabet(alphabet_list)

Теперь нам нужно очистить check_based_alphabet. Заменяю while на for. Поскольку вы сопоставляете каждый элемент со всеми остальными, которые у вас есть:

def check_based_alphabet(alphabet_list):
    for k in range(len(alphabet_list)):
        first_key = alphabet_list[k]
        for j in range(len(alphabet_list))):
            second_key = alphabet_list[j]
            if first_key[1] != second_key[1]:
                distance = td.jaccard(first_key[0], second_key[0])
                if distance > 0.7:
                    duplicate_list.append([first_key[0], second_key[0]])

У вас может быть проблема: вы сравниваете alphabet_list[0] с alphabet_list[1], alphabet_list[2], ..., затем alphabet_list[1] с alphabet_list[0], alphabet_list[2], .... Если ваш td.jaccard симметричный c, вы получите каждую пару дважды. Чтобы избежать этого, используйте:

...
for j in range(k+1, len(alphabet_list))):
...

Вы избавитесь от дубликатов, поскольку second_key всегда после first_key в списке. (Я не буду утверждать, что код теперь полностью чистый, но он, по крайней мере, читается.)

Временная сложность

Теперь мы можем найти временную сложность. Во-первых, временная сложность check_based_alphabet равна O(n^2 * complexity of td.jaccard), где n - это номер элемента в подсписке. Это очевидно из-за двух составных циклов.

Если N - количество элементов в списке, у вас есть глобальная временная сложность O(n1^2 * complexity of td.jaccard) + O(n2^2 * complexity of td.jaccard) + ..., где N = n1 + n2 + ..., то есть O(N^2 * complexity of td.jaccard):

  1. В худшем случае у нас есть N = n1 и n2 = ... = 0, и результат тривиален.
  2. В лучшем случае у нас n1 = n2 = ... и 27 * O((N/27)^2 * complexity of td.jaccard) и это все еще O(N^2 * complexity of td.jaccard), хотя это может быть быстрее.

Кажется, трудно улучшить эту временную сложность. Посмотрите на свою пробную версию: вы группируете ключи в сегменты (все ключи, которые начинаются с одной и той же буквы) и сравниваете каждый ключ со всеми ключами того же сегмента. Хорошая идея, но недостаток в том, что размер ведер все равно зависит от N. Чтобы уменьшить временную сложность, вы должны создавать корзины фиксированного размера. Это невозможно, если td.jaccard и / или ключи не имеют определенных свойств c.

(Пример определенных свойств c: представьте, что у вас есть список различных целых чисел, и эти два целые числа i, j называются «дубликатами», если |i -j| < K, где K не зависит от N. Вы можете сравнивать все пары целых чисел (O(N^2)), но вы также можете сортировать целые числа (O(N lg N) в общем случае c), а затем сравните каждый элемент отсортированного списка со следующими целыми числами K (выше, все целые числа не менее i + K, следовательно, не дублируются): O(N * K). Следовательно, время сложность O(N (K + lg N)) лучше, чем O(N^2).)

...