如果有人愿意的话,可以做一个相当字面的翻译,但是如此多的索引工作通常是无法理解真正的迭代模式的标志。在这里,我看到的最直接的翻译是维护 A、B、C 等,并跟踪列表的其余部分。使用 monad 理解tails
将列表分开似乎很简单。我想象这样的事情:
import Data.Bits
import Data.List (tails)
import Data.Maybe (listToMaybe)
import Control.Monad (guard)
cookedWords :: [Int]
cookedWords = [0..]
match :: [Int] -> Maybe (Int, Int, Int, Int, Int)
match words =
listToMaybe $ do
a : as <- tails cookedWords
b : bs <- tails as
guard $ a .&. b == 0
let ab = a .|. b
c : cs <- tails bs
guard $ ab .&. c == 0
let abc = ab .|. c
d : ds <- tails cs
guard $ abc .&. d == 0
let abcd = abc .|. d
e : es <- tails ds
guard $ abcd .&. e == 0
pure (a, b, c, d, e)
当然,因为我已经将单词定义为[0..]
,它只找到 2 的前 5 个幂(好吧,其中 4 个和一个零):
*Main> match cookedWords
Just (0,1,2,4,8)
但最好将其推广到一个函数,该函数的输入是要搜索的匹配单词列表以及所需的组大小,其输出是此类匹配的列表。
match :: Int -> [Int] -> [[Int]]
match = go 0
where go mask 0 _ = [[]]
go mask n words = do
x : xs <- tails words
guard $ mask .&. x == 0
let mask' = mask .|. x
(x:) <$> go mask' (n - 1) xs
这样,我们不必担心五个中间状态或结果的特定命名变量,我们可以只处理任意大小的列表。作为奖励,如果我们愿意,我们可以获得多个结果:
*Main> take 5 $ match 5 cookedWords
[[0,1,2,4,8],[0,1,2,4,16],[0,1,2,4,24],[0,1,2,4,32],[0,1,2,4,40]]
找到合理的抽象可以让我们编写出比 Java 版本更简单、更明显正确的代码。