Мне не нравится тип compTrips
; это слишком разрешительно. Объявление о том, что он может вернуть [Int]
, говорит, что вы не знаете, сколько Int
s он вернет. Но вы знаете, что будет ровно два; Итак:
compTrips :: [Int] -> [Int] -> (Int, Int)
В реализации я бы предпочел sum
вместо foldr (+) 0
; он более четко говорит о ваших намерениях, а также, в качестве дополнительного преимущества, sum
использует строгий левый сгиб и поэтому иногда может быть более эффективным с точки зрения памяти:
compTrips as bs =
let oneIfGreater a b = if a > b
then 1
else 0
countGreater x y = sum $ zipWith oneIfGreater x y
in (countGreater as bs, countGreater bs as)
Мне не нравится, что мы рассчитать каждое сравнение дважды. Я хотел бы отделить вычисление сравнения от подсчета того, как проходило сравнение. В то же время я бы переключился с использования sum
и вашей пользовательской реализации fromEnum
(а именно oneIfGreater
) на использование комбинации length
и filter
(или, в этом случае, когда мы хотим оба "стороны" фильтра, partition
). Итак:
import Data.Bifunctor
import Data.List
compTrips as bs = bimap length length . partition id $ zipWith (>) as bs
Я думаю, что вы можете абстрагировать чтение и анализ строки, чтобы этот лог c не реплицировался. Итак:
readInts :: IO [Int]
readInts = do
line <- getLine
pure (map read $ words line)
main = do
alice <- readInts
bob <- readInts
let (a, b) = compTrips alice bob
putStrLn $ show a ++ " " ++ show b
Мне не нравится тип read
, почти по той же причине, по которой мне не нравится тип compTrips
. В этом случае заявление о том, что он может принять любой String
, говорит о том, что он может анализировать что угодно, хотя в действительности он может анализировать только очень специфический c язык. readMaybe
имеет лучший тип, говоря, что иногда он может не анализироваться:
readMaybe :: Read a => String -> Maybe a
Существует большая коллекция основанных на Applicative
методов для комбинирования обработки ошибок многих вызовов с readMaybe
; особенно проверьте traverse
(что немного похоже на map
, но с возможностью обработки ошибок) и liftA2
(который может преобразовать любую двоичную операцию в одну, которая может обрабатывать ошибки).
One мы могли бы использовать его, чтобы напечатать красивое сообщение об ошибке, когда оно не удалось, поэтому:
import System.IO
import Text.Read
readInts = do
line <- getLine
case traverse readMaybe (words line) of
Just person -> pure person
Nothing -> do
hPutStrLn stderr "That doesn't look like a space-separated collection of numbers! Try again."
readInts
(Существуют другие варианты обработки ошибок.)
Это оставляет нас со следующим последним program:
import Data.Bifunctor
import Data.List
import System.IO
import Text.Read
compTrips :: [Int] -> [Int] -> (Int, Int)
compTrips as bs = bimap length length . partition id $ zipWith (>) as bs
readInts :: IO [Int]
readInts = do
line <- getLine
case traverse readMaybe (words line) of
Just person -> pure person
Nothing -> do
hPutStrLn stderr "That doesn't look like a space-separated collection of numbers! Try again."
readInts
main :: IO ()
main = do
alice <- readInts
bob <- readInts
let (a, b) = compTrips alice bob
putStrLn $ show a ++ " " ++ show b
Хотя текст на самом деле немного длиннее, я бы посчитал это более идиоматической реализацией c; две основные вещи - уделять пристальное внимание тому, чтобы избегать использования частичных функций (таких как read
и ваше частичное сопоставление с шаблоном в результате compTrips
) и увеличивать зависимость от массовых операций, предоставляемых библиотекой, при обработке списков (таких как наш использование length
и partition
).