欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

javascript種樹,js樹結(jié)構(gòu)

xml到底是什么?主要用于什么用途?

xml:簡單來說是存儲和描述數(shù)據(jù)的.

創(chuàng)新互聯(lián)公司從2013年成立,先為北流等服務建站,北流等地企業(yè),進行企業(yè)商務咨詢服務。為北流企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務解決您的所有建站問題。

比如說

bookstore

book category="CHILDREN"

titleHarry Potter/title

authorJ K. Rowling/author

year2005/year

price29.99/price

/book

book category="WEB"

titleLearning XML/title

authorErik T. Ray/author

year2003/year

price39.95/price

/book

/bookstore

這個xml描述了一個書城中的兩本書,一本是兒童(category="CHILDREN")讀物Harry Potter作者是J K. Rowling,出版年份是2005,價格是29.99,,另一本是web方面的,Learning XML作者是Erik T. Ray,出版年份是2003,價格是39.95

白楊樹資料

style type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/scriptstyle type=text/css.baidu{font-size:14px;line-height:1.5;}a{color:#0000cc;}

a.t{color: #006633;font-size:14px;text-decoration:none;}a.cn {color:#555555;}/style

script language="JavaScript" type="text/JavaScript" src=";cid=65536tn=fcuqlalllm=4rn=5"/script

有關竹子的詩?

詠竹 (齊.謝眺)

窗前一叢竹,清翠獨言奇。

南條交北葉,新筍雜故枝。

月光疏已密,風聲起復垂。

青扈飛不礙,黃口獨相窺。

但恨從風籜,根株長相離。

竹 (劉孝先)

竹生空野外,梢云聳百尋。

無人賞高節(jié),徒自抱貞心。

恥染湘妃淚,羞入上宮琴。

誰人制長笛,當為吐龍吟。

賦得階前嫩竹 (陳 . 張正見)

翠云梢云自結(jié)叢,輕花嫩筍欲凌空。

砌曲橫枝屢解籜,階來疏葉強來風。

欲知抱節(jié)成龍?zhí)帲斢谏铰犯疒橹小?/p>

詠竹 (唐.李嶠)

高簳楚江濆,嬋娟含曙氣。

白花搖風影,青節(jié)動龍文。

葉掃東南日,枝捎西北云。

誰知湘水上,流淚獨思君。

郡齋左偏栽竹百余詩 (唐 . 令狐楚)

齋居栽竹北窗邊,素壁新開映碧鮮。

青藹近當行藥處,綠陰深到臥帷前。

風驚曉葉如聞雨,月過春枝似帶煙。

老子憶山心暫緩,退公閑坐對嬋娟。

秋日白沙館對竹 (唐 . 許渾)

蕭蕭凌雪霜,濃翠異三湘。

疏影月移壁,寒聲風滿堂。

卷簾秋更早,高枕夜偏長。

忽憶秦溪路,萬竿今正涼。

初食筍呈座中 (唐 . 李商隱)

嫩籜香苞初出林,於陵論價重如金。

皇都陸海應無數(shù),忍剪凌云一寸金。

詠竹 (唐.鄭谷)

宜煙宜雨又宜風,拂水藏村復間松。

移得蕭騷從遠寺,洗來疏凈見前峰。

侵階蘚拆春芽迸,繞徑莎微夏蔭濃。

誣賴杏花多意緒,數(shù)枝穿翠好相容。

竹風 (唐.唐彥謙)

竹映風窗數(shù)陣斜,---人愁坐思無涯。

夜來留得江湖夢,全為乾聲似荻花。

春日山中竹 (唐.裴說)

數(shù)竿蒼翠擬龍形,峭拔須教此地生。

無限野花開不得,半山寒色與春爭。

詠竹 (唐.張必)

樹色連云萬葉開,王孫不厭滿庭載。

凌霜盡節(jié)無人見,終日虛心待鳳來。

誰許風流添興詠,自憐瀟灑出塵埃。

朱門處處多閑地,正好移云撫翠苔。

霜筠亭 (宋.蘇軾)

解籜新篁不自持,嬋娟已有歲寒姿。

要看凜凜霜前意,須待秋風粉落時。

賦園中所有 (宋.蘇轍)

寒地竹不生,雖生常若病。

斸根種幽砌,開葉何已猛。

嬋娟冰雪姿,散亂風日影。

繁華見孤深,一個敵千頃。

令人憶江上,森聳緣崖勁。

無風籜自飄,策策鳴荒徑。

新竹 (宋.楊萬里)

東風弄巧補殘山,一夜吹添玉數(shù)竿。

半脫錦衣猶半著,籜龍未信怯春寒。

詠東湖新竹 (宋.陸游)

插棘編籬謹護持,養(yǎng)成寒碧映淪漪。

清風掠地秋先到,赤日行天午不知。

解籜時聞聲簌簌,放梢初見葉離離。

官閑我欲頻來此,枕簟仍教到處隨。

野竹 (元.吳鎮(zhèn))

野竹野竹絕可愛,枝葉扶疏有真態(tài)。

生平素守遠荊榛,走壁懸崖穿石埭。

虛心抱節(jié)山之河,清風白月聊婆娑。

寒梢千尺將如何,渭川淇澳風煙多。

新筍歌 (明.岳岱)

滿林黃鳥不勝啼,林下新筍與人齊。

春風閉門走山兔,白晝露滴驚竹雞。

雨中三日春已過,又近石床添幾個。

競將頭角向青云,不管階前綠苔破。

竹石 (清.鄭燮)

咬定青山不放松,立根原在破巖中。

千磨萬擊還堅勁,任爾東西南北風。

竹 (清.鄭燮)

一節(jié)復一節(jié),千枝攢萬葉。

我自不開花,免撩蜂與蝶。

為了您的安全,請只打開來源可靠的網(wǎng)址

打開網(wǎng)站 取消

來自:

如何用 Python 實現(xiàn)一個圖數(shù)據(jù)庫(Graph Database)?

本文章是 重寫 500 Lines or Less 系列的其中一篇,目標是重寫 500 Lines or Less 系列的原有項目:Dagoba: an in-memory graph database。

Dagoba 是作者設計用來展示如何從零開始自己實現(xiàn)一個圖數(shù)據(jù)庫( Graph Database )。該名字似乎來源于作者喜歡的一個樂隊,另一個原因是它的前綴 DAG 也正好是有向無環(huán)圖 ( Directed Acyclic Graph ) 的縮寫。本文也沿用了該名稱。

圖是一種常見的數(shù)據(jù)結(jié)構(gòu),它將信息描述為若干獨立的節(jié)點( vertex ,為了和下文的邊更加對稱,本文中稱為 node ),以及把節(jié)點關聯(lián)起來的邊( edge )。我們熟悉的鏈表以及多種樹結(jié)構(gòu)可以看作是符合特定規(guī)則的圖。圖在路徑選擇、推薦算法以及神經(jīng)網(wǎng)絡等方面都是重要的核心數(shù)據(jù)結(jié)構(gòu)。

既然圖的用途如此廣泛,一個重要的問題就是如何存儲它。如果在傳統(tǒng)的關系數(shù)據(jù)庫中存儲圖,很自然的做法就是為節(jié)點和邊各自創(chuàng)建一張表,并用外鍵把它們關聯(lián)起來。這樣的話,要查找某人所有的子女,就可以寫下類似下面的查詢:

還好,不算太復雜。但是如果要查找孫輩呢?那恐怕就要使用子查詢或者 CTE(Common Table Expression) 等特殊構(gòu)造了。再往下想,曾孫輩又該怎么查詢?孫媳婦呢?

這樣我們會意識到,SQL 作為查詢語言,它只是對二維數(shù)據(jù)表這種結(jié)構(gòu)而設計的,用它去查詢圖的話非常笨拙,很快會變得極其復雜,也難以擴展。針對圖而言,我們希望有一種更為自然和直觀的查詢語法,類似這樣:

為了高效地存儲和查詢圖這種數(shù)據(jù)結(jié)構(gòu),圖數(shù)據(jù)庫( Graph Database )應運而生。因為和傳統(tǒng)的關系型數(shù)據(jù)庫存在極大的差異,所以它屬于新型數(shù)據(jù)庫也就是 NoSql 的一個分支(其他分支包括文檔數(shù)據(jù)庫、列數(shù)據(jù)庫等)。圖數(shù)據(jù)庫的主要代表包括 Neo4J 等。本文介紹的 Dagoba 則是具備圖數(shù)據(jù)庫核心功能、主要用于教學和演示的一個簡單的圖數(shù)據(jù)庫。

原文代碼是使用 JavaScript 編寫的,在定義調(diào)用接口時大量使用了原型( prototype )這種特有的語言構(gòu)造。對于其他主流語言的用戶來說,原型的用法多少顯得有些別扭和不自然。

考慮到本系列其他數(shù)據(jù)庫示例大多是用 Python 實現(xiàn)的,本文也按照傳統(tǒng),用 Python 重寫了原文的代碼。同樣延續(xù)之前的慣例,為了讓讀者更好地理解程序是如何逐步完善的,我們用迭代式的方法完成程序的各個組成部分。

原文在 500lines 系列的 Github 倉庫中只包含了實現(xiàn)代碼,并未包含測試。按照代碼注釋說明,測試程序位于作者的另一個代碼庫中,不過和 500lines 版本的實現(xiàn)似乎略有不同。

本文實現(xiàn)的代碼參考了原作者的測試內(nèi)容,但跳過了北歐神話這個例子——我承認確實不熟悉這些神祇之間的親緣關系,相信中文背景的讀者們多數(shù)也未必了解,雖然作者很喜歡這個例子,想了想還是不要徒增困惑吧。因此本文在編寫測試用例時只參考了原文關于家族親屬的例子,放棄了神話相關的部分,盡管會減少一些趣味性,相信對于入門級的代碼來說這樣也夠用了。

本文實現(xiàn)程序位于代碼庫的 dagoba 目錄下。按照本系列程序的同意規(guī)則,要想直接執(zhí)行各個已完成的步驟,讀者可以在根目錄下的 main.py 找到相應的代碼位置,取消注釋并運行即可。

本程序的所有步驟只需要 Python3 ,測試則使用內(nèi)置的 unittest , 不需要額外的第三方庫。原則上 Python3.6 以上版本應該都可運行,但我只在 Python3.8.3 環(huán)境下完整測試過。

本文實現(xiàn)的程序從最簡單的案例開始,通過每個步驟逐步擴展,最終形成一個完整的程序。這些步驟包括:

接下來依次介紹各個步驟。

回想一下,圖數(shù)據(jù)庫就是一些點( node )和邊( edge )的集合。現(xiàn)在我們要做出的一個重大決策是如何對節(jié)點/邊進行建模。對于邊來說,必須指定它的關聯(lián)關系,也就是從哪個節(jié)點指向哪個節(jié)點。大多數(shù)情況下邊是有方向的——父子關系不指明方向可是要亂套的!

考慮到擴展性及通用性問題,我們可以把數(shù)據(jù)保存為字典( dict ),這樣可以方便地添加用戶需要的任何數(shù)據(jù)。某些數(shù)據(jù)是為數(shù)據(jù)庫內(nèi)部管理而保留的,為了明確區(qū)分,可以這樣約定:以下劃線開頭的特殊字段由數(shù)據(jù)庫內(nèi)部維護,類似于私有成員,用戶不應該自己去修改它們。這也是 Python 社區(qū)普遍遵循的約定。

此外,節(jié)點和邊存在互相引用的關系。目前我們知道邊會引用到兩端的節(jié)點,后面還會看到,為了提高效率,節(jié)點也會引用到邊。如果僅僅在內(nèi)存中維護它們的關系,那么使用指針訪問是很直觀的,但數(shù)據(jù)庫必須考慮到序列化到磁盤的問題,這時指針就不再好用了。

為此,最好按照數(shù)據(jù)庫的一般要求,為每個節(jié)點維護一個主鍵( _id ),用主鍵來描述它們之間的關聯(lián)關系。

我們第一步要把數(shù)據(jù)庫的模型建立起來。為了測試目的,我們使用一個最簡單的數(shù)據(jù)庫模型,它只包含兩個節(jié)點和一條邊,如下所示:

按照 TDD 的原則,首先編寫測試:

與原文一樣,我們把數(shù)據(jù)庫管理接口命名為 Dagoba 。目前,能夠想到的最簡單的測試是確認節(jié)點和邊是否已經(jīng)添加到數(shù)據(jù)庫中:

assert_item 是一個輔助方法,用于檢查字典是否包含預期的字段。相信大家都能想到該如何實現(xiàn),這里就不再列出了,讀者可參考 Github 上的完整源碼。

現(xiàn)在,測試是失敗的。用最簡單的辦法實現(xiàn)數(shù)據(jù)庫:

需要注意的是,不管添加節(jié)點還是查詢,程序都使用了拷貝后的數(shù)據(jù)副本,而不是直接使用原始數(shù)據(jù)。為什么要這樣做?因為字典是可變的,用戶可以在任何時候修改其中的內(nèi)容,如果數(shù)據(jù)庫不知道數(shù)據(jù)已經(jīng)變化,就很容易發(fā)生難以追蹤的一致性問題,最糟糕的情況下會使得數(shù)據(jù)內(nèi)容徹底混亂。

拷貝數(shù)據(jù)可以避免上述問題,代價則是需要占用更多內(nèi)存和處理時間。對于數(shù)據(jù)庫來說,通常查詢次數(shù)要遠遠多于修改,所以這個代價是可以接受的。

現(xiàn)在測試應該正常通過了。為了讓它更加完善,我們可以再測試一些邊緣情況,看看數(shù)據(jù)庫能否正確處理異常數(shù)據(jù),比如:

例如,如果用戶嘗試添加重復主鍵,我們預期應拋出 ValueError 異常。因此編寫測試如下:

為了滿足以上測試,代碼需要稍作修改。特別是按照 id 查找主鍵是個常用操作,通過遍歷的方法效率太低了,最好是能夠通過主鍵直接訪問。因此在數(shù)據(jù)庫中再增加一個字典:

完整代碼請參考 Github 倉庫。

在上個步驟,我們在初始化數(shù)據(jù)庫時為節(jié)點明確指定了主鍵。按照數(shù)據(jù)庫設計的一般原則,主鍵最好是不具有業(yè)務含義的代理主鍵( Surrogate key ),用戶不應該關心它具體的值是什么,因此讓數(shù)據(jù)庫去管理主鍵通常是更為合理的。當然,在部分場景下——比如導入外部數(shù)據(jù)——明確指定主鍵仍然是有用的。

為了同時支持這些要求,我們這樣約定:字段 _id 表示節(jié)點的主鍵,如果用戶指定了該字段,則使用用戶設置的值(當然,用戶有責任保證它們不會重復);否則,由數(shù)據(jù)庫自動為它分配一個主鍵。

如果主鍵是數(shù)據(jù)庫生成的,事先無法預知它的值是什么,而邊( edge )必須指定它所指向的節(jié)點,因此必須在主鍵生成后才能添加。由于這個原因,在動態(tài)生成主鍵的情況下,數(shù)據(jù)庫的初始化會略微復雜一些。還是先寫一個測試:

為支持此功能,我們在數(shù)據(jù)庫中添加一個內(nèi)部字段 _next_id 用于生成主鍵,并讓 add_node 方法返回新生成的主鍵:

接下來,再確認一下邊是否可以正常訪問:

運行測試,一切正常。這個步驟很輕松地完成了,不過兩個測試( DbModelTest 和 PrimaryKeyTest )出現(xiàn)了一些重復代碼,比如 get_item 。我們可以把這些公用代碼提取出來。由于 get_item 內(nèi)部調(diào)用了 TestCase.assertXXX 等方法,看起來應該使用繼承,但從 TestCase 派生基類容易引起一些潛在的問題,所以我轉(zhuǎn)而使用另一個技巧 Mixin :

實現(xiàn)數(shù)據(jù)庫模型之后,接下來就要考慮如何查詢它了。

在設計查詢時要考慮幾個問題。對于圖的訪問來說,幾乎總是由某個節(jié)點(或符合條件的某一類節(jié)點)開始,從與它相鄰的邊跳轉(zhuǎn)到其他節(jié)點,依次類推。所以鏈式調(diào)用對查詢來說是一種很自然的風格。舉例來說,要知道 Tom 的孫子養(yǎng)了幾只貓,可以使用類似這樣的查詢:

可以想象,以上每個方法都應該返回符合條件的節(jié)點集合。這種實現(xiàn)是很直觀的,不過存在一個潛在的問題:很多時候用戶只需要一小部分結(jié)果,如果它總是不計代價地給我們一個巨大的集合,會造成極大的浪費。比如以下查詢:

為了避免不必要的浪費,我們需要另外一種機制,也就是通常所稱的“懶式查詢”或“延遲查詢”。它的基本思想是,當我們調(diào)用查詢方法時,它只是把查詢條件記錄下來,而并不立即返回結(jié)果,直到明確調(diào)用某些方法時才真正去查詢數(shù)據(jù)庫。

如果讀者比較熟悉流行的 Python ORM,比如 SqlAlchemy 或者 Django ORM 的話,會知道它們幾乎都是懶式查詢的,要調(diào)用 list(result) 或者 result[0:10] 這樣的方法才能得到具體的查詢結(jié)果。

在 Dagoba 中把觸發(fā)查詢的方法定義為 run 。也就是說,以下查詢執(zhí)行到 run 時才真正去查找數(shù)據(jù):

和懶式查詢( Lazy Query )相對應的,直接返回結(jié)果的方法一般稱作主動查詢( Eager Query )。主動查詢和懶式查詢的內(nèi)在查找邏輯基本上是相同的,區(qū)別只在于觸發(fā)機制不同。由于主動查詢實現(xiàn)起來更加簡單,出錯也更容易排查,因此我們先從主動查詢開始實現(xiàn)。

還是從測試開始。前面測試所用的簡單數(shù)據(jù)庫數(shù)據(jù)太少,難以滿足查詢要求,所以這一步先來創(chuàng)建一個更復雜的數(shù)據(jù)模型:

此關系的復雜之處之一在于反向關聯(lián):如果 A 是 B 的哥哥,那么 B 就是 A 的弟弟/妹妹,為了查詢到他們彼此之間的關系,正向關聯(lián)和反向關聯(lián)都需要存在,因此在初始化數(shù)據(jù)庫時需要定義的邊數(shù)量會很多。

當然,父子之間也存在反向關聯(lián)的問題,為了讓問題稍微簡化一些,我們目前只需要向下(子孫輩)查找,可以稍微減少一些關聯(lián)數(shù)量。

因此,我們定義數(shù)據(jù)模型如下。為了減少重復工作,我們通過 _backward 字段定義反向關聯(lián),而數(shù)據(jù)庫內(nèi)部為了查詢方便,需要把它維護成兩條邊:

然后,測試一個最簡單的查詢,比如查找某人的所有孫輩:

這里 outcome/income 分別表示從某個節(jié)點出發(fā)、或到達它的節(jié)點集合。在原作者的代碼中把上述方法稱為 out/in 。當然這樣看起來更加簡潔,可惜的是 in 在 Python 中是個關鍵字,無法作為函數(shù)名。我也考慮過加個下劃線比如 out_.in_ 這種形式,但看起來也有點怪異,權衡之后還是使用了稍微啰嗦一點的名稱。

現(xiàn)在我們可以開始定義查詢接口了。在前面已經(jīng)說過,我們計劃分別實現(xiàn)兩種查詢,包括主動查詢( Eager Query )以及延遲查詢( Lazy Query )。

它們的內(nèi)在查詢邏輯是相通的,看起來似乎可以使用繼承。不過遵循 YAGNI 原則,目前先不這樣做,而是只定義兩個新類,在滿足測試的基礎上不斷擴展。以后我們會看到,與繼承相比,把共同的邏輯放到數(shù)據(jù)庫本身其實是更為合理的。

接下來實現(xiàn)訪問節(jié)點的方法。由于 EagerQuery 調(diào)用查詢方法會立即返回結(jié)果,我們把結(jié)果記錄在 _result 內(nèi)部字段中。雖然 node 方法只返回單個結(jié)果,但考慮到其他查詢方法幾乎都是返回集合,為統(tǒng)一起見,讓它也返回集合,這樣可以避免同時支持集合與單結(jié)果的分支處理,讓代碼更加簡潔、不容易出錯。此外,如果查詢對象不存在的話,我們只返回空集合,并不視為一個錯誤。

查詢輸入/輸出節(jié)點的方法實現(xiàn)類似這樣:

查找節(jié)點的核心邏輯在數(shù)據(jù)庫本身定義:

以上使用了內(nèi)部定義的一些輔助查詢方法。用類似的邏輯再定義 income ,它們的實現(xiàn)都很簡單,讀者可以直接參考源碼,此處不再贅述。

在此步驟的最后,我們再實現(xiàn)一個優(yōu)化。當多次調(diào)用查詢方法后,結(jié)果可能會返回重復的數(shù)據(jù),很多時候這是不必要的。就像關系數(shù)據(jù)庫通常支持 unique/distinct 一樣,我們也希望 Dagoba 能夠過濾重復的數(shù)據(jù)。

假設我們要查詢某人所有孩子的祖父,顯然不管有多少孩子,他們的祖父應該是同一個人。因此編寫測試如下:

現(xiàn)在來實現(xiàn) unique 。我們只要按照主鍵把重復數(shù)據(jù)去掉即可:

在上個步驟,初始化數(shù)據(jù)庫指定了雙向關聯(lián),但并未測試它們。因為我們還沒有編寫代碼去支持它們,現(xiàn)在增加一個測試,它應該是失敗的:

運行測試,的確失敗了。我們看看要如何支持它。回想一下,當從邊查找節(jié)點時,使用的是以下方法:

這里也有一個潛在的問題:調(diào)用 self.edges 意味著遍歷所有邊,當數(shù)據(jù)庫內(nèi)容較多時,這是巨大的浪費。為了提高性能,我們可以把與節(jié)點相關的邊記錄在節(jié)點本身,這樣要查找邊只要看節(jié)點本身即可。在初始化時定義出入邊的集合:

在添加邊時,我們要同時把它們對應的關系同時更新到節(jié)點,此外還要維護反向關聯(lián)。這涉及對字典內(nèi)容的部分復制,先編寫一個輔助方法:

然后,將添加邊的實現(xiàn)修改如下:

這里的代碼同時添加正向關聯(lián)和反向關聯(lián)。有的朋友可能會注意到代碼略有重復,是的,但是重復僅出現(xiàn)在該函數(shù)內(nèi)部,本著“三則重構(gòu)”的原則,暫時不去提取代碼。

實現(xiàn)之后,前面的測試就可以正常通過了。

在這個步驟中,我們來實現(xiàn)延遲查詢( Lazy Query )。

延遲查詢的要求是,當調(diào)用查詢方法時并不立即執(zhí)行,而是推遲到調(diào)用特定方法,比如 run 時才執(zhí)行整個查詢,返回結(jié)果。

延遲查詢的實現(xiàn)要比主動查詢復雜一些。為了實現(xiàn)延遲查詢,查詢方法的實現(xiàn)不能直接返回結(jié)果,而是記錄要執(zhí)行的動作以及傳入的參數(shù),到調(diào)用 run 時再依次執(zhí)行前面記錄下來的內(nèi)容。

如果你去看作者的實現(xiàn),會發(fā)現(xiàn)他是用一個數(shù)據(jù)結(jié)構(gòu)記錄執(zhí)行操作和參數(shù),此外還有一部分邏輯用來分派對每種結(jié)構(gòu)要執(zhí)行的動作。這樣當然是可行的,但數(shù)據(jù)處理和分派部分的實現(xiàn)會比較復雜,也容易出錯。

本文的實現(xiàn)則選擇了另外一種不同的方法:使用 Python 的內(nèi)部函數(shù)機制,把一連串查詢變換成一組函數(shù),每個函數(shù)取上個函數(shù)的執(zhí)行結(jié)果作為輸入,最后一個函數(shù)的輸出就是整個查詢的結(jié)果。由于內(nèi)部函數(shù)同時也是閉包,盡管每個查詢的參數(shù)形式各不相同,但是它們都可以被閉包“捕獲”而成為內(nèi)部變量,所以這些內(nèi)部函數(shù)可以采用統(tǒng)一的形式,無需再針對每種查詢設計額外的數(shù)據(jù)結(jié)構(gòu),因而執(zhí)行過程得到了很大程度的簡化。

首先還是來編寫測試。 LazyQueryTest 和 EagerQueryTest 測試用例幾乎是完全相同的(是的,兩種查詢只在于內(nèi)部實現(xiàn)機制不同,它們的調(diào)用接口幾乎是完全一致的)。

因此我們可以把 EagerQueryTest 的測試原樣不變拷貝到 LazyQueryTest 中。當然拷貝粘貼不是個好注意,對于比較冗長而固定的初始化部分,我們可以把它提取出來作為兩個測試共享的公共函數(shù)。讀者可參考代碼中的 step04_lazy_query/tests/test_lazy_query.py 部分。

程序把查詢函數(shù)的串行執(zhí)行稱為管道( pipeline ),用一個變量來記錄它:

然后依次實現(xiàn)各個調(diào)用接口。每種接口的實現(xiàn)都是類似的:用內(nèi)部函數(shù)執(zhí)行真正的查詢邏輯,再把這個函數(shù)添加到 pipeline 調(diào)用鏈中。比如 node 的實現(xiàn)類似下面:

其他接口的實現(xiàn)也與此類似。最后, run 函數(shù)負責執(zhí)行所有查詢,返回最終結(jié)果;

完成上述實現(xiàn)后執(zhí)行測試,確保我們的實現(xiàn)是正確的。

在前面我們說過,延遲查詢與主動查詢相比,最大的優(yōu)勢是對于許多查詢可以按需要訪問,不需要每個步驟都返回完整結(jié)果,從而提高性能,節(jié)約查詢時間。比如說,對于下面的查詢:

以上查詢的意思是從孫輩中找到一個符合條件的節(jié)點即可。對該查詢而言,主動查詢會在調(diào)用 outcome('son') 時就遍歷所有節(jié)點,哪怕最后一步只需要第一個結(jié)果。而延遲查詢?yōu)榱颂岣咝剩瑧谡业椒蠗l件的結(jié)果后立即停止。

