Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >巧用指针/引用实现多级省市区嵌套

巧用指针/引用实现多级省市区嵌套

原创
作者头像
yaxin
发布于 2021-01-22 10:35:22
发布于 2021-01-22 10:35:22
1.2K0
举报
文章被收录于专栏:DevOps BoyDevOps Boy

开发中经常遇到需要将一个二维结构的数据转换为N级嵌套(如多级菜单、省市区嵌套等),一般遇到这种问题我们会借助数据表添加冗余列配合代码,高级点的可以再配合数据库的存储过程,简单粗暴点的是把数据拉回来后代码多次循环处理。下面我们用指针/引用再没有冗余列的情况下仅遍历一次原始数据实现省市区的嵌套输出。

开始之前我们先简单说明一下数据源(数据结构)以及期望的输出结果:

这里先简单说明一下数据结构,从行政区划代码上可以看出,省级别的后4位位0,市级别的后两位为0,且市级别的前2位等于所在省份的前两位,而区级别的前4位与所在市的前4位相同,因此我们可以把一个行政区划代码分为三部分,前两位是省份代码、中间两位是市代码、最后两位为区代码。也就是从行政区划代码上就可以知道节点的级别,归属(父节点),相当于数据表中增加了parentIdlevel

0x01 完整程序

先把完整的代码给出,有兴趣的可以不看后面的分析。如果觉得不方便的可以去gist中查看,gist地址:https://gist.github.com/yaxin-cn/43873178ab6b6347b0466d0c0a262780

代码语言:txt
AI代码解释
复制
<?php
$data = file_get_contents("http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html");
preg_match_all('#<td class=xl\d+?>(<span.*?)?(\d+|[\x{4e00}-\x{9fa5}]+).*?</td>#u', $data, $matches);

$administratives = [];
$node = array(
    'id' => '',
    'name' => ''
);
$fixNode = array(
    'id' => '',
    'name' => '市辖区'
);
foreach ($matches[2] as $idx => $v) {
    if ($idx % 2 == 0) {
        $node = array(
            'id' => $v,
            'name' => ''
        );
        if (in_array($v, ['110000', '120000', '310000', '500000'])) {
            $v[3] = '1';
            $fixNode['id'] = $v;
        }
    } else if ($idx % 2 == 1) {
        $node['name'] = $v;
        $administratives[] = $node;
        
        if (in_array($v, ['北京市', '天津市', '上海市', '重庆市'])) {
            $administratives[] = $fixNode;
        }
    }
}
unset($node);

$root = array();
$nodeMap = array();

foreach ($administratives as $item) {
    $rawId = (string)$item['id'];
    $id = (int)$rawId;
    $provinceId = (int)($id / 10000);
    $cityId = ((int)($id / 100)) % 100;
    $districtId = $id % 100;

    if ($cityId === 0 && $districtId === 0) {
        // 省份信息
        $node = array(
            'Id' => $rawId,
            'Name' => $item['name'],
            'Cities' => []
        );
        $nodeMap[$id] = &$node;

        $root[] = &$node;
    } else if ($districtId === 0) {
        // 市信息
        $fullCityId = $provinceId * 10000 + $cityId * 100;
        $node = array(
            'Id' => $rawId,
            'Name' => $item['name'],
            'Districts' => []
        );
        $nodeMap[$fullCityId] = &$node;

        $provinceNode = &$nodeMap[$provinceId * 10000];
        $provinceNode['Cities'][] = &$node;
    } else {
        // 区信息
        $fullCityId = $provinceId * 10000 + $cityId * 100;
        $node = array(
            'Id' => $rawId,
            'Name' => $item['name'],
        );
        $cityNode = &$nodeMap[$fullCityId];

        $cityNode['Districts'][] = $node;
    }
    unset($node);
}

print_r($root);

0x02 代码分析

该算法的本质是用空间换时间,并且在遍历到每一个节点时都将其添加到一个ID到节点的映射,并且通过是指针(引用)到节点的,而如果该节点是某个节点的子节点的话,会直接用指针的方式附加到改节点的子节点中,这样后面对该节点修改后会直接体现到父节点上。下面我们详细分析上面的代码。

