Java's LinkedHashSet似乎正是我所追求的,除了它不是用Haskell编写的。
最简单(也是相对低效)的解决方案是将其作为列表实现,并在需要时将其转换为集合,但我相信可能有更好的方法。
我的第一个想法是将其实现为Data.Set
的{{1}} newtype
(Int, a)
,其中第一个元组索引和第二个索引({{1} })是实际价值。我很快意识到这不会起作用,因为该集合将允许a
类型的重复,这将破坏使用集合的整个目的。
我的另一个想法是有一个抽象的数据类型,它既可以维护数据的列表也可以设置表示,而且听起来效率也不高。
Haskell中是否存在这种数据结构的下降实现?我已经看过Data.List.Ordered,但它似乎只是将列表操作添加到列表中,这听起来非常低效(但如果我找不到解决方案,我可能会解决这个问题) 。建议here的另一个解决方案是通过finger tree实现它,但如果它已经解决了问题,我宁愿不再重新实现它。
答案 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,并且在没有太多簿记的情况下保留了很多集合的好处。