目前我們尚未實現(xiàn) take 方法。老規(guī)矩,先添加測試:

主動查詢的 take 實現(xiàn)比較簡單,我們只要從結(jié)果中返回前 n 條記錄:

延遲查詢的實現(xiàn)要復雜一些。為了避免不必要的查找,返回結(jié)果不應該是完整的列表( list ),而應該是個按需返回的可迭代對象,我們用內(nèi)置函數(shù) next 來依次返回前 n 個結(jié)果:

寫完后運行測試,確保它們是正確的。

從外部接口看,主動查詢和延遲查詢幾乎是完全相同的,所以用單純的數(shù)據(jù)測試很難確認后者的效率一定比前者高,用訪問時間來測試也并不可靠。為了測試效率,我們引入一個節(jié)點訪問次數(shù)的概念,如果延遲查詢效率更高的話,那么它應該比主動查詢訪問節(jié)點的次數(shù)更少。

為此,編寫如下測試:

我們?yōu)? Dagoba 類添加一個成員來記錄總的節(jié)點訪問次數(shù),以及兩個輔助方法,分別用于獲取和重置訪問次數(shù):

然后瀏覽代碼,查找修改點。增加計數(shù)主要在從邊查找節(jié)點的時候,因此修改部分如下:

此外還有 income/outcome 方法,修改都很簡單,這里就不再列出。

