1. 程式人生 > >用 JSON 表現樹的結構兼談佇列、堆疊的練習(二)

用 JSON 表現樹的結構兼談佇列、堆疊的練習(二)

樹的查詢

查詢,又叫作搜尋 search。查詢跟遍歷的概念不同,遍歷是全部的節點都要走一遍,而查詢,找到目標節點就立刻返回,不會繼續遍歷了。當然,如果什麼都沒查詢到,就是一次完整的遍歷過程了。

查詢的依據是什麼?假設我們對 K/V 結構,也就是 Map,對其增加 id 欄位,用來作為查詢的依據,那麼使用者輸入如果符合 id 就返回節點,表示查詢成功。

單層的查詢很好做,在遞迴的函式裡 if(id = map.get("id") ) 判斷一下就可以了。JSON 是多層的,怎麼做多層的呢?

還是那個 json 例子,已經有 id 欄位存在了。我們希望輸入查詢條件如 "product/new/yuecai" 返回 {id:yuecai, name : 粵菜} 這個節點。

呼叫方式如下

JsonStruTraveler t = new JsonStruTraveler();
		
assertEquals("關於我們", t.findByPath("about", list).get("name"));
assertEquals("企業文化", t.findByPath("about/cluture", list).get("name"));
assertEquals("粵菜", t.findByPath("product/new/yuecai", list).get("name"));

findByPath 返回是 map 型別,就是這個節點。如果你有了解過 JSONPath

,其實是有點相似的。

由於水平有限,當前只能做的從根節點一步一步查詢,非常簡單的原理。不支援從非第一級節點開始的查詢。

查詢的原理

原理還是非常簡單,依然是遞迴的呼叫。不過在輸入條件的處理上,使用了一點技巧,就是佇列的運用。

/**
 * 
 * 根據路徑查詢節點
 * @param str
 *            Key 列表字元
 * @param list
 *            列表
 * @return Map
 */
public Map<String, Object> findByPath(String str, List<Map<String, Object>> list) {
	if (str.startsWith("/"))
		str = str.substring(1, str.length());
	if (str.endsWith("/"))
		str = str.substring(0, str.length() - 1);
	
	String[] arr = str.split("/");
	Queue<String> q = new LinkedList<>(Arrays.asList(arr));

	return findByPath(q, list);
}

/**
 * 真正的查詢函式
 * 
 * @param stack
 *            棧
 * @param list
 *            列表
 * @return Map
 */
@SuppressWarnings("unchecked")
private Map<String, Object> findByPath(Queue<String> queue, List<Map<String, Object>> list) {
	Map<String, Object> map = null;
	
	while (!queue.isEmpty()) {
		map = findMap(list, queue.poll());
		
		if (map != null) {
			if (queue.isEmpty()) {
				break;// found it!
			} else if (map.get(children) != null) {
				map = findByPath(queue, (List<Map<String, Object>>) map.get(children));
			} else {
				map = null;
			}
		}
	}
	
	return map;
}

首先拆分輸入的字串,變為佇列( Queue 窄化了 LinkedList)。當 findMap 函式找到有節點,此時已消耗了佇列的一個元素(queue.poll())。如果佇列已經空了,表示已經查詢完畢,返回節點。否則再看看下級節點 children 是否有,這一步是遞迴的呼叫,私有函式 private Map<String, Object> findByPath 就是專門為遞迴用的函式。

如果不使用佇列,也可以完成,但就比較累,要自己寫多幾行程式碼控制,就操作如下程式碼。


最開始的時候,我先想到的不是 佇列,而是 棧。後來想想了,生成棧的過程比較繁瑣,因為字串 split 之後,是要反轉陣列才能變為棧的:

String[] arr = str.split("/");
Stack<String> stack = new Stack<>();

for (int i = arr.length - 1; i >= 0; i--)
	stack.push(arr[i]);

而後來想了下既然要反轉陣列,那不與直接使用與棧相反的 佇列 好了。這裡用棧或是佇列是有差別的但差別不大,我們只是避免手寫更多的行數來控制陣列,對精簡程式碼有好處。

扁平化 Map

