• 沒有找到結果。

第三章 IFC 解析器的實現

3.1 雜凑

在詞法階段,我們獲得了 Token,其由兩部分組成:Token 類型和 Token 值,

比如一個 IFC 類別名的 Token,其類型是 EntityName,(就上例而言)其值是

11

IFCPERSON,但很明顯,這個值是一個字元型(String),所以我們並不能直接使 用 new 操作符構建類,所以通常的一個簡單做法是:

// Initialize factory

Map<String, Class> factory = new HashMap<>();

factory.put("IFCPERSON", IfcPerson.class);

// During parsing

IfcPerson person = factory.get("IFCPERSON").newInstance(…);

這是軟體設計模式中常見的工廠模式(Factory Pattern),我們使用一個 Key-Value 容器作為工廠,當我們獲得一個類型為 EntityName 的 Token 後,我們 用它的值在工廠裏尋找對應的類型類(Class),類型類是 Java 反射(Reflection)

裏的基礎,其擁有一個特別的方法 newInstance 來構造對應的類別。這樣我們就通 過一個字串構建出了對應的類別。

考慮本文在第三章所著的 BNF,其中的<ifc-entity>即代表著類型為 EntityName 的 Token,其定義為:

<ifc-entity> ::= [A-Z] [A-Z0-9]*

這裏我們假設了 IFC 的類別名是由大寫字母和數字組成的,所以對於工廠容 器我們使用了 String 作為它的 Key。但實際上 IFC 的類別名是有限的,所以

<ifc-entity>其實也可以寫成如下形式:

<ifc-entity> ::= 'IFCPERSON' | 'IFCDIRECTION' | …

12

有限的組合意味著我們並不一定需要依賴於 HashMap 這類容器。如果可以把 IFC 裏 849 個類別名映射成正整數的話,我們就可以將其儲存於數組之中。數組的 訪問可以認為是恒定 O(1)的,這樣解析器的性能相較於 HashMap 的版本會有非常 大的提升。而為了將 String 映射為 Integer,我們需要找到一個合適的雜凑函數,

且符合以下兩個條件:

1)產生的雜凑沒有碰撞

2)最小雜凑值和最大雜凑值的差值應儘量小,848 是最優解 例如 Java 中 String 的雜凑函數偽代碼如下:

set hash = 0

for each character in string

____hash = hash * 31 + ascii(character) end for

在回圈內部,每一輪雜凑值都會乘上一個質數 31,所以最終的雜凑值也會隨 著字串的長度成幾何級數增加(並溢出)。這樣保證了當我們雜凑字串時,針對 兩個只有微小差別的字串(長度差 1 或者只有一個字母不同等情況),最後得到 的雜凑值差別會非常大,這確保了雜凑的離散性。ascii 函數是獲得字元

(character)ASCII 數值的函數。

但是本研究設計的雜凑需要盡可能地減少離散性,使最終的雜凑分佈在一個 很小的範圍內,以下是實現代碼:

set hash = 0

13

for each character in string

____hash = hash + VALUE(character) * 13 ____hash = hash xor 0x16ADA

end for

hash = hash / 2

其中係數降為了 13,且移到了與字元值相乘的地方,這樣雜凑的增長速度就 會降低,但碰撞的幾率也大幅增加,於是在每次計算後,雜凑值會再跟 0x16ADA

(10110101011011010)進行一次異或(xor)計算,這次操作相當於打亂雜凑值的

#4692875= IFCPROPERTYSET('1w46N3aqXDgflkE91KriAK',#41,…);

#4692877= IFCRELDEFINESBYPROPERTIES('1Hwq42Jnf1e8MaXnNoMs0a',…);

#4692881= IFCAXIS2PLACEMENT3D(#6,$,$);

#4794946= IFCRELAGGREGATES('3o9c2jHPz0UgvyZpd1xn_J',#41,…);

#4692883= IFCCARTESIANPOINT((-5991.,-3463.,0.));

相關文件