Як розділити рядок у Haskell?


163

Чи є стандартний спосіб розділити рядок в Haskell?

linesі wordsчудово працювати від розбиття на пробіл чи новий рядок, але, напевно, є стандартний спосіб розділити кому?

Я не міг знайти його в Hoogle.

Щоб бути конкретним, я шукаю щось, де split "," "my,comma,separated,list"повертається ["my","comma","separated","list"].


21
Я б дуже хотів, щоб така функція була в майбутньому випуску Data.Listабо навіть Prelude. Це настільки поширено і неприємно, якщо він не доступний для коду-гольфу.
фуз

Відповіді:


135

Існує пакет для цього під назвою спліт .

cabal install split

Використовуйте його так:

ghci> import Data.List.Split
ghci> splitOn "," "my,comma,separated,list"
["my","comma","separated","list"]

Він постачається з безліччю інших функцій для розбиття на відповідні роздільники або з декількома роздільниками.


9
Класно. Я не знав про цей пакет. Це остаточний розкол пакет , оскільки це дає більше контролю над роботою (облицювання простору в результатах, залиште роздільники в результаті, видалити послідовні роздільники і т.д ...). Існує так багато способів поділу списків, що неможливо мати єдину функцію, яка відповідатиме всім потребам, вам справді потрібен такий тип пакунків. split
gawi

1
в іншому випадку, якщо зовнішні пакети прийнятні, MissingH також забезпечує функцію розділення: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Цей пакет також забезпечує безліч інших функцій "приємно мати" і я вважаю, що від цього залежать зовсім деякі пакунки.
Еммануель Тузері

41
Розділений пакет тепер окрім платформи haskell останнього випуску.
Інтернет

14
імпортуйте Data.List.Split (splitOn) та вирушайте до міста. splitOn :: Eq a => [a] -> [a] -> [[a]]
Інтернет

1
@RussAbbott розділений пакет включений до платформи Haskell під час завантаження ( haskell.org/platform/contents.html ), але він не завантажується автоматично при складанні проекту. Додайте splitдо build-dependsсписку у файлі кабалу, наприклад, якщо ваш проект називається привіт, тоді у hello.cabalфайлі під executable helloрядком помістіть рядок типу "build-зависи: base, split" (зверніть увагу на два пробіли). Потім будуйте за допомогою cabal buildкоманди. Ср. haskell.org/cabal/users-guide/…
expz

164

Пам'ятайте, що ви можете шукати визначення функцій Prelude!

http://www.haskell.org/onlinereport/standard-prelude.html

Дивлячись там, визначення wordsє,

words   :: String -> [String]
words s =  case dropWhile Char.isSpace s of
                      "" -> []
                      s' -> w : words s''
                            where (w, s'') = break Char.isSpace s'

Отже, змініть його на функцію, яка приймає предикат:

wordsWhen     :: (Char -> Bool) -> String -> [String]
wordsWhen p s =  case dropWhile p s of
                      "" -> []
                      s' -> w : wordsWhen p s''
                            where (w, s'') = break p s'

Тоді називайте це будь-яким присудком, який ви хочете!

main = print $ wordsWhen (==',') "break,this,string,at,commas"

31

Якщо ви використовуєте Data.Text, існує splitOn:

http://hackage.haskell.org/packages/archive/text/0.11.2.0/doc/html/Data-Text.html#v:splitOn

Це побудовано на платформі Haskell.

Так, наприклад:

import qualified Data.Text as T
main = print $ T.splitOn (T.pack " ") (T.pack "this is a test")

або:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"

1
@RussAbbott, ймовірно, потрібно залежно від textпакета або встановити його. Хоч би належав до іншого питання.
Еммануель Тузері

Не вдалося збігти тип "T.Text" із "Char" Очікуваний тип: [Char] Фактичний тип: [T.Text]
Andrew Koster

19

У модулі Text.Regex (частина платформи Haskell) є функція:

splitRegex :: Regex -> String -> [String]

яка розбиває рядок на основі регулярного виразу. API можна знайти в Hackage .


Could not find module ‘Text.Regex’ Perhaps you meant Text.Read (from base-4.10.1.0)
Андрій Костер

18

Використання Data.List.Split, яке використовує split:

[me@localhost]$ ghci
Prelude> import Data.List.Split
Prelude Data.List.Split> let l = splitOn "," "1,2,3,4"
Prelude Data.List.Split> :t l
l :: [[Char]]
Prelude Data.List.Split> l
["1","2","3","4"]
Prelude Data.List.Split> let { convert :: [String] -> [Integer]; convert = map read }
Prelude Data.List.Split> let l2 = convert l
Prelude Data.List.Split> :t l2
l2 :: [Integer]
Prelude Data.List.Split> l2
[1,2,3,4]

14

Спробуйте це:

import Data.List (unfoldr)

separateBy :: Eq a => a -> [a] -> [[a]]
separateBy chr = unfoldr sep where
  sep [] = Nothing
  sep l  = Just . fmap (drop 1) . break (== chr) $ l

