代码之家  ›  专栏  ›  技术社区  ›  mb14

在Haskell中构建(惰性)映射的有效方法

  •  4
  • mb14  · 技术社区  · 6 年前

    Map 在哈斯克尔:

    1. 从列表中使用 fromList
    2. fromAscList
    3. 利用这个事实 地图 Monoid (或 Semigroup )和concat singletons .

    我知道 摊销 α1的复杂度为O(n*log(n)),其中α2为O(n)。 我想3应该大致相当于1,可能需要融合。

    这个 重要的是,因为Haskell在默认情况下是懒惰的,即使来自映射的查找是O(log(n)),实际上它可以与映射本身的构造交错,即O(n*log(n)),这可以在实践中使查找成为O(n*log(n))(特别是当您每次需要构建映射时)。如果使用硬编码映射,也可能会附加此项

    例如,我认为 lookup 'b' (fromList [('a', 1), ('b', 2)]) 实际上相当于只在列表中查找d而不使用中间映射?

    那么,1和3之间有什么区别吗,或者是排序列表和调用 ?

    另外,如果我需要一个映射只计算一次,我需要确保GHC不内联它,所以它是在函数之间共享的吗?

    我意识到这个问题可能有点模糊,事实上与我最近遇到的不同用例相对应。

    第一个对应于“静态连接”。我有一个管理项目的应用程序,每个项目代码可以按样式和变化进行拆分(例如“T恤-红色”=>(“T恤”,“红色”)。分割是基于规则(和regexp)的,而且非常慢。要不总是重新计算规则,只需执行一次并存储在db表中。我有几个纯函数,需要能够分割项目代码,所以我给他们传递一个函数 Text -> (Text, Text) . 该函数实际上是部分应用于地图的查找。代码与此类似

    getSplitter :: Handler (Text -> (Text, Text))
    getSplitter = do
          sku'style'vars <- runDB $ rawSQL "SELECT sku, style, var FROM style_cache " [] -- load the split table
          let sku'map = fromList [ (sku, (style,var)) 
                                 | (sku, style, var) <- sku'style'vars
                                 ]
          return $ flip lookup sku'map
    

    这一个可以很容易地加快通过排序项目 sku 使用 fromDistinctAscList (实际上比 从列表

    第二种情况是在两个表之间执行手动连接,我通常会执行以下操作

    do
       sku'infos <- selectList [] [] -- load item info
       let skuInfo = fromList sku'infos
    
       orderLines <- selectList [] [] -- load orders
       -- find infos corresponding to order items
       mapM (\o -> (o, lookup (orderSku o) skuInfo) orderLines
    

    在这里,我可以在SQL中对sku'infos进行排序,并使用fromDistinctAscList。

    第三种情况是获取与不同表中类别项相关的杂项信息。

    例如,我希望能够按类别比较sales(sales表)和purchase(purchases表)。

    SELECT style, sum(sales.amount), sum(purchase.amount)
    FROM style_cache
    LEFT JOIN sales USING(sku)
    LEFT JOIN purchases USING(sku)
    GROUP by style
    

    然而,这是一个简化的示例,在实践中,聚合要复杂得多,必须在Haskell和join中完成。为此,我将分别加载每个表(在sql中对所能做的进行分组),并返回 Map Style SalesInfo Map Style PurchaseInfo 等。。。合并他们。这张桌子很大,我意识到我最终会把所有东西都加载到内存中,而手动“压缩”东西可能会更有效,但我不知道怎么做。

    1 回复  |  直到 6 年前
        1
  •  2
  •   Daniel Wagner    6 年前

    我不确定我是否理解这个问题背后的全部动机,但我要发表几点看法:

    1. Map 脊椎是严格的——这意味着 地图 密钥本身被强制(至少足够做所有必要的比较)在 操作。所以我想 Data.Map.lookup k (fromList xs) xs )但我想 Prelude.lookup k xs 采取O(n)比较(实际上只是平等检查,但通常这是相当复杂的比较)。
    2. 如果 fromAscList . sort 可靠地比 fromList ,这是中的性能错误 Data.Map fromList = fromAscList . sort . 如果真是这样的话,我会很惊讶的。人们花了不少时间优化 containers ,所以我不希望看到任何水果挂得那么低。