Haskell帕斯卡三角输出优化求助:如何实现等边三角效果?
Hey there! I totally get the frustration—getting Pascal's Triangle to render as a clean, symmetrical equilateral shape instead of a lopsided Christmas tree can be tricky with basic spacing logic. Let's break down what's off with your current approach and fix it up.
First, here's your original code for reference:
import Data.List (intercalate) import Control.Concurrent (threadDelay) import System.IO -- I love how amazingly concise Haskell code can be. This same program in C, C++ or Java -- would be at least twice as long. pascal :: Int -> Int -> Int pascal row col | col >= 0 && col <= row = if row == 0 || col == 0 || row == col then 1 else pascal (row - 1) (col - 1) + pascal (row - 1) col pascal _ _ = 0 pascalsTriangle :: Int -> [[Int]] pascalsTriangle rows = [[pascal row col | col <- [0..row]] | row <- [0..rows]] main :: IO () main = do putStrLn "" putStr "Starting at row #0, how many rows of Pascal's Triangle do you want to print out? " hFlush stdout numRows <- (\s -> read s :: Int) <$> getLine let triangle = pascalsTriangle numRows triangleOfStrings = map (intercalate ", ") $ map (map show) triangle lengthOfLastDiv2 = div ((length . last) triangleOfStrings) 2 putStrLn "" mapM_ (\s -> let spaces = [' ' | x <- [1 .. lengthOfLastDiv2 - div (length s) 2]] in (putStrLn $ spaces ++ s) >> threadDelay 200000) triangleOfStrings putStrLn ""
The Problem with Your Current Spacing
Your current logic calculates leading spaces based on half the last line's length minus half the current line's length. This works okay for small rows, but falls apart as numbers get longer (since different numbers take up varying widths) and doesn't account for consistent column alignment. The result is that rows don't line up symmetrically, creating that "Christmas tree" lean.
A Better Approach
To get a true equilateral triangle, we need to:
- Standardize number widths: Make every number take up the same horizontal space so columns align perfectly.
- Use consistent element spacing: Keep the gap between numbers uniform across all rows.
- Center rows properly: Calculate leading spaces based on the difference between the widest row (last one) and the current row's total width.
Here's the modified code that implements this:
import Data.List (intercalate) import Control.Concurrent (threadDelay) import System.IO pascal :: Int -> Int -> Int pascal row col | col >= 0 && col <= row = if row == 0 || col == 0 || row == col then 1 else pascal (row - 1) (col - 1) + pascal (row - 1) col pascal _ _ = 0 pascalsTriangle :: Int -> [[Int]] pascalsTriangle rows = [[pascal row col | col <- [0..row]] | row <- [0..rows]] main :: IO () main = do putStrLn "" putStr "Starting at row #0, how many rows of Pascal's Triangle do you want to print out? " hFlush stdout numRows <- (\s -> read s :: Int) <$> getLine let triangle = pascalsTriangle numRows -- Convert all triangle values to strings numStrings = map (map show) triangle -- Find the longest number string to use as a fixed width for all elements maxNumWidth = maximum $ concatMap (map length) numStrings -- Pad each number to the fixed width (right-aligned for clean digit alignment) paddedNumbers = map (map (\s -> replicate (maxNumWidth - length s) ' ' ++ s)) numStrings -- Join elements in each row with consistent spacing (adjust " " to tweak gap size) formattedRows = map (intercalate " ") paddedNumbers -- Get the total width of the widest row (the last one) maxRowWidth = length $ last formattedRows putStrLn "" -- Print each row with centered leading spaces mapM_ (\row -> do let leadingSpaces = replicate ((maxRowWidth - length row) `div` 2) ' ' putStrLn $ leadingSpaces ++ row threadDelay 200000) formattedRows putStrLn ""
Key Improvements Explained
- Fixed number widths: By calculating
maxNumWidth, we ensure every number (even large ones like 120 or 252) takes up the same space. This keeps vertical columns perfectly aligned. - Consistent element spacing: Using
" "as a separator between numbers ensures the gap between columns stays the same across all rows, which helps maintain symmetry. - True centering: Leading spaces are calculated as half the difference between the widest row's width and the current row's width. This centers each row perfectly relative to the entire triangle, creating that clean equilateral shape you want.
Feel free to tweak the separator (swap " " for " " or " ") if you want tighter or looser spacing between elements. If you prefer left-aligned numbers instead of right, just reverse the padding logic to s ++ replicate (maxNumWidth - length s) ' '.
内容的提问来源于stack exchange,提问作者Douglas Lewit

