【问题标题】:Check a list if sorted in ascending or descending or not sorted in haskell?检查列表是否按升序或降序排序或未按haskell排序?
【发布时间】:2018-04-25 11:18:56
【问题描述】:

我是 Haskell 的新手。我刚刚学习了两周的 Haskell。我真的不明白 if else 语句和 haskell 的列表理解是如何工作的。所以我想做一个函数,可以找出排序类型,例如列表是按升序或降序排序或根本不排序的。我知道如何检查列表是否按升序和降序排序,但我不知道如何检查列表是否根本没有排序。

data SortType = Ascending | Descending | NotSorted deriving (Show)

sorted :: (Ord a) => [a] -> TypeOfSort 
sorted [] = Ascending
sorted [x] = Ascending
sorted (x:y:xs) | x < y = sorted (y:xs) 
            | otherwise = Descending
sorted_  = Ascending

如果有人能告诉我该怎么做,那将是一个很大的帮助。谢谢你。 P/s:这不是作业/工作的东西,而是我想学习的东西。

【问题讨论】:

  • 提示:还有第四种可能性,即列表包含重复 n 次的单个值。这样的列表不是未排序,但也不是完全按升序或降序排序。当您逐个元素地检查它时,想想您对给定列表的信念可能会如何在这四种可能性之间发生变化。
  • AscendingDescending,严格来说,真的只适用于没有重复的列表。更通用的术语 NondecreasingNonincreasing 可以描述可能包含重复项的列表,并且两者都同样适用于单个重复项的退化情况。
  • 这是不是列表理解!这是"guards"
  • @chepner 如何检查列表是否重复?
  • @WillNess 我试过了,但它给了我“输入'|'中的解析错误"

标签: list sorting haskell


【解决方案1】:

你的函数有问题的部分是| otherwise = Descending。根据您的函数定义,如果列表中有两个连续的示例,例如x &gt;= y,则该函数为降序。这不是True:如果 all 两个连续的元素 x &gt; y(或 x &gt;= y,如果您不要求它严格降序,则函数正在下降) .

此外,这里还有一个问题是一个包含一个元素(或没有元素)的列表可以被视为AscendingDescending。所以我认为我们要做的第一件事就是定义一些语义。我们可以决定使输出成为TypeOfSort 项目的列表,或者我们可以决定扩展TypeOfSort 的选项数量。

在这个答案中,我将选择最后一个选项。我们可以将TypeOfSort 扩展为:

data TypeOfSort = Ascending | Descending | Both | NotSorted
    deriving (Show)

现在我们可以处理函数本身了。这里的基本情况当然是空列表[] 和一个元素列表[_]

sorted [] = Both
sorted [_] = Both

现在我们需要定义归纳案例。列表何时升序排序?如果所有元素(严格)大于前一个元素。类似地,如果所有元素(严格)小于前一个元素,则列表按降序排序。现在让我们假设严格性。以后修改函数定义很容易。

因此,如果我们有一个包含两个或多个元素的列表,如果以第二个元素开头的列表是 AscendingBothx &lt; y,则列表是 Ascending,或者换句话说:

sorted (x:y:xs) | Both <- sort_yxs, x < y = Ascending
                | Ascending <- sort_yxs, x < y = Ascending
    where sort_yxs = sorted (y:xs)

降序也是如此:如果列表的其余部分按降序排列,并且第一个元素大于第二个元素,则列表按降序排列:

                | Both <- sort_yxs, x > y = Descending
                | Ascending <- sort_yxs, > y = Descending
    where sort_yxs = sorted (y:xs)

在所有剩余的情况下,这意味着列表的某些部分是Ascending,某些部分是Descending,因此列表是NotSorted

                | otherwise = NotSorted

或将这些放在一起:

sorted [] = Both
sorted [_] = Both
sorted (x:y:xs) | Both <- sort_yxs, x < y = Ascending
                | Ascending <- sort_yxs, x < y = Ascending
                | Both <- sort_yxs, x > y = Descending
                | Ascending <- sort_yxs, x > y = Descending
                | otherwise = NotSorted
    where sort_yxs = sorted (y:xs)

重构:使TypeOfSort 成为Monoid

上面的定义包含很多边缘情况,这使得编写一个简单的程序变得很困难。我们可以通过引入一些实用函数来简化它。例如,这可以通过定义一个接受两个TypeOfSorts 然后返回交集的函数来完成。这样的函数可能如下所示:

intersect Both x = x
intersect x Both = x
intersect Ascending Ascending = Ascending
intersect Descending Descending = Descending
intersect _ _ = NotSorted

这实际上形成了一个以Both 为标识元素的monoid

instance Monoid where
    mappend Both x = x
    mappend x Both = x
    mappend Ascending Ascending = Ascending
    mappend Descending Descending = Descending
    mappend _ _ = NotSorted

    mempty = Both

