Учитывая список, как я могу выполнить некоторые преобразования только в подсписках, каждые два элемента которых удовлетворяют двоичному предикату?
(В моем фактическом случае использования у меня есть список типа
[SomeType]
,
SomeType
наличие конечного числа конструкторов, все нулевые; в дальнейшем я буду использовать
String
вместо
[SomeType]
и использовать только 4
Char
s, чтобы немного упростить.)
У меня есть такой список
"aaassddddfaaaffddsssadddssdffsdf"
где каждый элемент может быть одним из
'a'
,
's'
,
'd'
,
'f'
, и я хочу произвести дополнительную обработку каждой непрерывной последовательности неa
s, скажем, переворачивая их в верхний регистр и меняя последовательность на обратную, таким образом получая
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
. (Я добавил требование реверсирования, чтобы прояснить, что обработка включает в себя все смежные не
'a'
-s в то же время.)
Чтобы включить каждый суб-String
верхний регистр, я могу использовать это:
func :: String -> String
func = reverse . map Data.Char.toUpper
Но как мне запустить это
func
только на суб-String
s не-'a'
с?
Моя первая мысль, что
Data.List.groupBy
может быть полезным, и общее решение может быть таким:
concat $ map (\x -> if head x == 'a' then x else func x)
$ Data.List.groupBy ((==) `on` (== 'a')) "aaassddddfaaaffddsssadddssdffsdf"
Однако это решение меня не убеждает, поскольку я использую
== 'a'
как при группировке (что мне кажется хорошим и неизбежным), так и при принятии решения, следует ли использовать верхний регистр группы.
Я ищу советы, как мне лучше всего выполнить эту небольшую задачу.
Об ответах
Мне нужно время, чтобы просмотреть их и переварить, прежде чем голосовать / принимать, так как я все еще новичок в этом языке, и понимание ваших ответов для меня нетривиально, хотя я считаю, что все они хорошего качества.
5 ответов
Вы можете классифицировать элементы списка по предикату перед группировкой. Обратите внимание, что я изменил смысл предиката, чтобы указать, какие элементы подлежат преобразованию, а не какие элементы сохраняются.
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Arrow ((&&&))
import Data.Function (on)
import Data.Monoid (First(..))
mapSegmentsWhere
:: forall a. (a -> Bool) -> ([a] -> [a]) -> [a] -> [a]
mapSegmentsWhere p f
= concatMap (applyMatching . sequenceA) -- [a]
. groupBy ((==) `on` fst) -- [[(First Bool, a)]]
. map (First . Just . p &&& id) -- [(First Bool, a)]
where
applyMatching :: (First Bool, [a]) -> [a]
applyMatching (First (Just matching), xs)
= applyIf matching f xs
applyIf :: forall a. Bool -> (a -> a) -> a -> a
applyIf condition f
| condition = f
| otherwise = id
Пример использования:
> mapSegmentsWhere (/= 'a') (reverse . map toUpper) "aaassddddfaaaffddsssadddssdffsdf"
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
Здесь я использую
First
моноид с
sequenceA
объединить списки смежных совпадающих элементов из
[(Bool, a)]
к
(Bool, [a])
, но вы также можете использовать что-то вроде
map (fst . head &&& map snd)
. Вы также можете пропустить
ScopedTypeVariables
если вы не хотите писать подписи типа; Я просто включил их для ясности.
Если нам нужно помнить разницу между
'a'
s и остальные, давайте поместим их в разные ветки
Either
. Фактически, теперь, когда мы находимся, давайте определим новый тип:
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE ViewPatterns #-}
import Data.Bifoldable
import Data.Char
import Data.List
newtype Bunched a b = Bunched [Either a b] deriving (Functor, Foldable)
instance Bifunctor Bunched where
bimap f g (Bunched b) = Bunched (fmap (bimap f g) b)
instance Bifoldable Bunched where
bifoldMap f g (Bunched b) = mconcat (fmap (bifoldMap f g) b)
fmap
позволит поработать без разделителей. fold
вернет конкатенацию разделителей, bifold
вернет конкатенацию всего. Конечно, мы могли бы определить отдельные функции, не связанные с
Foldable
и
Bifoldable
, но зачем избегать уже существующих абстракций?
Чтобы разделить список, мы можем использовать unfoldr
который поочередно ищет
a
s и не-a
s с span
функция:
splitty :: Char -> String -> Bunched String String
splitty c str = Bunched $ unfoldr step (True, str)
where
step (_, []) = Nothing
step (True, span (== c) -> (as, ys)) = Just (Left as, (False, ys))
step (False, span (/= c) -> (xs, ys)) = Just (Right xs, (True, ys))
Запускаем:
ghci> bifold . fmap func . splitty 'a' $ "aaassddddfaaaffddsssadddssdffsdf"
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
Примечание:
Bunched
на самом деле то же самое, что
Tannen [] Either
из пакета bifunctors, если вы не возражаете против дополнительной зависимости.
Здесь есть и другие ответы, но я думаю, что они слишком взволнованы абстракциями итераций. Ручная рекурсия, поочередно выбирающая то, что соответствует предикату, а что нет, делает эту задачу чрезвычайно простой:
onRuns :: Monoid m => (a -> Bool) -> ([a] -> m) -> ([a] -> m) -> [a] -> m
onRuns p = go p (not . p) where
go _ _ _ _ [] = mempty
go p p' f f' xs = case span p xs of
(ts, rest) -> f ts `mappend` go p' p f' f rest
Попробуйте в ghci:
Data.Char> onRuns ('a'==) id (reverse . map toUpper) "aaassddddfaaaffddsssadddssdffsdf"
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
Мы можем делать то, что вы описываете, шаг за шагом, получая ясный простой минимальный код, который мы сможем легко прочитать и понять позже:
foo :: (a -> Bool) -> ([a] -> [a]) -> [a] -> [a]
foo p f xs = [ a
| g <- groupBy ((==) `on` fst)
[(p x, x) | x <- xs] -- [ (True, 'a'), ... ]
, let (t:_, as) = unzip g -- ( [True, ...], "aaa" )
, a <- if t then as else (f as) ] -- final concat
-- unzip :: [(b, a)] -> ([b], [a])
Разбиваем список на одинаковые-
p
пролеты и распаковать каждую группу с помощью
unzip
. Пробуем:
> foo (=='a') reverse "aaabcdeaa"
"aaaedcbaa"
Так что нет, используя
== 'a'
это избежать , и , следовательно , не особенно хорошо, представляя ненужное ограничение на ваш тип данных , когда все , что нам нужно это равенство на Booleans.
Вот простое решение - функция
process
ниже - для этого требуется только определить две функции
isSpecial
и
func
. Учитывая конструктор вашего типа
SomeType
,
isSpecial
определяет, является ли он одним из тех конструкторов, которые образуют специальный подсписок. Функция
func
это тот, который вы указали в своем вопросе; он определяет, что должно происходить со специальными подсписками.
Код ниже предназначен для списков символов. Просто измени
isSpecial
и
func
чтобы он работал для ваших списков конструкторов.
isSpecial c = c /= 'a'
func = reverse . map toUpper
turn = map (\x -> ([x], isSpecial x))
amalgamate [] = []
amalgamate [x] = [x]
amalgamate ((xs, xflag) : (ys, yflag) : rest)
| xflag /= yflag = (xs, xflag) : amalgamate ((ys, yflag) : rest)
| otherwise = amalgamate ((xs++ys, xflag) : rest)
work = map (\(xs, flag) -> if flag then func xs else xs)
process = concat . work . amalgamate . turn
Попробуем на вашем примере:
*Main> process "aaassddddfaaaffddsssadddssdffsdf"
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
*Main>
При применении одной функции за раз показаны предпринятые промежуточные шаги:
*Main> turn "aaassddddfaaaffddsssadddssdffsdf"
[("a",False),("a",False),("a",False),("s",True),("s",True),("d",True),
("d",True),("d",True),("d",True),("f",True),("a",False),("a",False),
("a",False),("f",True),("f",True),("d",True),("d",True),("s",True),
("s",True),("s",True),("a",False),("d",True),("d",True),("d",True),
("s",True),("s",True),("d",True),("f",True),("f",True),("s",True),
("d",True),("f",True)]
*Main> amalgamate it
[("aaa",False),("ssddddf",True),("aaa",False),("ffddsss",True),
("a",False),("dddssdffsdf",True)]
*Main> work it
["aaa","FDDDDSS","aaa","SSSDDFF","a","FDSFFDSSDDD"]
*Main> concat it
"aaaFDDDDSSaaaSSSDDFFaFDSFFDSSDDD"
*Main>