Итерация по строке и замена одиночных символов подстроками в haskell

Я пытаюсь узнать некоторые Haskell и мне было трудно. У меня есть некоторые проблемы с моим текущий проект. Идея в том, что я должен пройти через строку и заменить определенные символы с новыми подстроками. Например, если у меня есть строка "FLXF", и я хочу заменить каждый F с подстрокой под названием "FLF "результат должен быть"FLFLXFLF". Сейчас я работаю над этим конкретная проблема в течение нескольких часов. Я читал о типах, различных функциях, которые могут пригодиться (карта, fold и т. д.), И все же я не смог решить эту проблему.

код ниже-это некоторые из разных попыток, которые у меня были:

apply :: String -> String
apply []     = []
apply (x:xs) = if (x == 'F')
               then do show "Hello"
                       apply xs
               else (apply (xs))

этот пример здесь я просто пытался показать привет каждый раз, когда я столкнулся с "F", но все это показывает"", поэтому это явно не работает. Я действительно не уверен, что утверждение if else-это путь сюда. Я также думал, что функциональная карта может сделать трюк. Здесь код, о котором я думал, может выглядеть примерно так: это:

map (x y -> if y == 'F' then "FLD" else y) "FLF"

но это дает мне ошибку типа. Так что, как видите, я заблудился. Извините, что я плохо знаю Хаскелла, но я все еще новичок в этом. Я очень надеюсь, что некоторые из вас могут помочь мне здесь или подтолкнуть меня в правильном направлении. Не стесняйтесь задавать вопросы, если я был неясен о чем-то.

спасибо заранее!

Джон

4 ответов


map (\x y -> if y == 'F' then "FLD" else y) "FLF"

это почти верно.

первый... почему функция принимает два аргумента?

map (\y -> if y == 'F' then "FLD" else y) "FLF"

ошибка оставшегося типа заключается в том, что then филиала дает String, а else филиала дает Char (каждая из двух ветвей должна давать значение одного и того же типа). Так что мы сделаем else филиала дать String вместо этого (вспомните, что String синоним [Char]):

map (\y -> if y == 'F' then "FLD" else [y]) "FLF"

теперь проблема в том, что это дает вам [String] значение вместо String. Итак, мы соединим все эти строки вместе:

concat (map (\y -> if y == 'F' then "FLD" else [y]) "FLF")

эта комбинация concat и map достаточно распространен, что есть стандартная функция, которая объединяет их.

concatMap (\y -> if y == 'F' then "FLD" else [y]) "FLF"

concatMap самая интуитивная вещь здесь. Такая комбинация между отображением структуры данных функцией, которая сама возвращает тип структуры данных (в данном случае список) и объединением результатов обратно в один "плотный" список, действительно очень общие в Haskell, и действительно не только для списков.

я хотел бы объяснить, почему ваша первая попытка компилируется вообще, и что она на самом деле делает – потому что она полностью отличается от того, что вы, наверное, думаете!

apply (x:xs) = if (x == 'F')

эта строка все еще совершенно ясна: вы просто берете первый символ из строки и сравниваете его с 'F'. На бит "пешеход", чтобы вручную разобрать строку, но хорошо. Ну, название, которое вы дали функции, не особенно велико, но я буду придерживаться его здесь.

               then do show "Hello"

вот это интересно. Вы, наверное, думаете do начинает список пунктов: "сначала сделайте это, затем сделайте это"... как в простой Привет, Мир-ish примеры программ. Но всегда помните: в Хаскелле обычно нет такой вещи, как ордер в котором материал рассчитывается. Это происходит только в IO контексте. Но нет IO в коде!?!

не уверен, что вы слышали о том, что IO на самом деле, в любом случае, здесь вы идете: это Monad. Эти "мифические конструкции Хаскелла, о которых вы только читали в книгах"...

действительно, хотя это может привести немного далеко вот,этот вопрос охватывает все, что нужно знать о Monads! Как это?

вот еще (правильно!) способ определения функции.

apply' str = do
   x <- str
   if (x == 'F')
    then "FLF"
    else return x

поэтому я использую этот странный do синтаксис, и это не в IO, и это выглядит совершенно иначе, чем то, что вы напишете в IO, но он работает. Как?

   x <- str

на do нотации, variable <- action всегда означает что-то вроде " возьмите одно значение из этой монадической штуки, и назовите это x". То, что вы, вероятно, видели, что-то вроде

response <- getLine

что означает " возьмите пользовательский ввод из реального мира (из IO монады!) и назовем это response". В x <- str, это строка, которая у нас есть, а не IO действие. Поэтому мы берем символ из строки – красиво и легко!