2.1 第一部分

第1~33行代码跟主逻辑并无关联,主要的作用是模拟从数据库拉取数据,生成一个二维数组。方便直接运行代码查看效果等,避免了建表的麻烦。当然你也可以建表并且将数据写入表中,然后使用程序拉取,这个也应该是现网运行的正常逻辑。

数据库表样例:

代码语言:txt
AI代码解释
复制
CREATE TABLE `t_locations` (
  `id` int unsigned NOT NULL DEFAULT '0',
  `name` varchar(100) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 省节点

根据之前的分析,当市和区代码为0时,该节点为省份信息节点。第47行我们创建一个省份节点,包含所有后续结果需要的信息,且包含了市节点Cities的空数组。52行将节点添加到一个以节点ID(行政区划代码)为键的关联数组(映射表)中,并且是通过指针(引用)的方式添加的,之所以这么做是为了这后面是市和区做准备。第54行将节点添加到最终结果数组中,这样$root变量就是我们最终需要的值。

2.3 市节点

我们接下来分析市节点代码:

市节点的处理包含了整个算法的核心逻辑,也就是如何把当前市添加到正确的身份的Cities中。

首先,我们可以通过市代码得知该市所在省份的代码,有了代码我们就可以通过上一步的$nodeMap获取到省份节点,由于我们上一步存进去的省份信息是一个指针(引用),所以这一步中我们获取到的省份节点就是最终结果$root变量中的省份节点,因而对该节点的Cities进行操作也会体现在最终的结果变量$root中,而这也保证了我们最终生成结果的正确性。

2.4 区节点

理解了市节点添加的逻辑,就很容易理解区节点添加的逻辑了,处理重点就在于定位市节点上,由于都是指针(引用),所有操作均会直接作用到最终结果上,从而实现我们最终想要的结果。

0x03 总结

开发中指针(引用)绝不仅仅是提升性能、减少资源占用的手段,它也可以作为简化代码逻辑的一个手段。最后这里再抛出一个思考题,我们上面的逻辑能正常运行的一个前提是$administratives变量中是有顺序的,如果没有顺序应该如何处理呢?这个可以人怎能思考一下,思路也是非常有趣的。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【数据结构】初识集合&深入剖析顺序表(Arraylist)
🚀所属专栏:数据结构与算法学习⭐⭐ 🚀欢迎光顾我的主页: 2的n次方_⭐⭐
2的n次方
2024/10/15
1100
【数据结构】初识集合&深入剖析顺序表(Arraylist)
JAVA增强for循环
为什么需要增强for循环呢? 简化数组和集合的遍历。 想要使用增强for循环必须先要了解它的格式 增强for循环的格式
全栈程序员站长
2022/07/04
1.1K0
Java基础笔记16
16.01_集合框架(去除ArrayList中重复字符串元素方式1) A:案例演示 需求:ArrayList去除集合中字符串的重复值(字符串的内容相同) 思路:创建新集合方式 /** * A:案例演示 * 需求:ArrayList去除集合中字符串的重复值(字符串的内容相同) * 思路:创建新集合方式 */ public static void main(String[] args) { ArrayList list = new ArrayList();
dreamkong
2018/06/21
5430
16(03)总结增强for循环,静态导入,可变参数
3:增强for循环(掌握) (1)是for循环的一种 (2)格式: for(元素的数据类型 变量名 : 数组或者Collection集合的对象) { 使用该变量即可,该变量其实就是数组或者集合中的元素。 } (3)好处: 简化了数组和集合的遍历 (4)弊端 增强for循环的目标不能为null。建议在使用前,先判断是否为null。 package cn.itcast_01; import java.util.ArrayList; import java.util.List; /* * JD
Java帮帮
2018/03/15
1.3K0
增强for循环
jdk1.5出现的新特性---->增强for循环
MonroeCode
2018/01/11
7720
增强for循环
需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合
秋落雨微凉
2022/10/25
1.3K0
增强for循环
Java 基础(六):数组
老生常谈的一个控制流程了,我们在是使用数组和集合的时候,遍历元素的时候经常会用到循环的结构,Java具有非常灵活的三种循环机制:
山禾说
2020/03/20
4000
Java 基础(六):数组
JAVA入门学习六
描述: 集合的由来数组长度是固定,当添加的元素超过了数组的长度时需要对数组重新定义太麻烦,java内部给我们提供了集合类能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少;
全栈工程师修炼指南
2020/10/23
6080
JAVA入门学习六
java基础第十二篇之集合、增强for循环、迭代器和泛型
public static void main(String[] args) { // TODO Auto-generated method stub //遍历数组 // int[] nums = {1,2,3,4,5}; // for(int num:nums){ // System.out.println(num); // } Collection nums = new ArrayList(); nums.add(10); nums.add(20); nums.add(30); nums.add(40); //1.用迭代器 //2.增强for循环 for(Integer i:nums){ nums.add(50); System.out.println(i); } //快捷键 foreach+alt+/
海仔
2019/08/05
5030
Java学习笔记之集合1
本文涉及Java知识点为集合,包括Collection集合,List集合,数组和队列结构,List集合的实现类
Jetpropelledsnake21
2021/12/10
3520
Java学习笔记之集合1
Java中普通for循环和增强for循环的一些区别
对于实现了RandomAccess接口的集合类,推荐使用普通for,这种方式faster than Iterator.next
XING辋
2019/03/26
1.6K0
【愚公系列】2022年01月 Java教学课程 47-集合对象Collection
Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
愚公搬代码
2022/01/15
1950
【愚公系列】2022年01月 Java教学课程 47-集合对象Collection
【Java学习笔记之十】Java中循环语句foreach使用总结及foreach写法失效的问题
foreach语句使用总结 增强for(part1:part2){part3}; part2中是一个数组对象,或者是带有泛性的集合. part1定义了一个局部变量,这个局部变量的类型与part2中的对象元素的类型是一致的. part3当然还是循环体. foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。 foreach语句是for语句的特殊简化版本,但是foreach语句并不能完全取代for语句,然而,任何的foreach语句都可以改写为fo
Angel_Kitty
2018/04/09
2.2K0
第十七天 集合-Collection&amp;增强for&amp;迭代器【悟空教程】
出现意义:面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。
Java帮帮
2018/07/26
5520
第十七天 集合-Collection&amp;增强for&amp;迭代器【悟空教程】
【day19】集合和常用API
在之前的学习中,我们了解了变量和数组用于保存数据,但数组是定长的。当需要添加或删除数据时,数组并不方便,因此我们引入了长度可变的容器——集合。
程序员波特
2024/12/30
900
【day19】集合和常用API
Java中的for循环介绍
1、Java中的for循环  不严格的说,Java的第二种for循环基本是这样的格式:
用户7886150
2021/04/23
1.3K0
JDK1.9-Iterator迭代器
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
cwl_java
2019/12/03
3010
Java ArrayList和LinkedList
​ 集合就是用于存储多个数据的容器。相对于具有相同功能的数组来说,集合的长度可变会更加灵活方便。Java中提供了使用不同数据结构存储数据的不同集合类,他们有各自不同的特点,并且在类中提供了很多常用了方法,便于我们使用。
愷龍
2022/09/26
3360
Java ArrayList和LinkedList
JAVA零基础小白学习免费教程day13-Collection&数据结构
JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。
张哥编程
2024/12/13
800
JAVA零基础小白学习免费教程day13-Collection&数据结构
Java 关于集合框架那点事儿
 1.引入集合框架   采用数组存在的一些缺陷:    1.数组长度固定不变,不能很好地适应元素数量动态变化的情况。    2.可通过数组名.length获取数组的长度,却无法直接获取数组中真实存储的个数。    3.在进行频繁插入、删除操作时同样效率低下。  2.Java集合框架包含的内容   Java集合框架为我们提供了一套性能优良、使用方便的接口和类,它们都位于Java.util包中。   集合框架是为表示和操作集合而规定的一种统一的标准体系结构。集合框架都包含三大块内容;对外的接口、接口的实现和对集
房上的猫
2018/03/14
1.2K0
Java 关于集合框架那点事儿
相关推荐
【数据结构】初识集合&深入剖析顺序表(Arraylist)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档