我已经通过以下方式定义了一个名为FS的数据类型:
type Name= String
data Ext where { Txt::Ext ; Mp3::Ext ; Jar::Ext ; Doc::Ext ; Hs::Ext }
deriving (Eq, Show)
data FS where { A :: (Name,Ext) -> FS;
Dir :: Name-> [FS] -> FS }
deriving (Eq, Show)
(A代表文件,Dir代表目录)
我试图创建一个给定FS
(目录)的函数,它返回相同的FS
,但在所有级别按字母顺序排序,我到目前为止的尝试如下:
orderFS :: FS-> FS
orderFS (Dir x y) = Dir x (map orderFS (sort y));
orderFS (A (x,y)) = A (x,y);
我唯一缺少的是一个名为" sort"需要[FS]
并按{0}字段按字母顺序返回
我读到有Name
排序等功能可以提供帮助,但我必须在不使用Data.List
以外的任何其他内容的情况下执行此操作。
那么我应该如何实现这样的功能呢?提前致谢
答案 0 :(得分:1)
我不相信Prelude中有任何排序功能,但在Data.List
这样的模块中却没有。请注意,Data.List
位于作为GHC一部分的基础包中,因此基本上在Prelude
可用的任何情况下,我都会想到Data.List
也是如此 - 你不应该不需要下载/包含任何其他软件包以便使用它。
也就是说,如果你想编写自己的排序函数,最好采用现有的简单排序算法并使用它。在Haskell中编写快速排序和合并排序有非常简洁/简单的方法,尽管明显的实现有时并不具有您期望的相同的确切性能特征。例如,合并排序具有大致相同的渐近,但是将列表分成两个实际上需要一些时间,因为列表是单链接的,因此您必须遍历其中的一半才能将其拆分。但是,它可以是一个非常好的短函数,看起来很像算法,并且可能值得做一个学习练习。
另外,我注意到您将Ext
和FS
类型定义为GADT,我对此并不十分肯定;我建议使用非GADT语法,这个例子更简单:
type Name = String
data Ext = Txt | Mp3 | Jar | Doc | Hs deriving (Eq, Show)
data FS = A Name Ext | Dir Name [FS] deriving (Eq, Show)
为了按名称对它们进行排序,可能值得编写一个可以获得FS名称的简单访问函数:
name :: FS -> Name
name (A n _) = n
name (Dir n _) = n
另一种方法是将两种情况共同的事物(名称)分解出来:
data FS = NamedFS { name :: Name, fs :: UnnamedFS }
data UnnamedFS = A Ext | Dir [FS]
此处的第一个条目使用记录语法,除其他外,它将自动生成name :: FS -> Name
访问者,以及fs :: FS -> UnnamedFS
。
对于实际排序,它看起来很像合并排序的算法描述。首先,让我们编写一个函数将列表分成两部分:
split :: [a] -> ([a], [a])
split xs = splitAt (length xs `div` 2) xs
我们还需要一个函数来合并两个排序列表:
merge :: Ord a => [a] -> [a] -> [a]
merge [] x = x
merge x [] = x
merge (x:xs) (y:ys) | x < y = x:merge xs (y:ys)
| otherwise = y:merge (x:xs) ys
实际上,这不是我们想要的,因为它总是使用Ord实例中的<
;相反,我们想要一些带有比较功能的东西。在这种情况下,我们假设如果比较函数在使用x和y调用时返回true,则x在概念上小于y。
merge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
merge _ [] x = x
merge _ x [] = x
merge le (x:xs) (y:ys) | x `le` y = x:merge le xs (y:ys)
| otherwise = y:merge le (x:xs) ys
现在,我们可以像往常一样实现mergesort:
mergeSort :: (a -> a -> Bool) -> [a] -> [a]
mergeSort _ [] = []
mergeSort _ [x] = [x]
mergeSort f l = merge f (mergeSort f left) (mergeSort f right)
where (left, right) = split l
并称之为:
-- fss is some [FS]
mergeSort (\x y -> name x < name y) fss
如果我们可以使用Data.Ord
,可以将其进一步简化为:
mergeSort (comparing name) fss
答案 1 :(得分:1)
Data.List
中可以帮助您的功能sortOn
这与函数getName :: FS -> Name
一起允许您通过比较Name
s进行排序。
如果你不能使用Data.List
中的函数,你必须自己实现一个排序算法(其中有很多可供选择)。一个例子是&#34; QuickSort&#34;正如Learn you a Haskell书中所实现的那样:
quicksort :: [FS] -> [FS]
quicksort [] = []
quicksort (x:xs) =
let smallerSorted = quicksort [a | a <- xs, getName a <= getName x]
biggerSorted = quicksort [a | a <- xs, getName a > getName x]
in smallerSorted ++ [x] ++ biggerSorted
请注意,我更改了比较以比较getName
s而不是整个节点。
另一件事:您使用GADT语法来定义数据类型,我看不出您有任何理由这样做。以下是我使用普通数据类型声明来编写它们的方法:
data Ext
= Txt
| Mp3
| Jar
| Doc
| Hs
deriving (Eq, Show)
data FS
= File Name Ext
| Dir Name [FS]
deriving (Eq, Show)
答案 2 :(得分:0)
在我看来,Haskell中最自然的列表排序功能是自下而上的mergesort(Peter Amidon的答案给出了一个自上而下的mergesort)。假设您从列表
开始[25,1,22,2,10,8,6,20,13,28,5,3,11]
第一步是将列表转换为列表列表。最简单的方法是map (:[])
,产生
[[25],[1],[22],[2],[10],[8],[6],[20],[13],[28],[5],[3],[11]]
接下来,您将成对合并列表:
[[1,25],[2,22],[8,10],[6,20],[13,28],[3,5],[11]]
重复!
[[1,2,22,25],[6,8,10,20],[3,5,13,28],[11]]
再一次!
[[1,2,6,8,10,20,22,25],[3,5,11,13,28]]
再一次:
[[1,2,3,5,6,8,10,11,13,20,22,25,28]]
由于我们现在只有一个列表,我们将其解压缩。
为了实现这一点,我认为在这种情况下最好使用自定义类型列表来表达相应的严格性:
infixr 5 :::
data LL a = ![a] ::: LL a | Nil
实施逐步进行。虽然将列表分解为单例是最简单开始流程的方式,但它有点浪费并且会减慢对前几个元素的访问速度。所以,让我们在那里成对:
breakUp :: (a -> a -> Ordering) -> [a] -> LL a
breakUp _cmp [] = Nil
breakUp _cmp xs@[a] = xs ::: Nil
breakUp cmp (x1 : x2 : xs)
| GT <- cmp x1 x2 = [x2,x1] ::: breakUp cmp xs
| otherwise = [x1,x2] ::: breakUp cmp xs
现在我们需要写一个merge
函数:
merge :: (a -> a -> Ordering)
-> [a] -> [a] -> [a]
merge _cmp [] ys = ys
merge _cmp xs [] = xs
merge cmp xss@(x:xs) yss@(y:ys)
| GT <- cmp x y = y : merge cmp xss ys
| otherwise = x : merge cmp xs yss
接下来,我们需要能够成对地合并列表列表:
mergePairwise :: (a -> a -> Ordering)
-> LL a -> LL a
mergePairwise _cmp Nil = Nil
mergePairwise _cmp xs@(_ ::: Nil) = xs
mergePairwise cmp (xs1 ::: xs2 ::: xss)
= merge cmp xs1 xs2 ::: mergePairwise cmp xss
然后我们必须合并直到完成:
mergeAll :: (a -> a -> Ordering)
-> LL a -> [a]
mergeAll _cmp Nil = []
mergeAll _cmp (xs ::: Nil) = xs
mergeAll cmp xss = mergeAll cmp (mergePairwise cmp xss)
现在我们用 glass grass bass做饭!
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
sortBy cmp = mergeAll cmp . breakUp cmp
sort :: Ord a => [a] -> [a]
sort = sortBy compare
通过识别存储在列表列表中的列表永远不会为空,有一点改进上述实现的空间。所以我们可能使用
获得小的性能提升data NonEmpty a = a :| [a]
data LL a = (:::) {-# UNPACK #-} !(NonEmpty a) (LL a) | Nil