實現(xiàn)后再次運行測試。測試通過,表明延遲查詢確實在效率上優(yōu)于主動查詢。

不像關系數(shù)據(jù)庫的結(jié)構(gòu)那樣固定,圖的形式可以千變?nèi)f化,查詢機制也必須足夠靈活。從原理上講,所有查詢無非是從某個節(jié)點出發(fā)按照特定方向搜索,因此用 node/income/outcome 這三個方法幾乎可以組合出任意所需的查詢。

但對于復雜查詢,寫出的代碼有時會顯得較為瑣碎和冗長,對于特定領域來說,往往存在更為簡潔的名稱,例如:母親的兄弟可簡稱為舅舅。對于這些場景,如果能夠類似 DSL (領域特定語言)那樣允許用戶根據(jù)專業(yè)要求自行擴展,從而簡化查詢,方便閱讀,無疑會更為友好。

如果讀者去看原作者的實現(xiàn),會發(fā)現(xiàn)他是用一種特殊語法 addAlias 來定義自己想要的查詢,調(diào)用方法時再進行查詢以確定要執(zhí)行的內(nèi)容,其接口和內(nèi)部實現(xiàn)都是相當復雜的。

而我希望有更簡單的方法來實現(xiàn)這一點。所幸 Python 是一種高度動態(tài)的語言,允許在運行時向類中增加新的成員,因此做到這一點可能比預想的還要簡單。

