Функциональные абзацы - PullRequest
4 голосов
/ 31 мая 2009

Извините, я еще не получил 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

Как бы вы поступили в Эрланге или Хаскеле?

Ответы [ 6 ]

4 голосов
/ 31 мая 2009

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

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

4 голосов
/ 31 мая 2009

Я только начинающий программист на 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"]]

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

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 /= "")

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

Edit : ephemient указывает, что (not . null) чище, чем (/= ""). Таким образом, мы можем написать

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

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

4 голосов
/ 31 мая 2009

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

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

, где я использую функции из Data.List . Те, что я использую, уже доступны в 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

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

3 голосов
/ 31 мая 2009

Вы хотите сгруппировать строки, поэтому 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 удаляет ведущие элементы, для которых предоставленная функция истинна (любые пустые начальные строки).

3 голосов
/ 31 мая 2009

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

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])
0 голосов
/ 23 декабря 2014

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

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"]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...