預測學校排名問題

題目描述

有A、B、C、D、E 5所學校,在一次檢查評比中,已知E校確定不是第2名或第3.他們相互進行推測。
A校有人說,E校必定是第1名。
B校有人說,我校多是第2名。
C校有人說,A校最差。
D校有人說,C校不是最好的。
E校有人說,D校會得到第1名。
結果只有第1名和第2名學校的人猜對了,編程指出這5所學校的名次。python

思考

每一個人的猜想結果均可能對,但只有兩我的猜對了。能夠直接取猜對的兩我的的猜想結果,以及另外三個猜錯的結果的反面,與已知條件進行對比,若不存在矛盾選項,且每一個學校的排名可以惟一肯定,認爲選取的猜對的人是正確的。git

例如,已知有 A、B、C、D、E 5 所學校及其相應的猜想結果,咱們能夠獲得相似這樣的數據結構:github

schools = ('A', 'B', 'C', 'D', 'E')
pred = [
    {
        "Pos": ('E', (1, ), "D"),
        "Neg": ('E', (4, 5))  # E 不會是 2 3
    },

    # ...
]

pred 是一個數組,數組中有 5 個元素,表示 5 個學校的預測結果,每一個元素(預測結果)是一個字典。用 「Pos」 表示該學校猜對的結果,「Neg」 表示相反的結果。編程

將「A校有人說,E校必定是第1名。」 轉化爲數據結構就是 { "Pos": ('E', (1, ), "D") },Pos 元祖第 1 個元素 「E」 表示對 E 校的猜想,第 2 個元素 (1, ) 表示猜想的排名,爲了統一,第 2 個用一個 tuple 表示,第 3 個元素 「D」 表示 A 的猜想是確定的。而 B 校的推測是 「可能」,所以用 「M」 表示。數組

題目中還給了一個條件,用數組表示:數據結構

# 猜對的人的學校排名是 1 或 2
RankFromRightPeople = [1, 2]
# 猜錯的人的學校排名是 3 4 5
RankFromWrongPeople = [3, 4, 5]

當對某個學校的排名進行可能性分析時,其實是利用已知的條件,對不一樣人給出的猜想或已知條件中的可能性作一個交集的操做。函數

而後咱們開始嘗試遍歷全部可能的正確的學校,獲得惟一的且不矛盾的結果便可。code

編程

咱們對學校的排名預測中會用到交集和差集,這裏利用 python set 的 API 進行交集或差集運算:blog

def union(A, B) :
    """ A + B
    """
    return list(set(A).intersection(B))

def diff(A, B):
    """ A - B
    """
    return list(set(A).difference(set(B)))

接下來,對某種可能的排名狀況進行分析。
根據給定的條件,咱們能夠遍歷前兩名的學校的可能狀況,因爲總共有 5 所學校,因此可能的狀況有 10 種。leetcode

每次遍歷的時候,從 pred 數組中取出各個學校對於排名的描述,將排名的描述(python 中的數組來表示)放入 predictSchool 數組中:

predictSchool = [0] * len(schools)

# 收集結果集
for index, p in enumerate(pred):
    if si == index or sj == index:
        predictSchool[schools.index(p["Pos"][0])] = list(p["Pos"][1])
    else:
        predictSchool[schools.index(p["Neg"][0])] = list(p["Neg"][1])

假設 A 和 B 學校在前兩名,那麼對 A 和 B 的描述中就要加上交集 (1, 2),另外 3 所學校的描述中要加上交集 (3, 4, 5):

# 取交集 union
for i in range(len(schools)):
    if i == si or i == sj:
        predictSchool[i] = union(predictSchool[i], RankFromRightPeople)
    else:
        predictSchool[i] = union(predictSchool[i], RankFromWrongPeople)

此時 predictSchool 應該是一個二維數組,它的數組相似於:

[
    [1, 2, 3],
    [1, 2, 5],
    [3, 4],
    [3, 5],
    [3, 4, 5]
]

