我使用大量中小型文档(~2 meg)数据文件,并试图确定基于时间戳查找值的最快方法。
如果我正在查找“查找时间戳 X 的数据”,这将很简单,但我通常希望“查找时间戳在日期 X 之前或日期之前的最新数据”。
以下是具体细节:假设您有一个由 300 座房屋组成的集群,每座房屋偶尔都会收到邮件。您正在监控他们收到的邮件类型。假设您关心 15 类邮件。
感兴趣的问题是“在日期 D 或之前递送到房子的最新邮件类别是什么?”
A. 被引用的数据文件具有以下形式:
<data>
<house house_ID = "XXX" mail_category="YYY" timestamp="ZZZ"/>
<house house_ID = "XXX" mail_category="YYY" timestamp="ZZZ"/>
<house house_ID = "XXX" mail_category="YYY" timestamp="ZZZ"/>
<house house_ID = "XXX" mail_category="YYY" timestamp="ZZZ"/>
...
</data>
B. 数据文件不一定是排序的。如果这对最佳实践产生了影响,请在您的回答中说明。
C. 即使在数据文件中跟踪了大约 300 所房屋,我的工作只需要来自 60 个特定房屋的数据。
D. 信息存在 100 个日期,大多数房屋在这 100 个日期中的 3-20 个收到邮件。
E. 邮件可以全天投递。所以在某一天,一个人可以先得到第 1 类,然后得到第 2 类,最后在晚上得到第 8 类。
F. 对于典型的数据文档,可能会要求大约 10 次给定房屋的信息。
这里有两条可能的路径,以及我对每一条的想法。我希望其中一位 XSLT3 超级程序员会有更好的选择。
解决方案 1:大地图 地图通常是许多 XSLT3 速度问题的首选解决方案,但我不确定它们对这个问题的适用程度如何,因为您似乎必须创建一个巨大的地图,其中大部分是您实际上不需要的。
我尝试过的草图如下:
<xsl:variable name="sorted_data" select="saxon:sort(houses I want from data, by date)"/>
<xsl:variable name="dates" select="distinct-values($sorted_data/date:date(@timestamp))"/>
<xsl:variable name="mail.map.pieces" as="map(*)*">
<xsl:for-each-group select="$sorted_data" group-by="$house_number">
<xsl:iterate select="current-group">
Use iteration to form one map for every possible date/house, reading data file once.
map has form map{concat($date'--'$house_number) := last_mail_type}
Note that this internal piece requires a bit of extra computation because you need a map for _every_ date in $dates, but the set being iterated over only contains nodes for dates on which the house received mail.
</xsl:iterate>
</xsl:for-each-group>
</xsl:variable>
<xsl:variable name="mail.map" select="map:new($mail.map.pieces)"/>
问题是构建这个地图需要 60 * 100 map{} 命令,其中只有 10% 会被使用。也有几个电话来处理失踪天数的问题。
解决方案 2:小地图
使用地图的另一个选项是将给定房屋的所有邮件数据与该房屋 ID 相关联,然后稍后进行搜索/过滤:
<xsl:variable name="sorted_data" select="saxon:sort(houses I want from data, by date)"/>
<xsl:variable name="dates" select="distinct-values($sorted_data/date:date(@timestamp))"/>
<xsl:variable name="mail.map.pieces" as="map(*)*">
<xsl:for-each-group select="$sorted_data" group-by="$house_number">
<xsl:sequence select="map{house_numer := current-group()}/>
</xsl: for-each-group
</xsl:variable>
<xsl:variable name="mail.map" select="map:new($mail.map.pieces)"/>
稍后,要回答给定日期的问题,您需要在与该房屋相关的少量数据中进行选择:
房子 x = map:get($mail.map, x)[current()/date le d][last()]/@mail_category 在日期 d 之前的最新邮件类别
这显然需要较少的工作来创建地图,但由于额外的过滤,每次检索数据需要更多的工作。还有一个问题是“大地图”解决方案允许我将房屋/日期直接连接到我想要的值[邮件类型],而这种方法需要我将键值连接到节点,所以会有是从该节点读出邮件类别信息的附加成本。
与解决方案 1 相比,它的最后一个优势是它很容易涵盖“在时间 T或之前的最新邮件类型是什么”的替代问题[因此,它不是基于日期,而是基于实际时间戳。]
解决方案 3:密钥 另一种选择是使用密钥,将给定房屋的所有邮件都键入其 house_id。从理论上讲,这应该与“小地图”选项非常相似。您使用一个键来检索您想要的房子的邮件,然后您过滤以选择最近但在所需日期之前或之前的邮件。
但是,在构造部分存在差异。这些地图需要针对每个组进行一次操作,然后对每个房屋进行一次地图操作。我期望的密钥的构建需要更少的时间。
另一方面,键仅适用于文档模式。如果原始文档没有排序,那么我需要对文档进行排序并在内存中创建一个新文档来处理。我不能简单地在排序的节点序列上构建一个键。我不知道在内存中创建此文档的相对成本,但我想这比在解决方案 2 中构建地图所需的时间要多。
如果原始文档已经排序,那么key可能会更快?