将给定矩形拆分为n个子矩形

时间:2011-05-05 16:11:51

标签: list haskell

初步评论

我正在学习Haskell。

我几天前回答的一个question给了我Haskell这个练习的灵感,这个练习让我有机会尝试我迄今学到的一些东西,并给我留下了一些问题:)

问题陈述

给定一个宽度为w且高度为h的矩形 A ,找到最适合<{1}}次的矩形 B < strong> A ,其中 best 表示周长最小。

我的尝试

我开始的基本思路是生成面积等于n A 的子矩形集,然后选择周长最小的子矩形。< / p>

以下是我提出的这个想法的三个实现;他们是按时间顺序排列的:第二次完成第二次后,我得到了第三次的灵感,这是我完成第一次后得到的(好吧,有一个版本0,其中我没有使用div (w * h) n但只是元组data Rectangle):

实施1

(x, y)

实施2

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)

实施3

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)

问题

  1. 哪种方法比较惯用?

  2. 哪种方法在性能方面更好?实现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. 在实现3中,当head head (x:_) = x)的实例时,我还应该定义Rectangle类的其他方法吗?这是使Ord成为Ord的实例的正确方法吗?

  4. 非常欢迎任何进一步改进的建议/建议。

2 个答案:

答案 0 :(得分:4)

回答你关于Haskell的问题(而不是你选择的算法):

  1. 我说你的第二个实现是最惯用的。
  2. 你是对的,问题只需要线性扫描,因此sort可能比需要的更贵。在询问由于懒惰而head . sort是否仅计算sort结果的第一个元素时,您也会问正确的问题。它会,但取决于sort的实现方式,可能很好地依赖于在返回第一个元素之前对整个列表进行排序。你应该假设它确实如此。
  3. 您可以从documentation for Ord了解compare是否足够。关键短语是最小完整定义:compare<=许多类型类具有相似的使用模式。您可以随意编写最小的实现。
  4. 关于您的代码的其他一些观察(再次,不是算法):

    • 函数调用绑定比运算符更严格,就像在大多数语言中一样 - 只是在Haskell中,参数周围没有括号。因此你会写perimeter r = width r + height r
    • 如果您要折叠必须至少包含一个元素的列表,则可以使用foldr1
    • 中的bestSubRectangle xs = foldr1 smaller xs
    • Ord的{​​{1}}实例与Rectangle的派生实例不一致。也就是说,有Eqcompare但会为EQ返回False的值。在这种情况下,为了进行边界比较,我不会尝试弯曲==的{​​{1}}通用实例,而是使用您在第二个实现中编写的Rectangle谓词

答案 1 :(得分:1)

好像你计算得太多了,感应地通过n的可能性(我们希望填充给定矩形的矩形数)我们应该得到:

  • n = 1 =&gt;总是返回给定的矩形
  • n = 2 =&gt;总是返回通过在最长边平分给定矩形给出的2个矩形,即给出2个最方形的矩形
  • n = 3 =&gt;与2使用3相同,沿最长边均分。
  • n = 4更复杂,基本上出现的问题是连续4次放置相同的矩形更好或者最好将长度和宽度分成2x2矩形组。对于更大数量的n,也是这些因子配置的问题&gt; 1

- 基本上这是一个分解n的问题,将长度和宽度除以每个因子然后选择配置,其中分开的长度&amp;宽度(即所得填充物矩形的长度和宽度)是最相似的(大多数为方形)。另请注意,没有必要针对所有方面尝试所有因素。最方形的矩形对长边的影响较大。

  • n =填充矩形的数量
  • l =容器矩形的最长宽度或高度
  • s =容器矩形的最短宽度
  • h1 =候选矩形1的高度
  • w1 =候选矩形1的宽度
  • d =候选维度的差异
  • t =候选高度和宽度的最小差异(初始化t到l)
  • b =最合适

因此步骤变为:

    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