Как разбить строку в Haskell?

163

Есть ли стандартный способ разбить строку в Haskell?

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

Я не смог найти его в Google.

Чтобы быть конкретным, я ищу что-то, где split "," "my,comma,separated,list"возвращается ["my","comma","separated","list"].

Эрик Уилсон
источник
21
Мне бы очень хотелось, чтобы такая функция была в будущем выпуске Data.Listили даже Prelude. Это так распространено и неприятно, если не доступно для код-гольфа.
fuz

Ответы:

135

Существует пакет для этого, который называется сплит .

cabal install split

Используйте это так:

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

Он поставляется с множеством других функций для разделения на совпадающие разделители или с несколькими разделителями.

Jonno_FTW
источник
9
Прохладно. Я не знал об этом пакете. Это окончательный раскол пакет , поскольку это дает больше контроля над работой (облицовка пространства в результатах, оставьте разделители в результате, удалить последовательные разделители и т.д ...). Существует так много способов разделения списков, что невозможно иметь одну функцию, которая будет отвечать всем потребностям, вам действительно нужен такой пакет. split
Гави
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список в своем файле cabal, например, если ваш проект называется hello, то в 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]
Эндрю Костер
19

В модуле Text.Regex (часть платформы Haskell) есть функция:

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

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

evilcandybag
источник
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

Работает только для одного символа, но должен легко расширяться.

FUZ
источник
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"]
fp_mora
источник
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.

Как правило, ее следует решить также с помощью правила о том, когда создавать новый подсписок для подачи.

Evi1M4chine
источник
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, почему этот комментарий? Я изучаю Haskell, и я хотел бы знать, что стоит за вашим комментарием.
Энрико Мария Де Анжелис
@ Андрей, есть ли причина, по которой я не могу даже запустить первую строку в моей ghci?
Энрико Мария Де Анжелис
1
@EnricoMariaDeAngelis Регулярные выражения - мощный инструмент для сопоставления строк. Имеет смысл использовать их, когда вы подходите к чему-то нетривиальному. Если вы просто хотите разбить строку на что-то столь же тривиальное, как другая фиксированная строка, нет необходимости использовать регулярные выражения - это только сделает код более сложным и, вероятно, более медленным.
Кирелагин
«Пожалуйста, не используйте регулярные выражения для разделения строк». WTF, почему бы и нет ??? Разделение строки с помощью регулярного выражения - вполне разумная вещь. Существует множество тривиальных случаев, когда необходимо разделить строку, но разделитель не всегда одинаков.
Андрей Костер
2

Я нахожу это проще для понимания:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
MXS
источник