简体   繁体   English

项目欧拉8 - 我不明白

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

I looked up for a solution in Haskell for the 8th Euler problem, but I don't quite 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

Here is the link for the solution and here you can find the task. 是解决方案的链接,您可以在此处找到该任务。

Could anyone explain me the solution one by one? 任何人都可以逐一解释我的解决方案吗?

Reading the data 读数据

readFile reads the file "number.txt" . readFile读取文件"number.txt" If we put a small 16 digit number in a file called number.txt 如果我们在名为number.txt的文件中放入一个小的16位数字

7316
9698
8586
1254

Runing 乳宁

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

Results in 结果是

"7316\n9698\n8586\n1254"

This string has extra newline characters in it. 该字符串中包含额外的换行符。 To remove them, the author splits the string into lines . 要删除它们,作者将字符串拆分为lines

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

The result no longer has any '\\n' characters, but is a list of strings. 结果不再包含任何'\\n'字符,而是一个字符串列表。

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

To turn this into a single string, the strings are concat enated together. 把它变成一个字符串,字符串是concat enated在一起。

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

The concatenated string is a list of characters instead of a list of numbers 连接字符串是字符列表而不是数字列表

"7316969885861254"

Each character is converted into an Int by digitToInt then converted into an Integer by fromInteger . 每个字符都通过digitToInt转换为Int ,然后通过fromInteger转换为Integer On 32 bit hardware using a full-sized Integer is important since the product of 13 digits could be larger than 2^31-1 . 在使用全尺寸Integer 32位硬件上很重要,因为13位数的乘积可能大于2^31-1 This conversion is map ped onto each item in the list. 此转换将map到列表中的每个项目。

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

The resulting list is full of Integer s. 结果列表中充满了Integer

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

Subsequences

The author's next goal is to find all of the 13 digit runs in this list of integers. 作者的下一个目标是在这个整数列表中找到所有13位数的运行。 tails returns all of the sublists of a list, starting at any position and running till the end of the list. tails返回列表的所有子列表,从任何位置开始并运行到列表的末尾。

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

This results in 17 lists for our 16 digit example. 这导致我们的16位数示例列出了17个列表。 (I've added formatting) (我添加了格式)

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

The author is going to pull a trick where we rearrange these lists to read off 13 digit long sub lists. 作者将在我们重新安排这些列表的过程中提取一个技巧来读取13位长的子列表。 If we look at these lists left-aligned instead of right-aligned we can see the sub sequences running down each column. 如果我们查看左对齐而不是右对齐的这些列表,我们可以看到每列中运行的子序列。

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

We only want these columns to be 13 digits long, so we only want to take the first 13 rows. 我们只希望这些列长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 []) transposes a list of lists (explaining it belongs to perhaps another question ). foldr (zipWith (:)) (repeat [])转置列表列表(解释它可能属于另一个问题 )。 It discards the parts of the rows longer than the shortest row. 它会丢弃比最短行长的行的部分。

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

We are now reading the sub-sequences across the lists as usual 我们现在像往常一样阅读列表中的子序列

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

The problem 问题

We find the product of each of the sub-sequences by map ping product on to them. 我们发现product分别由子序列的mapproduct上给他们。

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

This reduces the lists to a single number each 这会将列表减少为单个数字

[940584960,268738560,447897600,1791590400]

From which we must find the maximum . 从中我们必须找到maximum

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

The answer is 答案是

1791590400 1791590400

If you're not familiar with the functions used, the first thing you should do is examine the types of each function. 如果您不熟悉所使用的函数,首先应该检查每个函数的类型。 Since this is function composition, you apply from inside out (ie operations occur right to left, bottom to top when reading). 由于这是功能组合,因此您可以从内到外应用(即操作在读取时从右到左,从下到上)。 We can walk through this line by line. 我们可以逐行浏览。

Starting from the last line, we'll first examine the types. 从最后一行开始,我们将首先检查类型。

: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])

Since type String = [Char] (so [String] is equivalent to [[Char]] ), this line is converting the multi-line number into a single array of number characters. 由于type String = [Char] (因此[String]等同于[[Char]] ),此行将多行号转换为单个数字字符数组。 More precisely, it first creates an array of strings based on the full string. 更准确地说,它首先根据完整字符串创建一个字符串数组。 That is, one string per new line. 也就是说,每个新行一个字符串。 It then merges all of these lines (now containing only number characters) into a single array of characters (or a single String ). 然后它将所有这些行(现在只包含数字字符)合并为一个字符数组(或单个String )。

