2

下面的函数显然在两个列表推导之间存在重复,但是如何在不增加代码总长度的情况下消除它呢?我有一种鬼鬼祟祟的感觉,这里潜伏着一个很好的抽象,但我就是看不到它......

letterAt :: [Word] -> Int -> Int -> Maybe Char 
letterAt wrds x y = listToMaybe $ 
  [wtext !! (x - wx) | 
    Word wx wy wtext Across <- wrds, 
    wy == y && x >= wx && x < wx + length wtext] ++ 
  [wtext !! (y - wy) | 
    Word wx wy wtext Down <- wrds, 
    wx == x && y >= wy && y < wy + length wtext] 

一些背景:

该功能取自填字游戏。填字游戏表示为 [Word],其中

data Word = Word { startX :: Int, 
                   startY :: Int, 
                   text :: String, 
                   sense :: Sense } 

data Sense = Across | Down

sense == Across 从位置 (startX, startY) 开始并沿 x 正方向继续,而 sense == Down 则沿 y 正方向继续。该函数的目的是在 Just 中获取位置 (x, y) 处的字符,如果那里没有字符,则为 Nothing。

随意指出我在这里对 Haskell 犯下的任何其他罪行,我刚刚开始使用该语言,但仍在努力掌握它!

4

2 回答 2

4

以下是有关您的代码的一些要点:

  • filter当要根据谓词选择列表的某些元素时,最好使用它。
  • 由于您只想要满足某些谓词的第一个元素,因此您可以使用Data.List.find

您的条件看起来是对称的,因此您可以将transform函数定义为

transform f x y (Word wx wy wtext Across) = f wtext wy wx y x
transform f x y (Word wx wy wtext Down) = f wtext wx wy x y

现在编写代码只需要编写一次条件

letterAt :: [Word] -> Int -> Int -> Maybe Char
letterAt wrds x y = (transform charValue x y) <$> find (transform condition x y)  wrds
    where
        condition wtext wx wy x y = wx == x && y >= wy && y < wy + length wtext
        charValue wtext wx wy x y = wtext !! (y-wy)
于 2012-09-15T14:38:31.530 回答
1

Satvik,感谢您的回答,这让我沿着正确的思路思考。我按照您的建议分离了条件和转换函数,然后意识到转换数据而不是函数会更简单,并将所有内容放回列表理解中以提高可读性:

letterAt :: [Word] -> Int -> Int -> Maybe Char 
letterAt wrds x y = listToMaybe 
  [wtext !! x' | Word wx wy wtext sens <- wrds,   
                 let (x', y') = case sens of 
                       Across -> (x - wx, y - wy) 
                       Down   -> (y - wy, x - wx), 
                 y' == 0, x' >= 0, x' < length wtext ] 

您指出在这种情况下最好使用 Data.find .. 这是为了可读性还是效率?我猜是因为列表在 Haskell 中是惰性的,所以上面的代码会在列表理解中的第一项被评估后停止,对吗?

于 2012-09-16T04:41:43.547 回答