类似于Set的数据结构,维护插入顺序?

时间:2014-08-16 02:03:49

标签: haskell data-structures

我正在寻找的属性是

  • 最初维护广告订单
  • 以插入顺序横切
  • 当然要保持每个元素都是唯一的

但是有些情况下可以忽略插入顺序,例如......

  • 检索两个不同集之间的差异
  • 执行联合,两组消除任何重复

Java's LinkedHashSet似乎正是我所追求的,除了它不是用Haskell编写的。

当前&初步解决方案

最简单(也是相对低效)的解决方案是将其作为列表实现,并在需要时将其转换为集合,但我相信可能有更好的方法。

其他想法

我的第一个想法是将其实现为Data.Set的{​​{1}} newtype (Int, a),其中第一个元组索引和第二个索引({{1} })是实际价值。我很快意识到这不会起作用,因为该集合将允许a类型的重复,这将破坏使用集合的整个目的。

同时维护一个列表和一组? (没)

我的另一个想法是有一个抽象的数据类型,它既可以维护数据的列表也可以设置表示,而且听起来效率也不高。

概括

Haskell中是否存在这种数据结构的下降实现?我已经看过Data.List.Ordered,但它似乎只是将列表操作添加到列表中,这听起来非常低效(但如果我找不到解决方案,我可能会解决这个问题) 。建议here的另一个解决方案是通过finger tree实现它,但如果它已经解决了问题,我宁愿不再重新实现它。

1 个答案:

答案 0 :(得分:9)

您当然可以将Data.Set(Int, a)同构,但包含在具有不同Eq实例的新类型中:

newtype Entry a = Entry { unEntry :: (Int, a) } deriving (Show)

instance Eq a => Eq (Entry a) where
    (Entry (_, a)) == (Entry (_, b)) = a == b

instance Ord a => Ord (Entry a) where
    compare (Entry (_, a)) (Entry (_, b)) = compare a b

但是,如果你想要自动递增你的索引,这不能完全解决你所有的问题,所以你可以围绕(Set (Entry a), Int)创建一个包装器:

newtype IndexedSet a = IndexedSet (Set (Entry a), Int) deriving (Eq, Show)

但这确实意味着您必须重新实施Data.Set才能尊重这种关系:

import qualified Data.Set as S
import Data.Set (Set)
import Data.Ord (comparing)
import Data.List (sortBy)

-- declarations from above...

null :: IndexedSet a -> Bool
null (IndexedSet (set, _)) = S.null set

-- | If you re-index on deletions then size will just be the associated index
size :: IndexedSet a -> Int
size (IndexedSet (set, _)) = S.size set

-- Remember that (0, a) == (n, a) for all n
member :: Ord a => a -> IndexedSet a -> Bool
member a (IndexedSet (set, _)) = S.member (Entry (0, a)) set

empty :: IndexedSet a
empty = IndexedSet (S.empty, 0)

-- | This function is critical, you have to make sure to increment the index
--   Might also want to consider making it strict in the i field for performance
insert :: Ord a => a -> IndexedSet a -> IndexedSet a
insert a (IndexedSet (set, i)) = IndexedSet (S.insert (Entry (i, a)) set, i + 1)

-- | Simply remove the `Entry` wrapper, sort by the indices, then strip those off
toList :: IndexedSet a -> [a]
toList (IndexedSet (set, _))
    = map snd
    $ sortBy (comparing fst)
    $ map unEntry
    $ S.toList set

但在大多数情况下,这是相当简单的,您可以根据需要添加功能。您唯一需要真正担心的是删除操作。你重新索引一切还是只关心订单?如果您只是关注订单,那么它很简单(并且size可以通过实际计算基础Set的大小而保持次优,但是如果你重新索引然后你可以在O(1)时间内获得你的大小。应根据您尝试解决的问题来决定这些决定。


  

如果它已经解决了问题,我宁愿不再重新实现它。

这种方法绝对是一种重新实施。但是在大多数情况下它并不复杂,可以很容易地变成一个很好的小库来上传到Hackage,并且在没有太多簿记的情况下保留了很多集合的好处。