JSON 樹變成 MapList 之後,仍然是多層結構,如訪問其中某個值(注意不是“節點”),必須逐級別訪問。每級訪問時都要判斷物件是否為空,這就是要避免的 Java 空指標問題。例如 ((Map<String, Object>)list.get(0)).get("level"),比較繁瑣。於是,我們希望可以寫一個表示式就可以直接訪問某個節點。上述如 product/new/yuecai 路徑的方式是一種思路;這裡再介紹一種不同的方式,它把 / 變成 .  如 product.new.yuecai,而且內部實現完全不同,速度會快很多。這就是扁平化 Map。

之所以強調扁平化 Map 中的 Map ,其意思是隻強調處理 K/V 的 Map,輸入型別要求是 Map,對於遇到 List 型別的 Value 不會去區分那是值還是子節點,一律當作下一級的子節點集合處理。這與“product/new/yuecai” 指定 children 項的方式來處理子集合稍有區別的,後者要求輸入的是 List。而且前者通常是返回某個值,後者返回的是節點(Map),返回的內容其意義不一樣。

首先定義內部介面 TravelMapList_Iterator,有三個方法:

/**
 * 遍歷 MapList 的回撥
 * @author Frank Cheung [email protected]
 */
public static interface TravelMapList_Iterator {
	/**
	 * 當得到了 Map 的 key 和 value 時呼叫
	 * 
	 * @param key 鍵名稱
	 * @param obj 鍵值
	 */
	public void handler(String key, Object obj);

	/**
	 * 當得到一個新的 key 時候
	 * @param key 鍵名稱
	 */
	public void newKey(String key);
	
	/**
	 * 當退出一個當前 key 的時候
	 * @param key 鍵名稱
	 */
	public void exitKey(String key);

}

接著定義遞迴函式遍歷 MapList

/**
 * 遍歷 MapList,允許 TravelMapList_Iterator 控制
 * 
 * @param map
 *            輸入 Map
 * @param iterator
 *            回撥
 */
@SuppressWarnings("unchecked")
public static void travelMapList(Map<String, Object> map, TravelMapList_Iterator iterator) {
	for (String key : map.keySet()) {
		Object obj = map.get(key);

		if (iterator != null)
			iterator.handler(key, obj);

		if (obj != null) {
			if (obj instanceof Map) {
				if (iterator != null)
					iterator.newKey(key);

				travelMapList((Map<String, Object>) obj, iterator);

				if (iterator != null)
					iterator.exitKey(key);
			} else if (obj instanceof List) {
				List<Object> list = (List<Object>) obj;

				for (Object item : list) {
					if (item != null && item instanceof Map)
						travelMapList((Map<String, Object>) item, iterator);
				}
			}
		}
	}
}

TravelMapList_Iterator 到底有什麼用?說到底是為了進棧和退棧用的。堆疊有後入先出的特性,在這裡使用最適合不過了——用來記錄所在層級。

/**
 * 扁平化多層 map 為單層
 * @param map
 * @return
 */
public static Map<String, Object> flatMap(Map<String, Object> map) {
	final Stack<String> stack = new Stack<>();
	final Map<String, Object> _map = new HashMap<>();

	travelMapList(map, new TravelMapList_Iterator() {
		@Override
		public void handler(String key, Object obj) {
			if (obj == null || obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
				StringBuilder sb = new StringBuilder();
				for (String s : stack) {
					sb.append(s + ".");
				}
				_map.put(sb + key, obj);
				//					System.out.println(sb + key + ":" + obj);
			}
		}

		@Override
		public void newKey(String key) {
			stack.add(key); // 進棧
		}

		@Override
		public void exitKey(String key) {
			stack.pop(); // 退棧
		}
	});

	return _map;
}

外界呼叫 API 其實就是這個 flatMap 方法。

呼叫例子:

Map<String, Object> f_map = JsonStruTraveler.flatMap(map);
assertEquals(7, f_map.get("data.jobCatalog_Id"));

輸入 JSON

{
	"site" : {
		"titlePrefix" : "大華•川式料理",
		"keywords" : "大華•川式料理",
		"description" : "大華•川式料理飲食有限公司於2015年成立,本公司目標緻力打造中國新派川菜系列。煒爵爺川菜料理系列的精髓在於清、鮮、醇、濃、香、燙、酥、嫩,擅用麻辣。在服務出品環節上,團隊以ISO9000為藍本建立標準化餐飲體系,務求以嶄新的姿態面向社會各界人仕,提供更優質的服務以及出品。煒爵爺宗旨:麻辣鮮香椒,美味有訣竅,靚油用一次,精品煮御賜。 ",
		"footCopyright":"dsds" 
	},
	"dfd":{
		"dfd":'fdsf',
		"id": 888,
		"dfdff":{
			"dd":'fd'
		}
	},
	"clientFullName":"大華•川式料理",
	"clientShortName":"大華",
	"isDebug": true,
	"data" : {
		"newsCatalog_Id" : 6,
		"jobCatalog_Id" :7
	}
}

