Haskell中嵌套列表相似函数的优化实现求助
Great question! You're spot on that we can cut down the code duplication here—both flatB and flatC share identical core logic; the only differences are how they validate and parse the count element. Let's go through two solid ways to simplify this: using a higher-order function (the quickest, most straightforward fix) and using a typeclass (if you want a unified function name for both types).
1. Extract Common Logic with a Higher-Order Function
First, let's pull the repeated core logic into a generic function that accepts two helper functions: one to check if an element is a valid count, and another to convert that element to an Int. This keeps the main logic in one place, and we just pass in the type-specific bits for flatB and flatC.
We'll also add safety checks to avoid runtime crashes from head (your original code would fail if, say, flatB received [["a"], []]—this fixes that):
import Data.Char (isDigit) flatGeneric :: (a -> Bool) -> (a -> Int) -> [a] -> [a] flatGeneric _ _ [] = [] flatGeneric _ _ [x] = [x] flatGeneric isCount getCount (a : b : xs) | isCount b = replicate (getCount b) a ++ flatGeneric isCount getCount xs | otherwise = a : flatGeneric isCount getCount (b : xs)
Now define flatC and flatB using this generic function:
For flatC (String elements)
flatC :: [String] -> [String] flatC = flatGeneric isValidCount parseCount where isValidCount s = not (null s) && isDigit (head s) parseCount s = read s :: Int
For flatB ([String] elements)
flatB :: [[String]] -> [[String]] flatB = flatGeneric isValidCount parseCount where isValidCount bs = not (null bs) && let n = head bs in not (null n) && isDigit (head n) parseCount bs = read (head bs) :: Int
This approach keeps things simple, eliminates duplication, and makes it easy to modify the core logic later without updating both functions.
2. Unify with a Typeclass
If you want to use the same function name (flat) for both types, a typeclass is the way to go. We'll define a Flat typeclass with a flat method, then write instances for String and [String]—reusing our flatGeneric function to avoid repeating logic:
class Flat a where flat :: [a] -> [a] instance Flat String where flat = flatGeneric isValidCount parseCount where isValidCount s = not (null s) && isDigit (head s) parseCount s = read s :: Int instance Flat [String] where flat = flatGeneric isValidCount parseCount where isValidCount bs = not (null bs) && let n = head bs in not (null n) && isDigit (head n) parseCount bs = read (head bs) :: Int
Now you can call flat for both types—just make sure the compiler knows which type you're working with (via type annotations or context):
> flat ["a","2","b"] :: [String] ["a","a","b"] > flat [["a","b"],["2"]] :: [[String]] [["a","b"],["a","b"]]
Which Approach to Choose?
- Use the higher-order function if you want explicit, distinct function names (
flatB/flatC) and prefer simplicity over abstraction. - Use the typeclass if you want a unified interface (
flat) for both types, which is useful if you plan to add more instances later for other types.
Either way, you'll eliminate code duplication and make your code easier to maintain!
内容的提问来源于stack exchange,提问作者Meowcolm024