為了驗證這一點,編寫測試如下:

無需 Dagoba 的實現(xiàn)做任何改動,測試就可以通過了!其實我們要做的就是動態(tài)添加一個自定義的成員函數(shù),按照 Python 對象機制的要求,成員函數(shù)的第一個成員應該是名為 self 的參數(shù),但這里已經(jīng)是在 UnitTest 的內(nèi)部,為了和測試類本身的 self 相區(qū)分,新函數(shù)的參數(shù)增加了一個下劃線。

此外,函數(shù)應返回其所屬的對象,這是為了鏈式調(diào)用所要求的。我們看到,動態(tài)語言的靈活性使得添加新語法變得非常簡單。

到此,一個初具規(guī)模的圖數(shù)據(jù)庫就形成了。

和原文相比,本文還缺少一些內(nèi)容,比如如何將數(shù)據(jù)庫序列化到磁盤。不過相信讀者都看到了,我們的數(shù)據(jù)庫內(nèi)部結(jié)構(gòu)基本上是簡單的原生數(shù)據(jù)結(jié)構(gòu)(列表+字典),因此序列化無論用 pickle 或是 JSON 之類方法都應該是相當簡單的。有興趣的讀者可以自行完成它們。

我們的圖數(shù)據(jù)庫實現(xiàn)為了提高查詢性能,在節(jié)點內(nèi)部存儲了邊的指針(或者說引用)。這樣做的好處是,無論數(shù)據(jù)庫有多大,從一個節(jié)點到相鄰節(jié)點的訪問是常數(shù)時間,因此數(shù)據(jù)訪問的效率非常高。