最後得到 map 結構是這樣子的,根節點的話則沒有 . (點)。

由於是 map 查詢,所以查詢的速度會很快。

格式化 JSON

如果對棧的技術不甚明瞭,可以嘗試通過格式化 JSON 這個例子去理解。儘管這例子沒有使用的棧,但是有相仿的地方,都是異曲同工的,大家可以細細品味。

/**
 * 格式化 JSON,使其美觀輸出到控制或其他地方 請注意 對於json中原有\n \t 的情況未做過多考慮 得到格式化json資料 退格用\t
 * 換行用\r
 * 
 * @param json
 *            原 JSON 字串
 * @return 格式化後美觀的 JSON
 */
public static String format(String json) {
	int level = 0;
	StringBuilder str = new StringBuilder();

	for (int i = 0; i < json.length(); i++) {
		char c = json.charAt(i);
		if (level > 0 && '\n' == str.charAt(str.length() - 1))
			str.append(StringUtil.repeatStr("\t", "", level));

		switch (c) {
			case '{':
			case '[':
				str.append(c + "\n");
				level++;
				break;
			case ',':
				if (json.charAt(i + 1) == '"')
					str.append(c + "\n"); // 後面必定是跟著 key 的雙引號,但 其實 json 可以 key 不帶雙引號的
				break;
			case '}':
			case ']':
				str.append("\n");
				level--;
				str.append(StringUtil.repeatStr("\t", "", level));
				str.append(c);
				break;
			default:
				str.append(c);
				break;
		}
	}

	return str.toString();
}

其中,level 表示縮排數,level++ 可看著“進棧”的操作,反之,level-- 為“退棧”,這一退一進是不是與棧的 push/pop 很像?

StringUtil.repeatStr 是重複字串多少次的函式,比較簡單,這裡就不貼出了。

相關推薦

JSON 表現結構佇列堆疊練習

樹的查詢查詢,又叫作搜尋 search。查詢跟遍歷的概念不同,遍歷是全部的節點都要走一遍,而查詢,找到目標節點就立刻返回,不會繼續遍歷了。當然,如果什麼都沒查詢到,就是一次完整的遍歷過程了。查詢的依據是什麼?假設我們對 K/V 結構,也就是 Map,對其增加 id 欄位,用來

【資料結構】棧與佇列的面試題

