2009-05-30 4 views
4

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

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

ответ

4

Я только начинающий программист 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 опыт свидетельствует о том, что почти всегда есть библиотечные функции для делать вещи, как эти :) Одна из функций, связанных называются 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», и it's not in the libraries, но мы можем отфильтровать плохие сами:

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 module, как указано в приведенной ниже ответ.

+0

У вас есть ссылка на это? Отчет библиотеки Haskell 98 (http://www.cs.auckland.ac.nz/references/haskell/haskell-library-1.4-html/list.html) * говорит, что * это предикат равенства, но дает явную реализацию функция groupBy. Обратите внимание, что подпись типа groupBy не имеет ограничения «Eq», предполагая, что она должна работать с произвольной (транзитивной?) Предикатной функцией ... Похоже, что она используется так: http: //www.haskell .org/haskellwiki/List_function_suggestions # Generalize_groupBy_and_friends – ShreevatsaR

+2

Я бы предпочел использовать 'not. null', чтобы использовать '(/ =" ")', что приведет к еще более свободным точкам 'paragraph = map (filter $ not. null). groupBy (const $ not. null) ' – ephemient

3

Подумайте, рекурсивно.

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]) 
+1

О, мой! Является ли это [Парас] (http://bulbapedia.bulbagarden.net/wiki/Paras)? –

4

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

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 

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

+1

'null' немного более эффективен и определенно более общий, чем тестирование равенства (с) с пустыми списками и строками. 'span (/ =" ")' => 'break null',' dropWhile (== "") '=>' dropWhile null', оба улучшают читаемость. – ephemient

+0

Да и нет - при поиске пустой строки в списке строк, я думаю, что более читаемо искать "". По крайней мере, для начинающих вроде меня :-) –

3

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

4

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

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

0

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

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"]] 
Смежные вопросы