The next line takes this new String as input. 下一行将此新String作为输入。 Again, let's observe the types: 再次,让我们观察类型:

: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

If we apply these operations to our string current input, the first thing that happens is we map the composed function of (fromIntegral . digitToInt) over each character in our string. 如果我们将这些操作应用于我们的字符串当前输入,那么首先发生的是我们将字符串中每个字符的组合函数(fromIntegral . digitToInt)映射。 What this does is turn our string of digits into a list of number types. 这样做是将我们的数字串转换为数字类型列表。 EDIT As pointed out below in the comments, the fromIntegral in this example is to prevent overflow on 32-bit integer types. 编辑正如下面的注释中所指出的,本例中的fromIntegral是为了防止32位整数类型的溢出。 Now that we have converted our string into actual numeric types, we start by running tails on this result. 现在我们已经将字符串转换为实际的数字类型,我们首先在此结果上运行tails。 Since (by the problem statement) all values must be adjacent and we know that all of the integers are non-negative (by virtue of being places of a larger number), we take only the first 13 elements since we want to ensure our multiplication is groupings of 13 consecutive elements. 由于(通过问题陈述)所有值必须是相邻的,并且我们知道所有整数都是非负的(由于是更大数字的位置),我们只采用前13个元素,因为我们要确保我们的乘法是13个连续元素的分组。 How this works is difficult to understand without considering the next line. 如果不考虑下一行,这很难理解。

So, let's do a quick experiment. 那么,让我们做一个快速的实验。 After converting our string into numeric types, we now have a big list of lists. 将我们的字符串转换为数字类型后,我们现在有一个很大的列表列表。 This is actually kind of hard to think about what we actually have here. 这实际上很难想到我们实际拥有的东西。 For sake of understanding, the contents of the list are not very important. 为了便于理解,列表的内容不是很重要。 What is important is its size. 重要的是它的大小。 So let's take a look at an artificial example: 那么让我们来看一个人为的例子:

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

You can see what we have here is a big list of 13 elements. 你可以看到我们这里有13个元素的大清单。 Each element is a list of size 1000 (ie the full dataset) down to 988 in descending order. 每个元素都是大小为1000的列表(即完整数据集),降序为988。 So this is what we currently have for input into the next line which is, arguably, the most difficult-- yet most important-- line to understand. 所以这就是我们目前用于输入下一行的东西,可以说,这是最难理解但最重要的一行。 Why understanding this is important should become clear as we walk through the next line. 当我们走完下一行时,为什么理解这一点很重要。

: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

Remember how I mentioned we had a list of 13 elements before (of varying-sized lists)? 还记得我之前提到过我们之前有13个元素的列表(不同大小的列表)吗? This is important now. 这很重要。 The line is going to iterate over that list and apply (zipWith (:)) to it. 该行将迭代该列表并应用(zipWith (:)) The (repeat []) is such that each time zipWith is called on a subsequence, it starts with an empty list as its base. (repeat [])是这样的,每次在子zipWith上调用zipWith ,它都以空列表为基础开始。 This allows us to construct a list of lists containing our adjacent subsequences of length 13. 这允许我们构建包含长度为13的相邻子序列的列表。

Finally, we get to the last line which is pretty easy. 最后,我们到达最后一行很容易。 That said, we should still be mindful of our types 也就是说,我们仍然应该注意我们的类型

: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

The first thing we do is map the product function over each subsequence. 我们要做的第一件事是在每个子序列上映射product函数。 When this has completed we end up with a list of numeric types (hey, we finally don't have a list of lists anymore!). 完成后我们最终会得到一个数字类型列表(嘿,我们终于没有列表了!)。 These values are the products of each subsequence. 这些值是每个子序列的乘积。 Finally, we apply the maximum function which returns only the largest element in the list. 最后,我们应用maximum函数,它只返回列表中最大的元素。

EDIT: I found out later what the foldr expression was for. 编辑:我后来发现了foldr表达式的用途。 (See comments bellow my answer). (见下面的评论我的回答)。

I think that this could be expressed in different way - You can simply add a guard at the end of the list. 我认为这可以用不同的方式表达 - 你可以简单地在列表的末尾添加一个警卫。

My verbose version of that solution would be: 我的详细版本的解决方案是:

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