Працює лише для одного символу, але має бути легко розширюваним.


10

Не імпортуючи нічого прямого заміщення одного символу на пробіл, цільовим роздільником для цього wordsє пробіл. Щось на зразок:

words [if c == ',' then ' ' else c|c <- "my,comma,separated,list"]

або

words let f ',' = ' '; f c = c in map f "my,comma,separated,list"

Ви можете перетворити це у функцію з параметрами. Ви можете усунути параметр символів для відповідності багатьом моїм збігам, як, наприклад, у:

 [if elem c ";,.:-+@!$#?" then ' ' else c|c <-"my,comma;separated!list"]

9
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s

Напр

split ';' "a;bb;ccc;;d"
> ["a","bb","ccc","","d"]

Окремий кінцевий роздільник буде знято:

split ';' "a;bb;ccc;;d;"
> ["a","bb","ccc","","d"]

6

Я вчора почав вивчати Haskell, тому виправте мене, якщо я не прав, але:

split :: Eq a => a -> [a] -> [[a]]
split x y = func x y [[]]
    where
        func x [] z = reverse $ map (reverse) z
        func x (y:ys) (z:zs) = if y==x then 
            func x ys ([]:(z:zs)) 
        else 
            func x ys ((y:z):zs)

дає:

*Main> split ' ' "this is a test"
["this","is","a","test"]

а може, ти захотів

*Main> splitWithStr  " and " "this and is and a and test"
["this","is","a","test"]

що було б:

splitWithStr :: Eq a => [a] -> [a] -> [[a]]
splitWithStr x y = func x y [[]]
    where
        func x [] z = reverse $ map (reverse) z
        func x (y:ys) (z:zs) = if (take (length x) (y:ys)) == x then
            func x (drop (length x) (y:ys)) ([]:(z:zs))
        else
            func x ys ((y:z):zs)

1
Я шукав вбудований split, зіпсований мовами з добре розвиненими бібліотеками. Але все-таки спасибі
Ерік Вілсон

3
Ви писали це в червні, тому я припускаю, що ви продовжили свою подорож :) Як вправа, намагаючись переписати цю функцію без зворотного чи тривалості, оскільки використання цих функцій спричиняє алгоритмічну складність штрафу, а також перешкоджає застосуванню до нескінченного списку. Веселіться!
Тоні Морріс

5

Я не знаю, як додати коментар до відповіді Стіва, але я хотів би порекомендувати
  документацію щодо бібліотек GHC ,
а там спеціально
  функції Sublist у Data.List

Що набагато краще як довідник, ніж просто читання звіту про звичайну Haskell.

Загалом, складка з правилом про те, коли створити новий підпис, для годування, також має вирішити його.


2

На додаток до ефективних та заздалегідь вбудованих функцій, що даються у відповідях, я додам свої власні, які просто є частиною мого реферату функцій Haskell, про які я писав, щоб самостійно вивчити мову:

-- Correct but inefficient implementation
wordsBy :: String -> Char -> [String]
wordsBy s c = reverse (go s []) where
    go s' ws = case (dropWhile (\c' -> c' == c) s') of
        "" -> ws
        rem -> go ((dropWhile (\c' -> c' /= c) rem)) ((takeWhile (\c' -> c' /= c) rem) : ws)

-- Breaks up by predicate function to allow for more complex conditions (\c -> c == ',' || c == ';')
wordsByF :: String -> (Char -> Bool) -> [String]
wordsByF s f = reverse (go s []) where
    go s' ws = case ((dropWhile (\c' -> f c')) s') of
        "" -> ws
        rem -> go ((dropWhile (\c' -> (f c') == False)) rem) (((takeWhile (\c' -> (f c') == False)) rem) : ws)

Рішення є щонайменше хвостовими рекурсивними, тому вони не матимуть переповнення стека.


2

Приклад у ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]

1
Не використовуйте регулярні вирази для розділення рядків. Дякую.
kirelagin

@kirelagin, чому цей коментар? Я вивчаю Хаскелл, і я хотів би знати раціональне за вашим коментарем.
Енріко Марія Де Анджеліс

@Andrey, чи є причина, чому я навіть не можу запустити перший рядок у своєму ghci?
Енріко Марія Де Анджеліс

1
@EnricoMariaDeAngelis Регулярні вирази є потужним інструментом для зіставлення рядків. Є сенс використовувати їх, коли ви співставляєте щось нетривіальне. Якщо ви просто хочете розділити рядок на щось таке тривіальне, як інший фіксований рядок, абсолютно не потрібно використовувати регулярні вирази - це лише зробить код більш складним і, швидше за все, повільніше.
kirelagin

"Будь ласка, не використовуйте регулярні вирази для розділення рядків." WTF, чому б не ??? Розщеплення рядка регулярним виразом - цілком розумна річ. Є безліч тривіальних випадків, коли рядок потрібно розділити, але роздільник не завжди є однаковим.
Андрій Костер

2

Я вважаю це більш простим для розуміння:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.