如何使用可变数据在Haskell中有效地建模关系

时间:2014-12-12 17:46:26

标签: haskell

我遇到与this类似的问题。但是,它涉及很多更新,我不确定IxSet是否是解决方案。

基本上我正在编写一个优化仓库布局的应用程序。没有数据库或任何东西;它只是简单的数据来操作并在最后生成一个文件。仓库由不同尺寸的货架制成;货架包含不同尺寸的箱子;我们的目标是找到最好的安排(或者至少是一个好的安排),把盒子放在哪里以便它们都适合。

基本模型(实际上并不重要)是:

data Dimension = Dimension {length::Double, width::Double, height::Double}
data Box = Box  {boxId :: Int,  boxDim:: Dimension }
data Shelf = Shelf {shelfId :: Int, shelfDim :: Dimension, postion :: ... }

现在,第一个问题是有一个货架图。有些架子背靠背。我需要知道它,因为可以调整一个架子的深度(以相反的方式修改后架)。 我还需要知道对面的架子和下一个架子。

对此进行建模的最有效方法是什么?

我的第一个想法是:

data Shelf = Shelf { ... , backShelf :: Maybe Shelf 
                         , frontShelf :: Maybe Shelf
                         , nextShelf :: Maybe Shelf
                   }

现在,数据在Haskell中是不可变的,那么如何更新Shelf?我的意思是,想象一下我有Shelf的列表;如果我修改一个,那么我需要更新它的所有副本?

我的第二个想法是使用id代替:

newtype ShelfId = Int
data Shelf = Shelf { ..., backShelfId :: Maybe ShelfId ... }

或者我应该使用外部图表?像

这样的东西
 back, front, next :: [(shelfId, shelfId)]

第二个问题如何模拟一个盒子属于一个架子的事实。

我想到的可能方法是:

  data Box = Box { ..., shelf :: Shelf }

  data Box = Box { ..., shelfId :: Maybe ShelfId }   

  data Shelf = Shelf { ..., boxes = [Box] }

  data Shelf = Shelf { ..., boxIds = [BoxId] }

外部图表

 boxToShelf :: [(BoxId, ShelfId)]

另外,正如我所说,这些操作涉及大量更新;我可能会逐个向每个架子添加盒子,这对于不可变数据来说效率非常低。

我认为另一种解决方案是使用STRef或同等的:

data Box = { ..., shelf :: STRef Shelf }

感觉就像使用指针一样,所以这可能不是一个好主意。

更新

这不是XY问题,也不是玩具问题。这是一个真正的仓库应用程序(100个货架和3000个盒子)。它需要能够绘制和加载现有数据(框及其位置)。优化只是其中的一小部分,可能是半手动的。

所以我的问题是关于表示可变对象之间的关系而不是基本的组合问题。

4 个答案:

答案 0 :(得分:1)

了解有关优化算法如何工作的更多信息会有所帮助。

问题的核心是一个数据结构,它跟踪哪些盒子在哪个架子上(反之亦然)。我们称之为"配置"。

组合搜索算法在探索所有可能配置的空间时,从旧的配置中创建新配置。在任何时候,内存中都有几种配置 - 一种用于递归搜索的每个堆栈帧。

另一方面,像本地搜索这样的算法只有一个(全局)数据结构,它使用启发式或随机性进行变异,直到找到足够好的解决方案。

您最喜欢的算法是什么?

更新:请注意,可能没有一个表示适用于所有用例。要存储数据,您只需要从架子到盒子的地图。对于显示,您可能会发现它也很方便,也有反向映射(框 - >架子)。为了优化,您可能需要使用可变数组模拟问题以进行有效更新。

更新2:我会尝试使用presistent数据结构方法,看看它的工作情况。

type BoxId = Int
type ShelfId = Int

data Shelf = Shelf { ... just the dimensions of the shelf ... }
data Box   = Box { ... just the dimensions of the box ... }

data Configuration = {
    shelves       :: IntMap Shelf,    -- map from shelf id to shelf characterstics
    boxes         :: IntMap Box,      -- map from box id to box characteristics
    boxesForShelf :: IntMap [BoxId],  -- map from shelf id to box ids
    shelfForBox   :: IntMap ShelfId   -- map from box id to shelf id (or 0)
}

然后写下这个函数:

assignBox :: BoxId -> ShelfId -> Configuration -> Configuration

为了提高效率,您还可以编写如下内容:

assignBoxes :: [BoxId] -> ShelfId -> Configuration -> Configuration

并随意编写其他优化函数,以便通过您的用例在配置中进行其他批量更新。

你可能会发现在Box结构中使用BoxId很方便(并且同样适用于ShelfId / Shelf结构)......但你并不一定需要它。但是,使用单独的地图可以更好地处理盒子和架子之间的关系。

我将boxesForShelf定义为IntMap [BoxId]只是因为它听起来每个架子上只有少量的盒子。也许那是无效的。

希望这有帮助。

答案 1 :(得分:1)

为什么不使用persistent? 我已经以cabal包的形式整理了一个示例应用程序,供您在https://github.com/gxtaillon/Shelves使用。

  

坚持遵循类型安全和简洁的指导原则,   声明性语法。其他一些不错的功能是:

     
      
  • 数据库无关。 PostgreSQL,SQLite,MySQL和MongoDB都有一流的支持,支持Redis实验。
  •   
  • 方便的数据建模。 Persistent允许您建模关系并以类型安全的方式使用它们。默认的类型安全持久性   API不支持连接,允许支持更多的连接   存储层。连接和其他SQL特定功能可以   通过使用原始SQL层(具有非常少的类型)实现   安全)。另外一个库Esqueleto建立在   持久数据模型,添加类型安全的连接和SQL   功能。
  •   
  • 自动执行数据库迁移
  •   

只要您知道需要存储哪些数据,您就会拥有一个有效的数据库,并且能够开始使用该优化算法,而无需担心性能,可扩展性或重新发明轮子。

models - 包含数据库模式定义的文件

Shelf
    hrId Text
    length Double
    width Double
    height Double
    UniqueShelf hrId
    deriving Show
Box
    hrId Text
    length Double
    width Double
    height Double
    UniqueBox hrId
    deriving Show
Storage
    shelfId ShelfId
    boxId BoxId
    UniqueStorage shelfId boxId
    deriving Show

Model.hs - 导入models并生成相应类型

的位置
{-# LANGUAGE EmptyDataDecls             #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
module Model where
import Database.Persist.Quasi
import Database.Persist.TH
import ClassyPrelude

share [mkPersist sqlSettings, mkMigrate "migrateAll"]
    $(persistFileWith upperCaseSettings "models")

Main.hs - 示例应用程序

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Model
import Control.Monad.IO.Class  (liftIO)
import Database.Persist.Sqlite hiding (master, Connection)

main :: IO ()
main = runSqlite ":memory:" $ do
    runMigration migrateAll

    myShelfId <- insert $ Shelf "ABCD.123" 10.0 1.5 2.0
    thatBoxId <- insert $ Box "ZXY.098" 1.0 1.0 1.0
    thisBoxId <- insert $ Box "ZXY.099" 2.0 1.0 1.0

    _ <- insert $ Storage myShelfId thatBoxId
    _ <- insert $ Storage myShelfId thisBoxId

    myStorage <- selectList [StorageShelfId ==. myShelfId] []
    liftIO $ print (myStorage :: [Entity Storage])

    update myShelfId [ShelfWidth +=. 0.5]

    thatBox <- get thatBoxId
    liftIO $ print (thatBox :: Maybe Box)
    myShelf <- get myShelfId
    liftIO $ print (myShelf :: Maybe Shelf)

哪个会输出这些内容:

Migrating: [...]
[Entity {entityKey = StorageKey {unStorageKey = SqlBackendKey {unSqlBackendKey = 1}}, entityVal = Storage {storageShelfId = ShelfKey {unShelfKey = SqlBackendKey {unSqlBackendKey = 1}}, storageBoxId = BoxKey {unBoxKey = SqlBackendKey {unSqlBackendKey = 1}}}},Entity {entityKey = StorageKey {unStorageKey = SqlBackendKey {unSqlBackendKey = 2}}, entityVal = Storage {storageShelfId = ShelfKey {unShelfKey = SqlBackendKey {unSqlBackendKey = 1}}, storageBoxId = BoxKey {unBoxKey = SqlBackendKey {unSqlBackendKey = 2}}}}]
Just (Box {boxHrId = "ZXY.098", boxLength = 1.0, boxWidth = 1.0, boxHeight = 1.0})
Just (Shelf {shelfHrId = "ABCD.123", shelfLength = 10.0, shelfWidth = 2.0, shelfHeight = 2.0})

答案 2 :(得分:0)

因此,让我们采取最简单的情况:需要保持一些矩形的矩形,不允许堆叠。然后我们可以提供一个新的&#34;货架&#34;当我们在旧的&#34;架子&#34;:

中放置一个矩形时
newtype Box = Box Int Int Int deriving (Eq, Ord, Show)
newtype Shelf = Shelf Int Int Int deriving Show
type ShelfID = Int
type BoxID = Int
type ShelfBox = (ShelfID, BoxID)

fitsOn :: (Int, Box) -> (Int, Shelf) -> Maybe (ShelfID, Shelf)
fitsOn (bid, Box bw bh) (sid, Shelf sw sh) 
   | bw <= sw && bh <= sh = Just (sid, Shelf (sw - bw) sh)
   | otherwise = Nothing

从最宽的框开始,进行深度优先搜索可能效率最高:

import Data.IntMap.Strict (IntMap, (!))
import Data.IntMap.Strict as IntMap
import Data.List (sort)

collect (mx : mxs) = case mx of 
    Just x -> x : collect mxs
    Nothing -> collect mxs

-- need to feed something like `IntMap.fromList $ zip [0..] $ sort bs` to `boxes`:
search :: IntMap Box -> IntMap Shelf -> [ShelfBox] -> Maybe [ShelfBox]
search boxes shelves acc 
    | IntMap.empty boxes = Just acc
    | otherwise = case collect $ (map process) options of
        [] -> Nothing
        (x : xs) -> Just x
    where (box, boxes') = IntMap.deleteFindMax boxes
          options = collect [box `fitsOn` shelf | shelf <- IntMap.toList shelves]
          process (sid, s') = search boxes' (IntMap.insert sid s') ((sid, fst box) : acc)

现在我们怎么能把两个架子放在彼此之上,总高度为H,否则独立高度?我们可以把这两个写在我们的货架上:

vert_shelves other_shelves h w = [Shelf w (h - i) : Shelf w i : other_shelves | i <- [0..h - 1]]

如果你想要盒子上的盒子,你将开始从fitsOn(上面和旁边)开始产生两个矩形并试图聚合&#34;上面的#34;盒子进入任何其他来自同一架子并且具有相同高度的盒子,这将需要一些重新设计这个东西。你可能也想要一个无限数量的盒子,如果没有改写那些maybes的传递方式,这将会有点棘手。

答案 3 :(得分:0)

听起来你需要一个关系数据库管理系统(RDBMS)。如果你不想在Haskell中实现一个(你可能不会这样做,那么我建议你使用优秀的,免费的Postgres并在Haskell中与它进行通信。使用Opaleye的类型安全和可组合的方式。 [免责声明:我写过Opaleye。]