Làm thế nào để tách một chuỗi trong Haskell?


163

Có một cách tiêu chuẩn để phân chia một chuỗi trong Haskell?

lineswordslàm việc tuyệt vời từ việc phân tách trên một không gian hoặc dòng mới, nhưng chắc chắn có một cách tiêu chuẩn để phân chia trên dấu phẩy?

Tôi không thể tìm thấy nó trên Hoogle.

Để cụ thể, tôi đang tìm kiếm một cái gì đó split "," "my,comma,separated,list"trở lại ["my","comma","separated","list"].


21
Tôi thực sự muốn một chức năng như vậy trong một phiên bản tương lai Data.Listhoặc thậm chí Prelude. Nó rất phổ biến và khó chịu nếu không có sẵn cho môn đánh gôn.
fuz

Câu trả lời:


135

Có một gói cho cái này được gọi là chia .

cabal install split

Sử dụng nó như thế này:

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

Nó đi kèm với rất nhiều chức năng khác để phân tách trên các dấu phân cách phù hợp hoặc có một số dấu phân cách.


9
Mát mẻ. Tôi đã không nhận thức được gói này. Đây là các gói phân chia cuối cùng vì nó mang lại nhiều quyền kiểm soát các hoạt động (không gian trang trí trong kết quả, tách rời khỏi trong kết quả, loại bỏ dải phân cách liên tiếp, vv ...). Có rất nhiều cách để chia danh sách, không thể có splitchức năng duy nhất sẽ trả lời mọi nhu cầu, bạn thực sự cần loại gói đó.
gawi

1
mặt khác, nếu các gói bên ngoài được chấp nhận, MissingH cũng cung cấp chức năng phân tách: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/. Gói đó cũng cung cấp nhiều chức năng "đẹp-có" khác và tôi thấy rằng một số gói phụ thuộc vào nó.
Emmanuel Touzery

41
Gói chia tách bây giờ là ngoài nền tảng haskell như phát hành gần đây nhất.
Internet

14
nhập Data.List.Split (splitOn) và đi đến thị trấn. splitOn :: Eq a => [a] -> [a] -> [[a]]
Internet

1
@RussAbbott gói chia được bao gồm trong Nền tảng Haskell khi bạn tải xuống ( haskell.org/pl platform / content.html ), nhưng nó không được tự động tải khi xây dựng dự án của bạn. Thêm splitvào build-dependsdanh sách trong tệp cabal của bạn, ví dụ: nếu dự án của bạn được gọi là hello, thì trong hello.cabaltệp bên dưới executable hellodòng hãy đặt một dòng như `build-phụ thuộc: cơ sở, split` (lưu ý hai khoảng cách thụt lề). Sau đó xây dựng bằng cách sử dụng cabal buildlệnh. Cf. haskell.org/cabal/users-guide/ Lỗi
expz

164

Hãy nhớ rằng bạn có thể tra cứu định nghĩa của các chức năng Prelude!

http://www.haskell.org/onlinereport/st Chuẩn-prelude.html

Nhìn vào đó, định nghĩa wordslà,

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

Vì vậy, thay đổi nó cho một hàm có một vị ngữ:

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

Sau đó gọi nó với bất kỳ vị ngữ nào bạn muốn!

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

31

Nếu bạn sử dụng Data.Text, có splitOn:

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

Điều này được xây dựng trong Nền tảng Haskell.

Ví dụ:

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

hoặc là:

{-# LANGUAGE OverloadedStrings #-}

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

1
@RussAbbott có lẽ bạn cần phụ thuộc vào textgói hoặc cài đặt nó. Sẽ thuộc về một câu hỏi khác mặc dù.
Emmanuel Touzery

Không thể khớp loại 'T.Text' với 'Char' Loại dự kiến: [Char] Loại thực tế: [T.Text]
Andrew Koster

19

Trong mô-đun Text.Regex (một phần của Nền tảng Haskell), có một chức năng:

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

mà tách một chuỗi dựa trên một biểu thức thông thường. API có thể được tìm thấy tại Hackage .


Could not find module ‘Text.Regex’ Perhaps you meant Text.Read (from base-4.10.1.0)
Andrew Koster

18

Sử dụng Data.List.Split, trong đó sử dụng 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

Hãy thử cái này:

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

Chỉ hoạt động cho một char duy nhất, nhưng nên dễ dàng mở rộng.


10

Không nhập bất cứ thứ gì thay thế thẳng một ký tự cho một khoảng trắng, dấu phân cách đích wordslà một khoảng trắng. Cái gì đó như:

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

hoặc là

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

Bạn có thể biến điều này thành một hàm với các tham số. Bạn có thể loại bỏ nhiều tham số khớp với ký tự của tôi, như trong:

 [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

Ví dụ

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

Một dấu phân cách duy nhất sẽ bị loại bỏ:

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

6

Tôi đã bắt đầu học Haskell ngày hôm qua, vì vậy hãy sửa tôi nếu tôi sai nhưng:

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)

cho:

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

hoặc có thể bạn muốn

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

đó sẽ là:

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
Tôi đang tìm kiếm một tích hợp split, được các ngôn ngữ với các thư viện phát triển tốt. Nhưng dù gì cũng cảm ơn.
Eric Wilson

3
Bạn đã viết điều này vào tháng 6, vì vậy tôi cho rằng bạn đã tiếp tục trong hành trình của mình :) Như một bài tập, cố gắng viết lại hàm này mà không đảo ngược hoặc dài vì sử dụng các hàm này phải chịu một hình phạt phức tạp về thuật toán và cũng ngăn ứng dụng vào danh sách vô hạn. Chúc vui vẻ!
Tony Morris

5

Tôi không biết làm thế nào để thêm một bình luận lên câu trả lời của Steve, nhưng tôi muốn giới thiệu các
  tài liệu GHC thư viện ,
và trong đó đặc biệt là
  chức năng sublist trong Data.List

Đó là một tài liệu tham khảo tốt hơn nhiều so với việc chỉ đọc báo cáo Haskell đơn giản.

Nói chung, một nếp gấp với quy tắc về thời điểm tạo một danh sách con mới để cung cấp, cũng nên giải quyết nó.


2

Ngoài các chức năng được xây dựng sẵn và hiệu quả được đưa ra trong các câu trả lời, tôi sẽ thêm chức năng của riêng mình, đây đơn giản là một phần trong tài năng của tôi về các chức năng Haskell mà tôi đã viết để học ngôn ngữ theo thời gian của riêng tôi:

-- 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)

Các giải pháp ít nhất là đệ quy đuôi để chúng không bị tràn ngăn xếp.


2

Ví dụ trong ghci:

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

1
Xin vui lòng, không sử dụng biểu thức thông thường để phân chia chuỗi. Cảm ơn bạn.
kirelagin

@kirelagin, tại sao nhận xét này? Tôi đang học Haskell, và tôi muốn biết lý do đằng sau bình luận của bạn.
Enrico Maria De Angelis

@Andrey, có lý do tại sao tôi thậm chí không thể chạy dòng đầu tiên trong tôi ghci?
Enrico Maria De Angelis

1
@EnricoMariaDeAngelis Biểu thức chính quy là một công cụ mạnh mẽ để khớp chuỗi. Nó có ý nghĩa để sử dụng chúng khi bạn phù hợp với một cái gì đó không tầm thường. Nếu bạn chỉ muốn tách một chuỗi trên một thứ tầm thường như một chuỗi cố định khác, hoàn toàn không cần sử dụng các biểu thức thông thường - nó sẽ chỉ làm cho mã phức tạp hơn và, có thể, chậm hơn.
kirelagin

"Xin vui lòng, không sử dụng biểu thức thông thường để phân chia chuỗi." WTF, tại sao không ??? Tách một chuỗi với một biểu thức thông thường là một điều hoàn toàn hợp lý để làm. Có rất nhiều trường hợp tầm thường trong đó một chuỗi cần được phân tách nhưng dấu phân cách không phải lúc nào cũng giống hệt nhau.
Andrew Koster

2

Tôi thấy điều này đơn giản hơn để hiểu:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.