一.使用兩個佇列實現(實現棧先進後出的特點)     思路:              1.建立兩個佇列的結構體,並將這倆個佇列(Queue1和Queue2)的結構體封裝到一個結構體裡。                       2.入棧:判斷哪個佇列中為空(Queue1和

A1—淺JavaScript中的原型

js原型是什麽?想要了解這個問題,我們就必須要知道對象。對象根據w3cschool上的介紹:對象只是帶有屬性和方法的特殊數據類型。我們知道,數組是用來描述數據的。其實呢,對象也是用來描述數據的。只不過有一點點的區別,那就是數組的下標只能是數字。所以,數組最好只用來裝同樣意義的內容。比如說[1,2,3,4,5]

我的MongoDB學習

如果 自動 淺談 查詢 技術分享 刪除 insert 工作經歷 posit   上一篇簡單講了mongodb的安裝,mongo的windows服務安裝,這樣服務器重啟windows服務會自動重啟mongodb的server,然後我們就可以用客戶端去管理數據了。mongod

ASP.NET Core MVC 和 EF Core 構建Web應用

work nal nta 多個 包括 catch web 應用 自動 選項卡 本節學習如何執行基本的 CRUD (創建、 讀取、 更新、 刪除) 操作。 自定義“詳細信息”頁 學生索引頁的基架代碼省略了 Enrollments 屬性,因為該屬性包含一個集合。 在“詳細信息”

PHP面向物件程式設計

和一些面向物件的語言有所不同,PHP並不是一種純面向物件的語言,包PIP它支援面向物件的程式設計,並可以用於開發大型的商業程式。因此學好面向物件輸程對PHP程式設計師來說也是至關重要的。本章並針對面向物件程式設計在PIP語言中的使用進行詳細講解。 2.1 面向物件概述   面向物件是一種符

從招式與內功起——設計模式概述

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

專案練習—微博資料結構

1.ETL概念       ETL,是英文 Extract-Transform-Load 的縮寫,用來描述將資料從來源端經過抽取(extract)、互動轉換(transform)、載入(load)至目的端的過程。 2.專案目標: 本次專案側重於資料的整合(即將檔案中

排序演算法的效率Java筆記

首先:咱也借用一下網上的那張XXX的圖,咯!在下面: 接下來,就是咱的驗證時間了(驗證什麼?當然是各種演算法的時間複雜度比較咯),沒什麼好說的了,直接上碼吧。 程式碼實現: import java.util.Arrays; public class SortSum

Type-CPD原理

目錄 一、Type-C簡介以及歷史 二、Type-C Port的Data Role、Power Role 三、Type-C的Data/Power Role識別協商/Alt Mode 四、如何進行資料鏈路的切換 五、相關引數/名詞解釋 六、PD協議簡介  

資料結構內排序之慘死攻略

接上回合《資料結構內排序之慘死攻略(一)》 聽聞今天還要學資料結構,心中堵著一片烏雲。 就算受低潮情緒影響也要堅持學下去啊。 目錄 5 歸併排序 5.1 栗子 5.2 程式碼實現 5.3 歸併演算法優化 5.3.1 R.Sedgewick優化 5.3.2

SQL 中的鎖餘額問題的處理

上次模擬了 SQL 中併發執行更新餘額的語句,出現餘額負數的問題:http://blog.csdn.net/closurer/article/details/54288628 現在說說它的解決方法。 事務要正確地執行,就需要【隔離性】這個基本要素。更新餘額的語句之所以會偏離

QT迴圈佇列實時處理資料

  上一篇多執行緒介紹的是,QT多執行緒處理機制,這篇,將對接收資料,實時處理進行分析。          QT通過socket通訊,從接收緩衝區中讀取資料,交給執行緒進行處理,那麼問題來了,如果執行緒還沒有處理完資料,則執行緒就沒有辦法繼續從緩衝區中取數,那麼當資料量

連結串列實現一元多項式加減求導Java

Lnode.java package PloyItem; /** *@Author wzy *@Date 2017年11月12日 *@Version JDK 1.8 *@Description */ public class Lnode imp

Linux PCI裝置驅動

我們在 淺談Linux PCI裝置驅動(一)中(以下簡稱 淺談(一) )介紹了PCI的配置暫存器組,而Linux PCI初始化就是使用了這些暫存器來進行的。後面我們會舉個例子來說明Linux PCI裝置驅動的主要工作內容(不是全部內容),這裡只做文字性的介紹而不會涉及具體程

分散式訊息佇列ActiveMQ訊息模型

在ActiveMQ中,一共支援4種訊息型別,分別是TextMessage訊息型別、BytesMessage訊息型別、ObjectMessage訊息型別,還有一種MapMessage訊息型別。 (1)       TextMessage訊息型別 TextMessage訊息是

資料結構數和

1 - 引言 關於樹和二叉樹,我們需要達到的能力有: 熟悉樹和二叉樹的有關概念 熟悉二叉樹的性質 熟練掌握遍歷二叉樹的遞迴演算法,並靈活運用 遞迴遍歷二叉樹及其應用 本文著重在樹和二叉樹實際應用與程式碼實現基本操作,對概念就不再贅述 2 - 二

資料結構實驗之棧與佇列六:下一較大值(因為資料量大所以棧來操作)

資料結構實驗之棧與佇列六:下一較大值(二) Time Limit: 150 ms Memory Limit: 8000 KiB Problem Description 對於包含n(1<=n<=100000)個整數的序列,對於序列中的每一元素,在序列中查詢

數據結構

創建 int iter out for 結點 spa left nbsp 輸出二叉樹中所有從根結點到葉子結點的路徑 1 #include <iostream> 2 #include <vector> 3 us

學習之路:bash及其特性,命令歷史以及戶管理及權限,shell的類型

bash 管理權限 過了一周了,進度似乎有點懈怠,不過過了周末重整旗鼓啦shell(外殼)GUI:Gnome,KDE,xfceCLI:sh,csh,ksh,bashbash(父進程)-----bash(子進程)他們相互獨立彼此不知命令歷史:historybash支持的引號:‘ ’命令替換(鍵盤~的按鍵