现在我们可以将定义改写为:

sorted [] = Both
sorted [_] = Both
sorted (x:y:ys) | x > y = mappend rec Ascending
                | x < y = mappend rec Descending
                | otherwise = NotSorted
    where rec = sorted (y:ys)

【讨论】:

  • 感谢您的明确回答,但它在输入“|”时给了我解析错误...
  • 非常感谢您的解释,先生!
  • @JayRowen:是的,当将片段合并在一起时出了点问题。
  • @WillNess:这正是我坚持认为这是一个坏主意的原因:因为不幸的是,薪水太高,无法支付人们寻找错别字:)
  • 还有,为什么sorted (1:3:2:undefined)不应该返回NotSorted
【解决方案2】:

我的解决方案不使用排序功能且不使用递归:

data SortType = Ascending | Descending | NotSorted | Flat | Unknown deriving (Show)

sorted :: (Ord a) => [a] -> SortType 
sorted [] = Unknown
sorted [a] = Flat
sorted xs  
    | and [x == y | (x, y) <- zipPairs xs] = Flat
    | and [x <= y | (x, y) <- zipPairs xs] = Ascending
    | and [x >= y | (x, y) <- zipPairs xs] = Descending
    | otherwise                            = NotSorted

zipPairs :: [a] -> [(a, a)]
zipPairs xs = zip xs (tail xs)

使用 lambdas 可能更快

all (\(x, y) -> x <= y) (zipPairs xs)

在 Python 中我可能会做这样的事情

from itertools import izip, islice

n = len(lst)

all(x <= y for x, y in izip(islice(lst, 0, n - 1), islice(lst, 1, n)))

【讨论】:

  • 你不需要init。此外,您的版本将在仅检查其前三个元素后正确返回 NotSorted[1,3,2,1/0],这是应该的;对此表示敬意。你也可以有另一个子句,| and [x == y | (x, y) &lt;- zipCustom xs] = Flat;并可能将结果称为[]“未知”(类似于 Redu 的回答)。
  • 是的,确实如此,因为 Haskell 的懒惰。它可以在无限列表上正常工作,并尽快检测到NotSorted。不像威廉的回答。
【解决方案3】:

我猜你也可以这样做;

data Sorting = Why | Karma | Flat | Descent | Ascent deriving (Show)

howSorted :: Ord a => [a] -> Sorting
howSorted xs | xs == []                    = Why
             | all (== head xs) $ tail xs  = Flat
             | and $ map (uncurry (<=)) ts = Ascent
             | and $ map (uncurry (>=)) ts = Descent
             | otherwise                   = Karma
             where ts = zip xs $ tail xs

【讨论】:

  • a==Truea 相同。
  • @Will Ness 这是一个愚蠢的错误,无论何时,我都无法停止犯。幸运的是,它是无害的。
【解决方案4】:

我们已经从 Data.List 中实现了sort,我们可以使用它来实现这一点。

import Data.List (sort)

sorted xs 
    | sort xs == xs = Ascending
    | reverse (sort xs) == xs = Descending
    | otherwise = NotSorted

如果排序后的列表等于列表,则必须使用升序排序。

如果排序后的列表,反转后,等于列表,那么它必须使用降序排序。

否则不排序。

正如@Benjamin-Hodgson 指出的那样,边缘条件可能需要考虑。使用此实现,空列表计为已排序,一个项目的列表也是如此,同一项目的重复列表也是如此。

用法:

λ> sorted [1..5]
Ascending
λ> sorted [5,4..1]
Descending
λ> sorted [1,3,1]
NotSorted
λ> sorted []
Ascending
λ> sorted [1]
Ascending
λ> sorted [1,1,1]
Ascending

或者,我们可以使用sortBy 来反转情况,以避免必须完全反转列表。这只是按默认比较函数排序,参数翻转,因此小于变为大于。

import Data.List (sort, sortBy)

sorted xs 
    | sort xs == xs = Ascending
    | sortBy (flip compare) xs == xs = Descending
    | otherwise = NotSorted

【讨论】:

  • 实际上对列表进行排序是低效的。如果列表已经排序,您可以在 O(n) 中确定,而排序可能需要 O(n lg n)。如果列表未排序,您可能会花费 O(n lg n) 时间对其进行排序,然后再进行一次或多次线性时间传递以与原始列表进行比较,而对原始列表进行 2 次比较就可以告诉您列表未排序。
  • @chepner 没错,无论如何,威廉的回答要全面得多。我通常会尝试编写清晰的代码并稍后进行优化,这可能不是最好的主意
猜你喜欢
  • 1970-01-01
  • 2021-03-27
  • 1970-01-01
  • 1970-01-01
  • 2018-03-18
  • 2018-05-03
  • 1970-01-01
  • 2011-08-25
  • 1970-01-01
相关资源
最近更新 更多