[英]Project Euler 8 - I don't understand it
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位,所以我們只想take
前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]
]
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
分別由子序列的map
平product
上給他們。
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.