我正在学习Haskell.
几天前我回答的一个问题让我对Haskell的这个练习有了灵感,这给了我实验到目前为止学到的一些东西的机会,并给我留下了问题:)
给定宽度和高度的矩形A找到最适合A的时间的矩形B,其中最佳意味着具有最小的周长.whn
我开始的基本思想是生成A的子矩形集,其面积等于div (w * h) n,然后选择具有最小周长的子矩形.
以下是我提出的这个想法的三个实现; 他们是按照时间顺序排列的:我完成了第二次后得到了第三次的灵感,这是我完成第一次后得到的(好的,有一个版本0,其中我没有使用data Rectangle,只是一个元组(x, y)):
data Rectangle = Rectangle { width :: Integer,
height :: Integer
} deriving (Show)
subRectangles :: Rectangle -> Integer -> [ Rectangle ]
subRectangles r n = [ Rectangle x y | x <- [1..w ], y <- [1..h], x * y == (w * h) `div` n ]
where w = width r
h = height r
bestSubRectangle :: [ Rectangle ] -> Rectangle
bestSubRectangle [ r ] = r
bestSubRectangle (r:rs)
| perimeter r < perimeter bestOfRest = r
| otherwise = bestOfRest
where bestOfRest = bestSubRectangle rs
perimeter :: Rectangle -> Integer
perimeter r = (width r) + (height r)
Run Code Online (Sandbox Code Playgroud)
data Rectangle = Rectangle { width :: Integer,
height :: Integer
} deriving (Show)
subRectangles :: Rectangle -> Integer -> [ Rectangle ]
subRectangles r n = [ Rectangle x y | x <- [1..w ], y <- [1..h], x * y == (w * h) `div` n ]
where w = width r
h = height r
bestSubRectangle :: [ Rectangle ] -> Rectangle
bestSubRectangle xs = foldr smaller (last xs) xs
smaller :: Rectangle -> Rectangle -> Rectangle
smaller r1 r2
| perimeter r1 < perimeter r2 = r1
| otherwise = r2
perimeter :: Rectangle -> Integer
perimeter r = (width r) + (height r)
Run Code Online (Sandbox Code Playgroud)
import Data.List
data Rectangle = Rectangle { width :: Integer,
height :: Integer
} deriving (Show, Eq)
instance Ord Rectangle where
(Rectangle w1 h1) `compare` (Rectangle w2 h2) = (w1 + h1) `compare` (w2 + h2)
subRectangles :: Rectangle -> Integer -> [ Rectangle ]
subRectangles r n = [ Rectangle x y | x <- [1..w ], y <- [1..h], x * y == (w * h) `div` n ]
where w = width r
h = height r
bestSubRectangle :: [ Rectangle ] -> Rectangle
bestSubRectangle = head . sort
Run Code Online (Sandbox Code Playgroud)
哪种方法比较惯用?
哪种方法在性能方面更好?bestSubRectangle实现3中的sort实际上取决于O(n lg n),而在实现1中,2 bestSubRectangle只需要扫描返回的数组subRectangles,从而使其成为O(n).但是我不确定Haskell laziness是否/如何工作bestSubRectangle = head . sort:将sort只生成排序数组的第一个元素,因为head只需要第一个元素(head (x:_) = x)?
在实现3中,在创建Rectangle实例时,Ord我还应该定义Ord该类的其他方法吗?这是制作Rectangle实例的正确方法Ord吗?
任何进一步的改进建议/建议都非常受欢迎.
要回答有关 Haskell 的问题(而不是有关您选择的算法的问题):
sort可能比需要的更昂贵。当询问是否由于懒惰而head . sort仅计算 的结果的第一个元素时,您也提出了正确的问题sort。会的,但取决于如何sort实现,这很可能依赖于对整个列表进行排序,然后才能返回第一个元素。你应该假设确实如此。Ord中看出这compare已经足够了。关键词是最小完整定义:要么compare或<=。许多类型类都有相似的使用模式。您应该随意编写一个最小的实现。关于您的代码的一些其他观察(同样,不是算法):
perimeter r = width r + height rfoldr1使用bestSubRectangle xs = foldr1 smaller xsOrd您的for实例Rectangle与 的派生实例不一致Eq。也就是说,有些值将compare作为EQ但会返回Falsefor ==。在这种情况下,我不会尝试弯曲Rectangle的通用实例Ord来进行周长比较,而是使用smaller您在第二个实现中编写的谓词。