但一個潛在的問題是,如果數(shù)據(jù)庫規(guī)模非常大,已經(jīng)無法整個放在內(nèi)存中,或者出于安全性等原因要實現(xiàn)分布式訪問的話,那么指針就無法使用了,必須要考慮其他機制來解決這個問題。分布式數(shù)據(jù)庫無論采用何種數(shù)據(jù)模型都是一個棘手的問題,在本文中我們沒有涉及。有興趣的讀者也可以考慮 500lines 系列中關于分布式和集群算法的其他一些文章。

本文的實現(xiàn)和系列中其他數(shù)據(jù)庫類似,采用 Python 作為實現(xiàn)語言,而原作者使用的是 JavaScript ,這應該和作者的背景有關。我相信對于大多數(shù)開發(fā)者來說, Python 的對象機制比 JavaScript 基于原型的語法應該是更容易閱讀和理解的。

當然,原作者的版本比本文版本在實現(xiàn)上其實是更為完善的,靈活性也更好。如果想要更為優(yōu)雅的實現(xiàn),我們可以考慮使用 Python 元編程,那樣會更接近于作者的實現(xiàn),但也會讓程序的復雜性大為增加。如果讀者有興趣,不妨對照著去讀讀原作者的版本。

在淘寶上怎么賣游戲幣

親,你如果是商家的話可以:1、申請?zhí)詫氋~號 2、選擇“我要開店”并填寫好相關信息(證件信息等)3、上架商品并歸為“虛擬類目”4、可以選擇交易類型(賣家代充、卡密出售、自動充值等)如果是卡密出售需要在淘寶申請后將卡密在淘寶上記錄(交易成功后淘寶負責自動發(fā)卡)如果賣家代充也要給淘寶登記,到時候淘寶會讓買家填寫要充值的賬號、大區(qū),之后你再根據(jù)買家提供的信息為其充值就行了!

網(wǎng)站題目:javascript種樹,js樹結(jié)構(gòu)
鏈接地址:http://chinadenli.net/article12/dsiccgc.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名手機網(wǎng)站建設外貿(mào)建站定制網(wǎng)站搜索引擎優(yōu)化響應式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設計