Вопрос

Извините, я еще не совсем понял FP, я хочу разделить последовательность строк на последовательность последовательностей строк, предполагая пустую строку в качестве разделения абзаца, я мог бы сделать это на python следующим образом:

def get_paraghraps(lines):
    paragraphs = []
    paragraph = []
    for line in lines:
        if line == "": # I know it could also be  "if line:"
            paragraphs.append(paragraph)
            paragraph = []
        else:
            paragraph.append(line)
    return paragraphs

Как бы вы это сделали на Erlang или Haskell?

Это было полезно?

Решение

Я всего лишь начинающий программист на Haskell (и тот маленький Haskell, который я выучил, был 5 лет назад), но для начала я бы написал естественный перевод вашей функции с передачей накопителя ("текущий абзац") (я добавил типы, просто для наглядности):

type Line = String
type Para = [Line]

-- Takes a list of lines, and returns a list of paragraphs
paragraphs :: [Line] -> [Para]
paragraphs ls = paragraphs2 ls []

-- Helper function: takes a list of lines, and the "current paragraph"
paragraphs2 :: [Line] -> Para -> [Para]
paragraphs2 [] para = [para]
paragraphs2 ("":ls) para = para : (paragraphs2 ls [])
paragraphs2 (l:ls)  para = paragraphs2 ls (para++[l])

Это работает:

*Main> paragraphs ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["Line 3","Line 4"]]

Так что это решение проблемы.Но с другой стороны, опыт Haskell подсказывает, что почти всегда существуют библиотечные функции для выполнения подобных действий :) Вызывается одна связанная функция Группировка, и это почти работает:

paragraphs3 :: [Line] -> [Para]
paragraphs3 ls = groupBy (\x y -> y /= "") ls

*Main> paragraphs3 ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["","Line 3","Line 4"]]

Упс.Что нам действительно нужно, так это "splitBy", и этого нет в библиотеках, но мы сами можем отфильтровать плохие:

paragraphs4 :: [Line] -> [Para]
paragraphs4 ls = map (filter (/= "")) (groupBy (\x y -> y /= "") ls)

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

paragraphs5 = map (filter (/= "")) . groupBy (\x y -> y /= "")

Я уверен, что есть еще более короткий путь.:-)

Редактировать: эфемерный указывает на то , что (not . null) чище, чем (/= "").Чтобы мы могли написать

paragraphs = map (filter $ not . null) . groupBy (const $ not . null)

Повторяющийся (not . null) это убедительный признак того, что мы действительно должны абстрагировать это в функцию, и это то, что Модуль разделения данных.Списка. делает, как указано в ответе ниже.

Другие советы

Я также пытаюсь выучить Haskell.Решением этого вопроса могло бы быть:

paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs rest)
    where (p, rest) = span (/= "") (dropWhile (== "") lines)

где я использую функции из Данные.Список.Те, которые я использую, уже доступны в Prelude, но вы можете найти их документацию по ссылке.

Идея состоит в том, чтобы найти первый абзац, используя span (/= "").Это вернет абзац и следующие за ним строки.Затем мы выполняем рекурсию по меньшему списку строк, который я вызываю rest.

Перед разделением первого абзаца мы удаляем все пустые строки, используя dropWhile (== "").Это важно для того, чтобы убрать пустые строки, разделяющие абзацы.Моя первая попытка была такой:

paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs $ tail rest)
    where (p, rest) = span (/= "") lines

но это не удается, когда мы доходим до последнего абзаца, поскольку rest тогда это пустая строка:

*Main> paragraphs ["foo", "bar", "", "hehe", "", "bla", "bla"]
[["foo","bar"],["hehe"],["bla","bla"]*** Exception: Prelude.tail: empty list

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

Самым чистым решением было бы использовать что-то подходящее из расколотый посылка.

Сначала вам нужно будет установить это, но затем Data.List.Split.splitWhen null должен выполнить свою работу идеально.

Думайте рекурсивно.

get_paragraphs []      paras para = paras ++ [para]
get_paragraphs ("":ls) paras para = get_paragraphs ls (paras ++ [para]) []
get_paragraphs (l:ls)  paras para = get_paragraphs ls paras (para ++ [l])

Вы хотите сгруппировать строки, так что groupBy От Data.List кажется, это хороший кандидат.Он использует пользовательскую функцию для определения, какие строки "равны", поэтому можно указать что-то, что делает строки в одном и том же абзаце "равными".Например:

import Data.List( groupBy )

inpara :: String -> String -> Bool
inpara _ "" = False
inpara _ _  = True

paragraphs :: [String] -> [[String]]
paragraphs = groupBy inpara

Это имеет некоторые ограничения, поскольку inpara можно сравнить только две соседние строки, а более сложная логика не вписывается в рамки, заданные groupBy.Более элементарное решение, если оно более гибкое.Используя базовую рекурсию, можно написать:

paragraphs [] = []
paragraphs as = para : paragraphs (dropWhile null reminder)
  where (para, reminder) = span (not . null) as
                           -- splits list at the first empty line

span разбивает список в тот момент, когда предоставленная функция становится ложной (первая пустая строка), dropWhile удаляет начальные элементы, для которых предоставленная функция имеет значение true (любые начальные пустые строки).

Лучше поздно, чем никогда.

import Data.List.Split (splitOn)

paragraphs :: String -> [[String]]
paragraphs s = filter (not . null) $ map words $ splitOn "\n\n" s

paragraphs "a\nb\n\nc\nd"                == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n\nc\nd\n\n\n"    == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n \n  c\nd\n\n\n" == [["a", "b"], ["c", "d"]]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top