Python类与对象引用案例:地名查询

目标要求

解析“中国地名表.json”,并实现如下功能:

  • 根据给定的中国地名,判断该地名是否存在;若地名不存在,返回None;若地名存在,则给出该地名所属的上级地名和该地名包含的下级地名。例如,给出“秦皇岛市”,返回其上级地名“河北省”以及其下级地名“海港区”、“山海关区”等。
  • 对于给定的中国地名A,判断该地名是否存在;若地名不存在,返回None;若地名存在,则返回该地名所属的各级上级地名。例如,给出“海港区”,返回其所有上级地名“中国”、“河北省”、“秦皇岛市”。

(若出现重复地名则默认保留更高等级的地名)

“中国地名表.json”

目标实现

构造地名节点类

根据目标要求,我们构造节点(Node对象)来存储地名,节点(Node对象)包括四个属性,分别为节点地名(name)、节点地名等级(level)、父节点对象(father)和子节点对象列表(children),这样就构造成了地名节点树。当我们需要当前地名(节点)的上级地名时,只需要返回父节点的name属性即可;当我们需要当前地名(节点)的下级地名时,只需要返回所有子节点的name属性即可。

当我们构造地名节点树时,从根节点(最高级的地名)开始构造,而后所有新节点都在上层节点中构造(add_child方法)。

class Node:  # 地名节点类
    def __init__(self, name, level, father=None):
        self._name = name
        self._level = level
        self._father = father
        self._children = []

    def add_child(self, name):  # 添加子节点
        child_node = Node(name, self)
        self._children.append(child_node)
        return child_node

    def pedigree(self):  # 获取所有父节点列表(包括当前节点)
        if self.father() is not None:
            return self.father().pedigree() + [self]
        else:
            return [self]

    def father(self):
        return self._father

    def children(self):
        return self._children

    def level(self):
        return self._level

    def __str__(self):
        return self._name

其中add_child方法用于构造地名节点树;father方法、children方法是为了实现给出当前地名的上级地名和下级地名;pedigree方法是为了实现给定当前地名的所有上级地名。

但是,仅依靠地名节点树,我们仍无法方便地实现判断一个地名是否存在的需求。

构造地名节点树

为了实现判断地名是否存在,并快速找到地名,我们需要构造一个以key为地名,value为地名节点(Node对象)的字典。但是,地名中存在类似“中国-北京市-北京市-海淀区”的情况,对此,我们希望key为”北京市“的地名,对应的value是等级较高节点,而不是等级较低节点。

为此,我们构造了地名节点树(Tree对象)来管理地名节点,并使用区分节点等级的列表(node_list),来存储各个等级地名和地名节点的对应字典(node_list中的元素)。

class Tree:  # 地名节点树
    def __init__(self, root: str, level_grade: int):
        """
        :param root: <str> 根节点名称
        :param level_grade: <int> 节点总层级数
        """
        self.root_node = Node(root, 0)
        self.node_list = [{} for _ in range(level_grade)]
        self.node_list[0][root] = self.root_node

    def add_child(self, name: str, node: Node = None):
        if node is None:
            node = self.root_node
        child_node = node.add_child(name)
        self.node_list[node.level() + 1][name] = child_node
        return child_node

    def get_node(self, name: str, level: int = None):
        if level is None:
            for level_node_list in self.node_list:
                if name in level_node_list:
                    return level_node_list[name]
        else:
            if name in self.node_list[level]:
                return self.node_list[level][name]
        return None

其中,add_child方法用于构造地名节点树时添加子节点,get_node用于在地名节点树中查询地名是否存在并返回对应节点(Node对象)。

解析“中国地名表.json”

“中国地名表.json”的结构大体如下,每个层级的对象有两个键,第一个键(name)为当前层级的地名,第二个键(province、city或area)为当前地名包含的下层地名;若当前地名只包含一个下层地名,则第二个键的值为对象,否则第二个键的值为数组。

