排除文件系统数据而不使用Prelude

时间:2016-11-28 15:02:06

标签: sorting haskell

我已经通过以下方式定义了一个名为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以外的任何其他内容的情况下执行此操作。

那么我应该如何实现这样的功能呢?提前致谢

3 个答案:

答案 0 :(得分:1)

我不相信Prelude中有任何排序功能,但在Data.List这样的模块中却没有。请注意,Data.List位于作为GHC一部分的基础包中,因此基本上在Prelude可用的任何情况下,我都会想到Data.List也是如此 - 你不应该不需要下载/包含任何其他软件包以便使用它。

也就是说,如果你想编写自己的排序函数,最好采用现有的简单排序算法并使用它。在Haskell中编写快速排序和合并排序有非常简洁/简单的方法,尽管明显的实现有时并不具有您期望的相同的确切性能特征。例如,合并排序具有大致相同的渐近,但是将列表分成两个实际上需要一些时间,因为列表是单链接的,因此您必须遍历其中的一半才能将其拆分。但是,它可以是一个非常好的短函数,看起来很像算法,并且可能值得做一个学习练习。

另外,我注意到您将ExtFS类型定义为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