Haskell 在水平线、垂直线和对角线上乘以数字

Haskell 在水平线、垂直线和对角线上乘以数字,haskell,functional-programming,Haskell,Functional Programming,我目前正在研究一个项目Euler问题(www.projecteuler.net)以获取乐趣,但遇到了一个绊脚石。其中一个问题提供了一个20x20的数字网格,并要求直线上的4个数字的最大乘积。这条线可以是水平线、垂直线或对角线 使用过程语言解决这个问题没有任何问题,但我首先解决这些问题的部分动机是获得更多经验和学习更多Haskell。 现在我正在阅读网格,并将其转换为整数列表,例如--[[Int]]。这使得水平乘法变得微不足道,而通过转置该网格,垂直乘法也变得微不足道 对角线就是给我带来麻烦的地方

我目前正在研究一个项目Euler问题(www.projecteuler.net)以获取乐趣,但遇到了一个绊脚石。其中一个问题提供了一个20x20的数字网格,并要求直线上的4个数字的最大乘积。这条线可以是水平线、垂直线或对角线

使用过程语言解决这个问题没有任何问题,但我首先解决这些问题的部分动机是获得更多经验和学习更多Haskell。
现在我正在阅读网格,并将其转换为整数列表,例如--[[Int]]。这使得水平乘法变得微不足道,而通过转置该网格,垂直乘法也变得微不足道


对角线就是给我带来麻烦的地方。我已经想到了一些方法,可以使用显式数组切片或索引来获得解决方案,但它似乎过于复杂和骇客化。我相信这里可能有一个优雅、实用的解决方案,我很想听听其他人的想法。

您可以使用
按索引检索列表中元素的函数。对于这个问题,列表是错误的数据结构,因为它们不提供固定时间内的随机索引——它们偏向于线性遍历。所以你的对角线在列表中总是更烦人/更慢


使用数组怎么样?或者。

我不同意可贵的唐·斯图尔特。考虑到问题的组合性质以及问题规模仅为20x20的事实,列表列表将足够快。你最不想做的事情就是用数组索引。相反,我建议你扩展理查德·伯德(Richard Bird)在其名副其实的著作中所开发的技术。更具体地说,我建议如下:

  • 编写一个给定序列的函数,返回长度为4的所有连续子序列

  • 编写一个给定网格的函数,返回所有行

  • 编写一个给定网格的函数,返回所有列

  • 编写一个给定网格的函数,返回所有对角线

有了这些功能,您的解决方案将变得简单。但正如你提到的,对角线并不那么明显。对角线是什么? 让我们看一个例子:

X . . . . .
. X . . . .
. . X . . . 
. . . X . .
. . . . X .
. . . . . X
假设您使用
drop
函数,从第0行中删除0个元素,从第1行中删除1个元素,依此类推。以下是你最后得到的:

X . . . . .
X . . . .
X . . . 
X . .
X .
X
对角线的元素现在形成了剩下的三角形的第一列。更妙的是,你剩下的每一列都是原始矩阵的对角线。再加上一些对称变换,你就可以很容易地列举出任意大小的方阵的所有对角线。用你的“长度为4的连续子序列”函数来打击每一个,鲍勃就是你的叔叔


对于那些可能陷入困境的人,请提供更多细节:

这个问题的关键是构图。对角线分为四组。我的例子给出了一组。要获得其他三个,请对镜像、转置和转置的镜像应用相同的函数

  • 转置是一个单行函数,您无论如何都需要它来干净地恢复列

  • 镜像比转置更简单想想你可以从序曲中使用什么函数


对称方法将给出每个主对角线两次;幸运的是,对于这个问题来说,重复对角线是可以的。

好吧,对于这个特殊的问题,单个线性列表或数组实际上是最简单的结构!关键是将这些跑步视为以给定步幅跳过列表。如果网格的大小为w×h,则

  • 水平跑的步幅为1
  • 垂直跑步的步幅为w
  • 一次对角跑的步幅为w-1
  • 一次对角跑的步幅为w+1
现在,对于四种运行中的每一种,您只需要计算可能的起点。大概是这样的:

allRuns :: Int -> Int -> Int -> [a] -> [[a]]
allRuns n w h es = horiz ++ vert ++ acute ++ grave
    where horiz = runs [0..w-n]   [0..h-1] 1
          vert  = runs [0..w-1]   [0..h-n] w
          acute = runs [n-1..w-1] [0..h-n] (w-1)
          grave = runs [0..w-n]   [0..h-n] (w+1)

          runs xs ys s = [run (x+y*w) s | x <- xs, y <- ys]
          run i s = map (es!!) [i,i+s..i+(n-1)*s]
allRuns::Int->Int->Int->Int->[a]->[[a]]
allRuns n w h es=水平++垂直++锐角++坟墓
其中horiz=运行[0..w-n][0..h-1]1
垂直=运行[0..w-1][0..h-n]w
急性=运行[n-1..w-1][0..h-n](w-1)
grave=运行[0..w-n][0..h-n](w+1)

运行xs ys s=[run(x+y*w)s | x所以你有一个NxN网格,你想提取长度为M的所有水平、垂直和对角线,然后找到最大乘积。让我们在示例4x4网格上演示一些Haskell技术,其中线长为2:

[[ 1, 2, 3, 4],
 [ 5, 6, 7, 8],
 [ 9,10,11,12],
 [13,14,15,16]]
横向和纵向都很简单,您只需要一个函数,从列表中提取长度为M的块:

chunks 2 [1,2,3,4] == [[1,2],[2,3],[3,4]]
这类函数的类型是
[a]->[[a]]
。这是一个与列表相关的函数,因此在重新设计控制盘之前,让我们看看是否有类似的东西。啊哈,
tails
是类似的,它返回列表,从列表的开头删除越来越多的元素:

tails [1,2,3,4] == [[1,2,3,4],[2,3,4],[3,4],[4],[]]
如果我们可以缩短子列表使其长度为2,那该多好。但是我们可以通过使用函数,将函数应用于列表的每个元素并返回一个新列表:

map (take n) (tails xs) -- [[1,2],[2,3],[3,4],[4],[]]
我不会担心更小的产品线,因为最初的任务是找到最大的产品,以及
[15,N]
[15]
的产品,N≥ 1.但是如果你想去掉它们,那么一个长度为N的列表似乎包含N-M+1个长度为M的区块,因此你可以对结果列表应用
take(4-2+1)
。或者你可以简单地选择列表:

chunks n xs = filter ((==n) . length) $ map (take n) (tails xs)
-- [[1,2],[2,3],[3,4]]
好的,我们可以从列表中提取块列表,但我们有一个二维网格,而不是平面列表!
map
再次拯救了我们:

map (chunks 2) grid -- [[[1,2],[2,3],[3,4]],[[5,6],[6,7],[7,8]],...]
但问题是,生成的代码将块放在单独的列表中,这使
concatMap (chunks 2) grid -- [[1,2],[2,3],[3,4],[5,6],[6,7],[7,8],...]
concatMap (chunks 2) (transpose grid) -- [[1,5],[5,9],[9,13],[2,6],[6,10],...]
zipWith drop [0..] grid -- [[1,2,3,4],[6,7,8],[11,12],[16]]
map head $ zipWith drop [0..] grid -- [1,6,11,16]
transpose $ zipWith drop [0,1] grid -- [[1,6],[2,7],[3,8],[4]]
concatMap (transpose . zipWith drop [0,1]) (tails g)
-- [[1,6],[2,7],[3,8],[5,10],[6,11],...]
concatMap (transpose . zipWith drop [0,1]) (tails $ reverse g)
-- [[13,10],[14,11],[15,12],[9,6],[10,7],...]
grid = [[1..4],[5..8],[9..12],[13..16]]
chunks n xs = map (take n) (tails xs)
horizontal = concatMap (chunks 2) grid
vertical = concatMap (chunks 2) (transpose grid)
grave = concatMap (transpose . zipWith drop [0,1]) (tails grid)
acute = concatMap (transpose . zipWith drop [0,1]) (tails $ reverse grid)
maxProduct = maximum $ map product $ horizontal ++ vertical ++ grave ++ acute
-- answer: 240