簡體   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