Project Euler 8 - I don't understand it
我在哈斯克尔寻找第八个欧拉问题的解决方案,但我不太明白。
1 2 3 4 5 6 7 8 9 |
这里是解决方案的链接,您可以在这里找到任务。
有人能一个接一个地给我解释一下解决办法吗?
读取数据
1 2 3 4 | 7316 9698 8586 1254 |
号
奔跑
结果
1 2 3 4 | "7316 9698 8586 1254" |
。
此字符串中有多余的换行符。为了删除它们,作者将字符串拆分为
结果不再包含任何
'
1 | ["7316","9698","8586","1254"] |
。
为了将其转换成一个字符串,这些字符串一起使用
。
连接的字符串是字符列表,而不是数字列表
1 | "7316969885861254" |
每个字符由
1 2 3 4 |
。
结果列表中充满了
1 | [7,3,1,6,9,6,9,8,8,5,8,6,1,2,5,4] |
子序列
作者的下一个目标是找到这个整数列表中所有的13位数字。
1 2 3 4 5 |
。
这将为我们的16位示例生成17个列表。(我添加了格式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [ [7,3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [1,6,9,6,9,8,8,5,8,6,1,2,5,4], [6,9,6,9,8,8,5,8,6,1,2,5,4], [9,6,9,8,8,5,8,6,1,2,5,4], [6,9,8,8,5,8,6,1,2,5,4], [9,8,8,5,8,6,1,2,5,4], [8,8,5,8,6,1,2,5,4], [8,5,8,6,1,2,5,4], [5,8,6,1,2,5,4], [8,6,1,2,5,4], [6,1,2,5,4], [1,2,5,4], [2,5,4], [5,4], [4], [] ] |
号
作者将采取一个技巧,我们重新排列这些列表,以读取13位长的子列表。如果我们看到这些列表是左对齐的而不是右对齐的,我们可以看到子序列沿着每列向下运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | [ [7,3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [1,6,9,6,9,8,8,5,8,6,1,2,5,4], [6,9,6,9,8,8,5,8,6,1,2,5,4], [9,6,9,8,8,5,8,6,1,2,5,4], [6,9,8,8,5,8,6,1,2,5,4], [9,8,8,5,8,6,1,2,5,4], [8,8,5,8,6,1,2,5,4], [8,5,8,6,1,2,5,4], [5,8,6,1,2,5,4], [8,6,1,2,5,4], [6,1,2,5,4], [1,2,5,4], [2,5,4], [5,4], [4], [] ] |
号
我们只希望这些列的长度为13位,所以我们只希望
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [ [7,3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [3,1,6,9,6,9,8,8,5,8,6,1,2,5,4], [1,6,9,6,9,8,8,5,8,6,1,2,5,4], [6,9,6,9,8,8,5,8,6,1,2,5,4], [9,6,9,8,8,5,8,6,1,2,5,4], [6,9,8,8,5,8,6,1,2,5,4], [9,8,8,5,8,6,1,2,5,4], [8,8,5,8,6,1,2,5,4], [8,5,8,6,1,2,5,4], [5,8,6,1,2,5,4], [8,6,1,2,5,4], [6,1,2,5,4], [1,2,5,4] ] |
号
1 2 3 4 5 6 |
号
我们现在像往常一样在列表中读取子序列
1 2 3 4 5 6 | [ [7,3,1,6,9,6,9,8,8,5,8,6,1], [3,1,6,9,6,9,8,8,5,8,6,1,2], [1,6,9,6,9,8,8,5,8,6,1,2,5], [6,9,6,9,8,8,5,8,6,1,2,5,4] ] |
号问题
我们通过
1 2 3 4 5 6 7 |
号
这会将列表减少到一个单独的数字
1 | [940584960,268738560,447897600,1791590400] |
号
从中我们必须找到
1 2 3 4 5 6 7 |
号
答案是
1791590400年
如果您不熟悉所使用的函数,首先应该检查每个函数的类型。因为这是函数组合,所以从内到外应用(即,读取时从右到左、从下到上进行操作)。我们可以一行一行地穿过这条线。
从最后一行开始,我们首先检查类型。
1
2
3
4
5
6由于
type String = [Char] (所以[String] 相当于[[Char]] ,所以此行将多个行号转换为一个数字字符数组。更准确地说,它首先基于完整的字符串创建一个字符串数组。也就是说,每新行一个字符串。然后,它将所有这些行(现在只包含数字字符)合并为一个字符数组(或单个String )。下一行将这个新字符串作为输入。同样,让我们观察一下类型:
1
2
3
4
5
6
7
8
9
10 :t digitToInt
digitToInt :: Char -> Int -- Convert a digit char to an int
:t fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b -- Convert integral to num type
:t map
map :: (a -> b) -> [a] -> [b] -- Perform a function on each element of the array
:t tails
tails :: [a] -> [[a]] -- Returns all final segments of input (see: http://hackage.haskell.org/package/base-4.8.0.0/docs/Data-List.html#v:tails)
:t take
take :: Int -> [a] -> [a] -- Return the first n values of the list号
如果我们将这些操作应用到我们的字符串电流输入中,首先要对字符串中的每个字符映射
(fromIntegral . digitToInt) 的复合函数。这样做的目的是将我们的数字串转换为数字类型列表。如下面注释所指出的那样编辑,本例中的fromIntegral 是为了防止32位整数类型溢出。既然我们已经将字符串转换为实际的数字类型,那么我们首先要对这个结果运行tails。因为(根据problem语句)所有的值都必须是相邻的,并且我们知道所有的整数都是非负的(由于是一个更大的数的位置),所以我们只取前13个元素,因为我们要确保乘法是13个连续元素的分组。不考虑下一行,很难理解这是如何工作的。所以,让我们做一个快速的实验。在将字符串转换成数字类型之后,我们现在有了一个大列表。这实际上有点难想象我们这里到底有什么。为了理解,清单的内容并不重要。重要的是它的大小。让我们来看一个人工例子:
你可以看到我们这里有一个13个元素的大列表。每个元素都是一个大小为1000(即完整数据集)的列表,按降序排列到988。所以这就是我们目前对下一行的输入,可以说,这是最难理解的——也是最重要的——行。为什么理解这一点很重要应该在我们走过下一行时变得清晰。
1
2
3
4
5
6
7
8 :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b -- Combine values into a single value
:t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] -- Generalization of zip
:t (:)
(:) :: a -> [a] -> [a] -- Cons operator. Add element to list
:t repeat
repeat :: a -> [a] -- Infinite list containing specified value。
还记得我说过我们以前有13个元素的列表吗(不同大小的列表)?这现在很重要。该行将遍历该列表并将
(zipWith (:)) 应用于该列表。(repeat []) 是这样的:每次在子序列上调用zipWith 时,它都以一个空列表作为基。这允许我们构建一个包含长度为13的相邻子序列的列表列表。最后,我们到达最后一行,这很容易。也就是说,我们仍然应该注意我们的类型
1
2
3
4我们要做的第一件事是在每个子序列上映射
product 函数。完成后,我们会得到一个数字类型列表(嘿,我们终于没有列表了!)。这些值是每个子序列的乘积。最后,我们应用maximum 函数,它只返回列表中最大的元素。编辑:我后来发现了
foldr 表达式的作用。(见我回答后的评论)。我认为这可以用不同的方式表达——您可以在列表的末尾添加一个守卫。
这个解决方案的详细版本是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import Data.List
import Data.Char
euler_8 = do
let len = 13
let str1 ="123456789
123456789"
-- Join lines
let str2 = concat (lines str1)
-- Transform the list of characters into a list of numbers
let lst1 = map (fromIntegral . digitToInt) str2
-- EDIT: Add a guard at the end of list
let lst2 = lst1 ++ [-1]
-- Get all tails of the list of digits
let lst3 = tails lst2
-- Get first 13 digits from each tail
let lst4 = map (take len) lst3
-- Get a list of products
let prod = map product lst4
-- Find max product
let m = maximum prod
print m