{
  "root": {
    "name": "中国",
    "province": [
      {
        "name": "北京市",
        "city": {
          "name": "北京市",
          "area": [...]
        }
      }, {
        "name": "天津市",
        "city": {...}
      }, {
        "name": "河北省",
        "city": [
          {
            "name": "石家庄市",
            "area": [
              {
                "name": "长安区"
              }, {
                "name": "桥东区"
              }, 
......

具体的,我们使用如下代码进行解析:

def load_place():
    """
    载入中国地名表并转换为节点树的形式

    :return: <dict>省级节点; <dict> 地级节点; <dict> 县级节点
    """
    # 载入“中国地名表.json”
    with open("中国地名表.json", encoding="GBK") as file:
        place_json = json.loads(file.read())

    def add_city(city_json):  # 处理市级结构
        city_node = node_tree.add_child(city_json["name"], node=province_node)
        if "area" in city_json:
            if type(city_json["area"]) == list:  # 处理包含多个县级市的地级市
                for area in city_json["area"]:
                    node_tree.add_child(area["name"], node=city_node)
            else:  # 处理仅包含一个县级市的地级市
                node_tree.add_child(city_json["area"]["name"], node=city_node)

    # 将Json形式转换为节点树形式
    node_tree = Tree(place_json["root"]["name"], 4)  # 构建地名节点树
    for province in place_json["root"]["province"]:
        province_node = node_tree.add_child(province["name"])
        if type(province["city"]) == list:  # 处理省级行政区
            for city in province["city"]:
                add_city(city)
        else:  # 处理直辖市
            add_city(province["city"])
    return node_tree
运行测试

最后,我们测试10个不同级别的地名,检查是否完成了需求中的要求。

if __name__ == "__main__":
    PLACE_TREE = load_place()  # 载入中国地名表并转换为节点树的形式
    demo_place = ["海淀区", "铜梁县", "成都市", "枣阳市", "孝感市", "绥化市", "察哈尔右翼中旗", "四川省", "宾川县", "阿拉尔市"]
    for place_name in demo_place:
        if (place_node := PLACE_TREE.get_node(place_name)) is not None:
            print(place_node, place_node.father(), str([str(child) for child in place_node.children()]))
            print(place_node, "-".join([str(node) for node in place_node.pedigree()]))
        else:
            print("地名不存在")

运行结果:

海淀区 北京市 []
海淀区 中国-北京市-北京市-海淀区
铜梁县 重庆市 []
铜梁县 中国-重庆市-重庆市-铜梁县
成都市 四川省 ['锦江区', '青羊区', '金牛区', '武侯区', '成华区', '龙泉驿区', '青白江区', '金堂县', '双流县', '温江县', '郫县', '新都县', '大邑县', '蒲江县', '新津县', '都江堰市', '彭州市', '邛崃市', '崇州市']
成都市 中国-四川省-成都市
枣阳市 襄樊市 []
枣阳市 中国-湖北省-襄樊市-枣阳市
孝感市 湖北省 ['孝南区', '孝昌县', '大悟县', '云梦县', '应城市', '安陆市', '汉川市']
孝感市 中国-湖北省-孝感市
绥化市 黑龙江省 ['绥化市', '安达市', '肇东市', '海伦市', '望奎县', '兰西县', '青冈县', '庆安县', '明水县', '绥棱县']
绥化市 中国-黑龙江省-绥化市
察哈尔右翼中旗 乌兰察布盟 []
察哈尔右翼中旗 中国-内蒙古-乌兰察布盟-察哈尔右翼中旗
四川省 中国 ['成都市', '自贡市', '攀枝花市', '泸州市', '德阳市', '绵阳市', '广元市', '遂宁市', '内江市', '乐山市', '南充市', '宜宾市', '广安市', '达川地区', '雅安地区', '阿坝藏族羌族自治州', '甘孜藏族自治州', '凉山彝族自治州', '巴中地区', '眉山地区', '资阳地区']
四川省 中国-四川省
宾川县 大理白族自治州 []
宾川县 中国-云南省-大理白族自治州-宾川县
阿拉尔市 省直辖行政单位 []
阿拉尔市 中国-新疆-省直辖行政单位-阿拉尔市

完整源代码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值