سؤال

آسف أنا لا أحصل تماما على FP حتى الآن، أريد تقسيم سلسلة من الخطوط إلى سلسلة من تسلسل الخطوط، على افتراض خط فارغ كقسمة فقرة، يمكنني أن أفعل ذلك في بيثون مثل هذا:

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

أنا متأكد من وجود طريقة أقصر. :-)

يحرر: ephemient ويشير إلى أن (not . null) هو نظافة من ذلك (/= ""). وبعد حتى نتمكن من الكتابة

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

المتكرر (not . null) هو مؤشر قوي على أننا يجب علينا حقا مجردة هذا في وظيفة، وهذا هو ما data.list.Split الوحدة النمطية يفعل، كما هو مذكور في الجواب أدناه.

نصائح أخرى

أنا أحاول أيضا أن أتعلم هاسكل. يمكن أن يكون الحل لهذا السؤال:

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

حيث أنا أستخدم الوظائف من قائمة البيانات. وبعد تلك التي أستخدمها متاحة بالفعل من مقدمة، ولكن يمكنك العثور على وثائقها في الرابط.

الفكرة هي العثور على الفقرة الأولى باستخدام span (/= ""). وبعد هذا سيعيد الفقرة، والخطوط التالية. ثم ننص على قائمة الأصغر من الخطوط التي أسميها rest.

قبل تقسيم الفقرة الأولى، نقفل أي خطوط فارغة باستخدام dropWhile (== ""). وبعد من المهم أن تأكل الخط (الخط) الفارغ يفصل الفقرات. كانت محاولتي الأولى هذا:

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

ولكن هذا فشل عندما نصل إلى الفقرة النهائية منذ ذلك الحين rest ثم السلسلة الفارغة:

* الصفحة الرئيسية> الفقرات ["FOO"، "Bar"، ""، "،"، "،"، "Bla"، "Bla"] [["FOO"، "Bar"]، ["Hehe"]، [" BLA "،" Bla "] *** استثناء: prelude.tail: قائمة فارغة

إن إسقاط الخطوط الفارغة يحل هذا، كما أنه يجعل الرمز يعالج أي عدد من الخطوط الفارغة كفاصل فاصل فقرة، وهو ما أتوقعه كمستخدم.

أنظف الحل سيكون استخدام شيء مناسب من الانقسام صفقة.

ستحتاج إلى تثبيت ذلك أولا، ولكن بعد ذلك 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. وبعد حل أكثر اعتمادا إذا كان أكثر مرونة. باستخدام Recursion Basic One يمكن أن يكتب:

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

span انشقاق قائمة في النقطة التي تصبح الوظيفة المقدمة كاذبة (الخط الأول الفارغ)، dropWhile يزيل العناصر الرائدة التي تكون الوظيفة المقدمة منها صحيحة (أي خطوط فارغة رائدة).

أن تأتي متأخرا أفضل من ألا تأتي أبدا.

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