接下來,對每一個學校的排名描述作差集處理,當 predictSchool 中出現某一項的元素爲 0,說明描述出現矛盾,咱們假設的第 1 名、第 2 名是錯誤的,能夠直接中止分析:

for _ in range(len(schools) * 2):
    # 只會執行必定次數循環
    s = set()
    # print("Before", predictSchool)
    for pr in predictSchool:
        lpr = len(pr)
        if lpr == 0:
            return []
        elif lpr == 1:
            if pr[0] in s:
                # print("Already definite.")
                return []
            s.add(pr[0])

    # print("Set 2 list", s)
    for i, pr in enumerate(predictSchool):
        lpr = len(pr)
        if lpr > 1:
            predictSchool[i] = diff(pr, s)

當 predictSchool 中出現某一項的元素數目爲 1 時,代表這個學校的排名當前能夠肯定下來,接下來將 prediectSchool 中其餘項的數組中去除掉這個排名的描述,最後循環到 predictSchool 中的每一項都只有一個元素時,說明咱們猜想的第 1 名和第 2 名學校是可能的。

接下來只須要遍歷全部可能的 1 2 名的狀況便可:

def startPredict():
    for i in range(5):
        for j in range(i+1, 5):
            pr = predictRank(i, j)
            if len(pr) > 0:
                for k, p in enumerate(pr):
                    pr[k] = [schools[k], p[0]]

                dpr = dict(pr)
                npr = sorted(dpr.items(), key = lambda item: item[1])
                print(npr)
                # 這裏其實能夠中止循環了
                # return
        # print(pr, i, j, '\n---------')

startPredict()

結果

運行咱們的 Python 程序,能夠獲得以下結果:

[('C', 1), ('B', 2), ('D', 3), ('E', 4), ('A', 5)]

完整的 python 程序能夠從 個人 github 倉庫 中獲取。

可擴展性的討論

至此爲止,這個 Python 程序已經能夠找到正確的排名了,但它還有改進的空間。還記得咱們定義 pred 數組的時候是怎麼樣的嗎:

schools = ('A', 'B', 'C', 'D', 'E')
pred = [
    {
        "Pos": ('E', (1, ), "D"),
        "Neg": ('E', (4, 5))  # E 不會是 2 3
    },

    # ...
]

pred 數組中的 "Pos" 項就是題目所描述的,然而 pred 數組中的 "Neg" 項是咱們從程序中獲取並通過了處理的,可是實際上它並不須要人爲的處理,咱們能夠寫一個簡單的函數對 pred 的 "Pos" 項作一個預處理,將其轉化爲 "Neg" 項。

須要注意的是,題目有個前置條件,E 學校不是第 2 名,也不是第 3 名。預處理的過程若是有對 E 學校的描述時,要與這個條件求交集

我已經給它留好了擴展的空間,"Pos" 對應 value 的第 3 個元素表示猜想的可能性,咱們能夠根據第 3 個元素和第 2 個元素推導出 "Neg":

  1. 若是第 3 個元素是 "D",那麼對 Pos 中的第 2 項取差集(固然,總集合是 schools 數組所對應的的 set)。
  2. 若是第 3 個元素是 "M",那麼 Pos 中的第 2 項和 Neg 中的第 2 項應該是同樣的。由於 A 多是第 1 名,和它的反面 A 可能不是第 1 名的可能狀況數是同樣的。(固然你可能認爲相反是另一種定義,好比 A 多是第 1 名,那麼它的反面是 「A 不多是第 1 名」,這樣的話, Pos 項中的 D 在 Neg 項中就變成了 M,這顯然不是咱們想要的結果)

加上這樣的預處理後,實際上解決這類問題就不用修改太多代碼,並且也不須要人爲的計算出每一個學校的 Pos 項和 Neg 項了。就是說這個程序的擴展性仍是比較好的。
若是學校變多了,好比從 A 到 G,咱們也只須要把題目給出的條件做爲參數給到程序中,就能夠了。

關於這個程序的執行效率問題,實際上程序就是靠遍歷找到可能的排名狀況,所以效率並不會過高。

相關文章
相關標籤/搜索