簡體   English   中英

在Python中查找多個列表中最相似的數字

[英]Finding the most similar numbers across multiple lists in Python

在Python中,我有3個浮點數(角度)列表,范圍在0-360之間,列表的長度不同。 我需要找到三元組(每個列表中有一個數字),其中數字最接近。 (任何數字都不太可能是相同的,因為這是現實世界的數據。)我想用一種簡單的最低標准偏差方法來衡量協議,但我不確定一個好方法實現這一點。 我可以循環遍歷每個列表,使用嵌套的for循環比較每個可能組合的標准偏差,並且有一個臨時變量保存最好的三元組的索引,但我想知道是否有人有更好或更優雅的方式做這樣的事情。 謝謝!

如果有一個已建立的算法來執行此操作,我不會感到驚訝,如果是這樣,您應該使用它。 但我不知道一個,所以我要推測一點。

如果我必須這樣做,我會嘗試的第一件事就是遍歷所有數字的所有可能組合,看看它需要多長時間。 如果你的數據集足夠小,那么發明一個聰明的算法是不值得的。 為了演示設置,我將包含示例代碼:

# setup
def distance(nplet):
    '''Takes a pair or triplet (an "n-plet") as a list, and returns its distance.
    A smaller return value means better agreement.'''
    # your choice of implementation here. Example:
    return variance(nplet)

# algorithm
def brute_force(*lists):
    return min(itertools.product(*lists), key = distance)

對於大型數據集,我會嘗試這樣的事情:首先為第一個列表中的每個數字創建一個三元組,其第一個條目設置為該數字。 然后瀏覽這個部分填充的三元組列表,並為每個三元組從第一個列表中選擇最接近該數字的第二個列表中的數字,並將其設置為三元組的第二個成員。 然后瀏覽三元組列表,對於每個三元組,從第三個列表中選擇最接近前兩個數字的數字(按協議指標衡量)。 最后,充分利用這一切。 此示例代碼演示了如何嘗試將運行時線性保持在列表的長度中。

def item_selection(listA, listB, listC):
    # make the list of partially-filled triplets
    triplets = [[a] for a in listA]
    iT = 0
    iB = 0
    while iT < len(triplets):
        # make iB the index of a value in listB closes to triplets[iT][0]
        while iB < len(listB) and listB[iB] < triplets[iT][0]:
            iB += 1
        if iB == 0:
            triplets[iT].append(listB[0])
        elif iB == len(listB)
            triplets[iT].append(listB[-1])
        else:
            # look at the values in listB just below and just above triplets[iT][0]
            # and add the closer one as the second member of the triplet
            dist_lower = distance([triplets[iT][0], listB[iB]])
            dist_upper = distance([triplets[iT][0], listB[iB + 1]])
            if dist_lower < dist_upper:
                triplets[iT].append(listB[iB])
            elif dist_lower > dist_upper:
                triplets[iT].append(listB[iB + 1])
            else:
                # if they are equidistant, add both
                triplets[iT].append(listB[iB])
                iT += 1
                triplets[iT:iT] = [triplets[iT-1][0], listB[iB + 1]]
        iT += 1
    # then another loop while iT < len(triplets) to add in the numbers from listC
    return min(triplets, key = distance)

問題是,我可以想象這實際上不會找到最好的三元組的情況,例如,如果第一個列表中的數字接近第二個列表中的一個但是根本不接近第三個列表中的任何一個。 所以你可以嘗試的是為列表的所有6種可能的排序運行這個算法。 我想不出一個特定的情況,那就是找不到最好的三重奏,但可能仍然存在。 在任何情況下,如果您使用聰明的實現,假設列表已排序,則算法仍為O(N)。

def symmetrized_item_selection(listA, listB, listC):
    best_results = []
    for ordering in itertools.permutations([listA, listB, listC]):
        best_results.extend(item_selection(*ordering))
    return min(best_results, key = distance)

另一種選擇可能是計算列表1和列表2之間,列表1和列表3之間以及列表2和列表3之間所有可能的數字對。然后將所有三個對列表排在一起,從兩者之間的最佳協議數字。 從最近的一對開始,逐個遍歷列表對,只要遇到一對與您已經看過的數字共享一個數字的對,就將它們合並為三元組。 對於一個合適的協議度量,一旦你找到你的第一個三元組,這將給你一個你需要迭代的最大對距離,一旦你達到它,你只需選擇你最接近的三元組找到。 我認為應該始終找到最好的三元組,但它將是O(N ^ 2 log N),因為需要對對的列表進行排序。

def pair_sorting(listA, listB, listC):
    # make all possible pairs of values from two lists
    # each pair has the structure ((number, origin_list),(number, origin_list))
    # so we know which lists the numbers came from
    all_pairs = []
    all_pairs += [((nA,0), (nB,1)) for (nA,nB) in itertools.product(listA,listB)]
    all_pairs += [((nA,0), (nC,2)) for (nA,nC) in itertools.product(listA,listC)]
    all_pairs += [((nB,1), (nC,2)) for (nB,nC) in itertools.product(listB,listC)]
    all_pairs.sort(key = lambda p: distance(p[0][0], p[1][0]))
    # make a dict to track which (number, origin_list)s we've already seen
    pairs_by_number_and_list = collections.defaultdict(list)
    min_distance = INFINITY
    min_triplet = None
    # start with the closest pair
    for pair in all_pairs:
        # for the first value of the current pair, see if we've seen that particular
        # (number, origin_list) combination before
        for pair2 in pairs_by_number_and_list[pair[0]]:
            # if so, that means the current pair shares its first value with
            # another pair, so put the 3 unique values together to make a triplet
            this_triplet = (pair[1][0], pair2[0][0], pair2[1][0])
            # check if the triplet agrees more than the previous best triplet
            this_distance = distance(this_triplet)
            if this_distance < min_distance:
                min_triplet = this_triplet
                min_distance = this_distance
        # do the same thing but checking the second element of the current pair
        for pair2 in pairs_by_number_and_list[pair[1]]:
            this_triplet = (pair[0][0], pair2[0][0], pair2[1][0])
            this_distance = distance(this_triplet)
            if this_distance < min_distance:
                min_triplet = this_triplet
                min_distance = this_distance
        # finally, add the current pair to the list of pairs we've seen
        pairs_by_number_and_list[pair[0]].append(pair)
        pairs_by_number_and_list[pair[1]].append(pair)
    return min_triplet

注意我在這個答案中寫的所有代碼示例都比你在實踐中做的更明確,以幫助你理解它們是如何工作的。 但是當真實地做這件事時,你會使用更多的列表理解和類似的東西。

NB2。 不保證代碼可以工作:-P但它應該得到粗略的想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM