预测学校排名问题

题目描述

有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,咱们也只须要把题目给出的条件做为参数给到程序中,就能够了。

关于这个程序的执行效率问题,实际上程序就是靠遍历找到可能的排名状况,所以效率并不会过高。

相关文章
相关标签/搜索