繁体   English   中英

项目欧拉8 - 我不明白

[英]Project Euler 8 - I don't understand it

我在Haskell中寻找第8个Euler问题的解决方案,但我不太明白。

import Data.List
import Data.Char

euler_8 = do
   str <- readFile "number.txt"
   print . maximum . map product
         . foldr (zipWith (:)) (repeat [])
         . take 13 . tails . map (fromIntegral . digitToInt)
         . concat . lines $ str

是解决方案的链接,您可以在此处找到该任务。

任何人都可以逐一解释我的解决方案吗?

读数据

readFile读取文件"number.txt" 如果我们在名为number.txt的文件中放入一个小的16位数字

7316
9698
8586
1254

乳宁

euler_8 = do
   str <- readFile "number.txt"
   print $ str

结果是

"7316\n9698\n8586\n1254"

该字符串中包含额外的换行符。 要删除它们,作者将字符串拆分为lines

euler_8 = do
   str <- readFile "number.txt"
   print . lines $ str

结果不再包含任何'\\n'字符,而是一个字符串列表。

["7316","9698","8586","1254"]

把它变成一个字符串,字符串是concat enated在一起。

euler_8 = do
   str <- readFile "number.txt"
   print . concat . lines $ str

连接字符串是字符列表而不是数字列表

"7316969885861254"

每个字符都通过digitToInt转换为Int ,然后通过fromInteger转换为Integer 在使用全尺寸Integer 32位硬件上很重要,因为13位数的乘积可能大于2^31-1 此转换将map到列表中的每个项目。

euler_8 = do
   str <- readFile "number.txt"
   print . map (fromIntegral . digitToInt)
         . concat . lines $ str

结果列表中充满了Integer

[7,3,1,6,9,6,9,8,8,5,8,6,1,2,5,4]

作者的下一个目标是在这个整数列表中找到所有13位数的运行。 tails返回列表的所有子列表,从任何位置开始并运行到列表的末尾。

euler_8 = do
   str <- readFile "number.txt"
   print . tails
         . map (fromIntegral . digitToInt)
         . concat . lines $ str

这导致我们的16位数示例列出了17个列表。 (我添加了格式)

[
  [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位长的子列表。 如果我们查看左对齐而不是右对齐的这些列表,我们可以看到每列中运行的子序列。

[
  [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位,所以我们只想take13行。

[
  [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]
]

foldr (zipWith (:)) (repeat [])转置列表列表(解释它可能属于另一个问题 )。 它会丢弃比最短行长的行的部分。

euler_8 = do
   str <- readFile "number.txt"
   print . foldr (zipWith (:)) (repeat [])
         . take 13 . tails
         . map (fromIntegral . digitToInt)
         . concat . lines $ str

我们现在像往常一样阅读列表中的子序列

[
  [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]
]

问题

我们发现product分别由子序列的mapproduct上给他们。

euler_8 = do
   str <- readFile "number.txt"
   print . map product
         . foldr (zipWith (:)) (repeat [])
         . take 13 . tails
         . map (fromIntegral . digitToInt)
         . concat . lines $ str

这会将列表减少为单个数字

[940584960,268738560,447897600,1791590400]

从中我们必须找到maximum

euler_8 = do
   str <- readFile "number.txt"
   print . maximum . map product
         . foldr (zipWith (:)) (repeat [])
         . take 13 . tails
         . map (fromIntegral . digitToInt)
         . concat . lines $ str

答案是

1791590400

如果您不熟悉所使用的函数,首先应该检查每个函数的类型。 由于这是功能组合,因此您可以从内到外应用(即操作在读取时从右到左,从下到上)。 我们可以逐行浏览。

从最后一行开始,我们将首先检查类型。

:t str
str :: String -- This is your input
:t lines
lines :: String -> [String] -- Turn a string into an array of strings splitting on new line
:t concat
concat :: [[a]] -> [a] -- Merge a list of lists into a single list (hint: type String = [Char])

由于type String = [Char] (因此[String]等同于[[Char]] ),此行将多行号转换为单个数字字符数组。 更准确地说,它首先根据完整字符串创建一个字符串数组。 也就是说,每个新行一个字符串。 然后它将所有这些行(现在只包含数字字符)合并为一个字符数组(或单个String )。

下一行将此新String作为输入。 再次,让我们观察类型:

: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。 由于(通过问题陈述)所有值必须是相邻的,并且我们知道所有整数都是非负的(由于是更大数字的位置),我们只采用前13个元素,因为我们要确保我们的乘法是13个连续元素的分组。 如果不考虑下一行,这很难理解。

那么,让我们做一个快速的实验。 将我们的字符串转换为数字类型后,我们现在有一个很大的列表列表。 这实际上很难想到我们实际拥有的东西。 为了便于理解,列表的内容不是很重要。 重要的是它的大小。 那么让我们来看一个人为的例子:

(map length . take 13 . tails) [1..1000]
[1000,999,998,997,996,995,994,993,992,991,990,989,988]

你可以看到我们这里有13个元素的大清单。 每个元素都是大小为1000的列表(即完整数据集),降序为988。 所以这就是我们目前用于输入下一行的东西,可以说,这是最难理解但最重要的一行。 当我们走完下一行时,为什么理解这一点很重要。

: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上调用zipWith ,它都以空列表为基础开始。 这允许我们构建包含长度为13的相邻子序列的列表。

最后,我们到达最后一行很容易。 也就是说,我们仍然应该注意我们的类型

:t product
product :: Num a => [a] -> a -- Multiply all elements of a list together and return result
:t maximum
maximum :: Ord a => [a] -> a -- Return maximum element in the list

我们要做的第一件事是在每个子序列上映射product函数。 完成后我们最终会得到一个数字类型列表(嘿,我们终于没有列表了!)。 这些值是每个子序列的乘积。 最后,我们应用maximum函数,它只返回列表中最大的元素。

编辑:我后来发现了foldr表达式的用途。 (见下面的评论我的回答)。

我认为这可以用不同的方式表达 - 你可以简单地在列表的末尾添加一个警卫。

我的详细版本的解决方案是:

import Data.List
import Data.Char

euler_8 = do
    let len  = 13
    let str1 = "123456789\n123456789"
    -- 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM