我正在学习Haskell。
我几天前回答的一个question给了我Haskell这个练习的灵感,这个练习让我有机会尝试我迄今学到的一些东西,并给我留下了一些问题:)
给定一个宽度为w
且高度为h
的矩形 A ,找到最适合<{1}}次的矩形 B < strong> A ,其中 best 表示周长最小。
我开始的基本思路是生成面积等于n
的 A 的子矩形集,然后选择周长最小的子矩形。< / p>
以下是我提出的这个想法的三个实现;他们是按时间顺序排列的:第二次完成第二次后,我得到了第三次的灵感,这是我完成第一次后得到的(好吧,有一个版本0,其中我没有使用div (w * h) n
但只是元组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)
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)
哪种方法比较惯用?
哪种方法在性能方面更好?实现3中的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
取决于bestSubRectangle
,最多为O(n lg n),而在实现1中,2 sort
仅需要扫描bestSubRectangle
返回的数组从而使其成为O(n)。但是我不确定Haskell laziness是否/如何在subRectangles
上起作用:will bestSubRectangle = head . sort
只产生排序数组的第一个元素,因为sort
只需要第一个元素({{ 1}}
在实现3中,当head
head (x:_) = x)
的实例时,我还应该定义Rectangle
类的其他方法吗?这是使Ord
成为Ord
的实例的正确方法吗?
非常欢迎任何进一步改进的建议/建议。
答案 0 :(得分:4)
回答你关于Haskell的问题(而不是你选择的算法):
sort
可能比需要的更贵。在询问由于懒惰而head . sort
是否仅计算sort
结果的第一个元素时,您也会问正确的问题。它会,但取决于sort
的实现方式,可能很好地依赖于在返回第一个元素之前对整个列表进行排序。你应该假设它确实如此。Ord
了解compare
是否足够。关键短语是最小完整定义:compare
或<=
。许多类型类具有相似的使用模式。您可以随意编写最小的实现。关于您的代码的其他一些观察(再次,不是算法):
perimeter r = width r + height r
foldr1
bestSubRectangle xs = foldr1 smaller xs
Ord
的{{1}}实例与Rectangle
的派生实例不一致。也就是说,有Eq
为compare
但会为EQ
返回False
的值。在这种情况下,为了进行边界比较,我不会尝试弯曲==
的{{1}}通用实例,而是使用您在第二个实现中编写的Rectangle
谓词答案 1 :(得分:1)
好像你计算得太多了,感应地通过n的可能性(我们希望填充给定矩形的矩形数)我们应该得到:
- 基本上这是一个分解n的问题,将长度和宽度除以每个因子然后选择配置,其中分开的长度&amp;宽度(即所得填充物矩形的长度和宽度)是最相似的(大多数为方形)。另请注意,没有必要针对所有方面尝试所有因素。最方形的矩形对长边的影响较大。
因此步骤变为:
1: Factor n
2: for each factor pair of n:
(l/largest factor) - (s/smaller factor) = d
if d < t:
t = d
store current best candidate rectangle
3: Return best fit remaining after all factors have been tried