на самом деле, это не совсем правильно, хотя. - возьми!--63-->a символ" - это то, что вы делаете с apply (x:xs) = ..., который просто берет первый. В отличие от, x <- str на самом деле принимает все возможные символы из строки, один за другим. Если вы привыкли к процедурным языкам, это может показаться очень несовместимым с response <- getLine, но на самом деле это не так: getLine и состоит из все возможные входные данные что пользователь может дать, и программа должна действовать в соответствии с этим.

   if (x == 'F')

ничего неожиданного здесь нет, но

    then "FLF"

уоу! Вот так просто? Давайте сначала посмотрим на следующий линия

    else return x

ОК, этот выглядит знакомо, но на самом деле это не так. На других языках это означало бы: "мы закончили с нашей функцией, x результат". Но это, очевидно, не то, что происходит здесь, потому что x и Char, и "тип возврата"apply' is String. В Хаскелле,return на самом деле имеет мало общего с возвращением значений из функции, вместо этого это означает "поместить это значение в монадический контекст, в котором мы работаем". Если монады были IO, это было бы то же самое:верните это значение в реальный контекст (это не означает печатать значение или что-то еще, просто передать его). Но здесь наш контекст-это строка или, скорее, список (символов, так что это is a String).

правильно, так что если x не 'F' мы положили его обратно в строку. Это звучит достаточно разумно, но как насчет then "FLF"? Обратите внимание, что я также могу написать это путь:

   if (x == 'F')
    then do
       x' <- "FLF"
       return x'
    else return x

что означает, что я беру все символы из "FLW" и вернуть их обратно в общий результат. Но нет необходимости думать только об окончательном результате, мы также можем изолировать только эту часть do { x' <- "FLF"; return x' } - и, совершенно очевидно, его значение не что иное, как строка !

надеюсь, теперь вы поняли, почему apply' строительство. Вернемся к вашей версии, хотя на самом деле это не имеет большого смысла...

           then do
              show "Hello"
              apply xs

здесь мы имеем строка, которая не находится в конце do блок, но не имеет <- в нем. Обычно вы видите это в IO в что-то вроде

main = do
   putStrLn "How ya doin'?"
   response <- getLine
   ...

помните, что действия" только для вывода " имеют тип IO() в Haskell, что означает, что они напрямую не возвращают никакого значимого значения, просто тривиальное значение (). Таким образом, вы действительно не заботитесь об этом, но вы все равно можете оценить его:

main = do
   trivial <- putStrLn "Hello, let's see what this IO action returns:"
   print trivial

компилирует и выводит

Привет, давай посмотрите, что возвращает это действие ввода-вывода:
()

было бы глупо, если бы нам пришлось делать эту оценку () все время, поэтому Haskell позволяет просто оставить () <- выход. Это действительно так!

так как show "Hello" в середине do блок в основном означает " возьмите один символ из show "Hello" (это просто строка со значением "\"Hello\""), но не делайте ничего другого с этим персонажем / просто бросьте его прочь."

остальная часть вашего определения - это просто другие рекурсивные вызовы apply, но поскольку никто из них не делает ничего более интересного, чем выбрасывать персонажей, вы в конечном итоге в apply [] = [], так что это конечный результат: пустая строка.


Если-то-иначе... Я знаю, что Haskell поддерживает их, однако, я очень удивлен, что никто здесь их не убрал...

Итак, ниже приведены мои решения для разных случаев замены.

  • замена символ
  • замена слова
  • замена через функции на каждое слово

$ кошки заменять.hs

import Data.List (isPrefixOf)

replaceC :: Char -> Char -> String -> String
replaceC _ _ [] = []
replaceC a b (x:xs)
  | x == a    = b:replaceC a b xs
  | otherwise = x:replaceC a b xs

replaceW :: String -> String -> String -> String
replaceW a b s = unwords . map replaceW' $ words s
  where replaceW' x | x == a    = b
                    | otherwise = x

replaceF :: (String -> String) -> String -> String
replaceF f = unwords . map f . words

string = "Hello world ^fg(blue)"

main = do
    print string
    print $ replaceC 'o' 'z' string
    print $ replaceW "world" "kitty" string
    print . replaceF f . replaceW "world" "kitty" $ replaceC 'H' 'Y' string
  where f s | "^" `isPrefixOf` s = '^':'^':drop 1 s
            | otherwise = s

$ runhaskell заменить.hs

"Hello world ^fg(blue)"
"Hellz wzrld ^fg(blue)"
"Hello kitty ^fg(blue)"
"Yello kitty ^^fg(blue)"

ваша основная ошибка заключалась в том, что вы хотели заменить символ в строке на строку. Это невозможно, потому что строка-это список символов, а символ-это символ, а не короткая строка. Ни одна строка никогда не является символом, даже если ее длина равна 1.

следовательно, то, что вы действительно хотели, это заменить некоторый символ на некоторые другие символы. Ваш подход был многообещающим и мог быть завершен так:

replace [] = []  -- nothing to replace in an empty string
replace (c:cs) = if c == 'F' then 'F':'L':'F':replace cs
                             else c:replace cs