Bagaimana cara membagi string di Haskell?

163

Apakah ada cara standar untuk membagi string di Haskell?

linesdan wordsbekerja dengan baik dari pemisahan pada ruang atau baris baru, tetapi pasti ada cara standar untuk membagi pada koma?

Saya tidak dapat menemukannya di Hoogle.

Untuk lebih spesifik, saya mencari sesuatu di mana split "," "my,comma,separated,list"kembali ["my","comma","separated","list"].

Eric Wilson
sumber
21
Saya benar-benar ingin fungsi seperti itu di rilis masa depan Data.Listatau bahkan Prelude. Ini sangat umum dan tidak menyenangkan jika tidak tersedia untuk kode-golf.
fuz

Jawaban:

135

Ada paket untuk split yang disebut ini .

cabal install split

Gunakan seperti ini:

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

Muncul dengan banyak fungsi lain untuk membelah pada pembatas yang cocok atau memiliki beberapa pembatas.

Jonno_FTW
sumber
9
Keren. Saya tidak mengetahui paket ini. Ini adalah yang paket perpecahan utama karena memberikan banyak kontrol atas operasi (ruang langsing dalam hasil, pemisah cuti dalam hasil, menghapus pemisah berturut-turut, dll ...). Ada begitu banyak cara pemecahan daftar, tidak mungkin memiliki splitfungsi tunggal yang akan menjawab setiap kebutuhan, Anda benar-benar membutuhkan paket semacam itu.
gawi
1
jika tidak, jika paket eksternal dapat diterima, MissingH juga menyediakan fungsi pemisahan: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Paket itu juga menyediakan banyak fungsi "yang bagus untuk dimiliki" lainnya dan saya menemukan bahwa beberapa paket bergantung padanya.
Emmanuel Touzery
41
Paket split sekarang terpisah dari platform haskell pada rilis terbaru.
Internet
14
impor Data.List.Split (splitOn) dan pergi ke kota. splitOn :: Persamaan a => [a] -> [a] -> [[a]]
Internet
1
@RussAbbott paket perpecahan termasuk dalam Platform Haskell ketika Anda mengunduhnya ( haskell.org/platform/contents.html ), tetapi itu tidak dimuat secara otomatis saat membangun proyek Anda. Tambahkan splitke build-dependsdaftar di file cabal Anda, mis. Jika proyek Anda dipanggil halo, maka dalam hello.cabalfile di bawah executable hellobaris beri garis seperti `build-depend: base, split` (catat dua spasi indent). Kemudian bangun menggunakan cabal buildperintah. Lih haskell.org/cabal/users-guide/…
expz
164

Ingatlah bahwa Anda dapat mencari definisi fungsi Prelude!

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

Melihat di sana, definisi dari wordsadalah,

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

Jadi, ubah untuk fungsi yang mengambil predikat:

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

Kemudian menyebutnya dengan predikat apa pun yang Anda inginkan!

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

Jika Anda menggunakan Data.Text, ada splitOn:

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

Ini dibangun di Platform Haskell.

Jadi misalnya:

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

atau:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"
Emmanuel Touzery
sumber
1
@RussAbbott mungkin Anda perlu ketergantungan pada textpaket atau menginstalnya. Akan termasuk dalam pertanyaan lain.
Emmanuel Touzery
Tidak dapat mencocokkan jenis 'T. Teks' dengan 'Char' Jenis yang diharapkan: [Char] Jenis sebenarnya: [T.Teks]
Andrew Koster
19

Dalam modul Text.Regex (bagian dari Platform Haskell), ada fungsi:

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

yang membagi string berdasarkan pada ekspresi reguler. API dapat ditemukan di Hackage .

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

Gunakan Data.List.Split, yang menggunakan 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]
antimateri
sumber
14

Coba yang ini:

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

Hanya bekerja untuk satu karakter, tetapi harus mudah diperpanjang.

fuz
sumber
10

Tanpa mengimpor apa pun pengganti langsung dari satu karakter untuk spasi, pemisah target wordsadalah spasi. Sesuatu seperti:

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

atau

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

Anda dapat menjadikan ini sebagai fungsi dengan parameter. Anda dapat menghilangkan parameter -to-match parameter saya yang cocok banyak, seperti di:

 [if elem c ";,.:-+@!$#?" then ' ' else c|c <-"my,comma;separated!list"]
fp_mora
sumber
9
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s

Misalnya

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

Pembatas trailing tunggal akan dijatuhkan:

split ';' "a;bb;ccc;;d;"
> ["a","bb","ccc","","d"]
Frank Meisschaert
sumber
6

Saya mulai belajar Haskell kemarin, jadi koreksi saya jika saya salah tetapi:

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)

memberi:

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

atau mungkin Anda inginkan

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

yang akan menjadi:

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)
Robin Begbie
sumber
1
Saya mencari built-in split, dimanjakan oleh bahasa dengan perpustakaan yang berkembang dengan baik. Tapi Terimakasih.
Eric Wilson
3
Anda menulis ini pada bulan Juni, jadi saya anggap Anda telah pindah dalam perjalanan Anda :) Sebagai latihan, mencoba menulis ulang fungsi ini tanpa terbalik atau panjang karena penggunaan fungsi-fungsi ini menimbulkan penalti kompleksitas algoritmik dan juga mencegah aplikasi ke daftar tanpa batas. Selamat bersenang-senang!
Tony Morris
5

Saya tidak tahu bagaimana cara menambahkan komentar pada jawaban Steve, tetapi saya ingin merekomendasikan
  dokumentasi perpustakaan GHC ,
dan di sana secara khusus
  fungsi Sublist di Data.List

Yang jauh lebih baik sebagai referensi, daripada hanya membaca laporan Haskell biasa.

Secara umum, lipatan dengan aturan kapan membuat sublist baru untuk diumpankan, harus menyelesaikannya juga.

Evi1M4chine
sumber
2

Selain fungsi efisien dan pra-dibangun yang diberikan dalam jawaban saya akan menambahkan sendiri yang hanya bagian dari perbendaharaan fungsi Haskell yang saya tulis untuk mempelajari bahasa pada waktu saya sendiri:

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

Solusi setidaknya rekursif ekor sehingga mereka tidak akan menyebabkan stack overflow.

Irfan Hamid
sumber
2

Contoh dalam ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andrey
sumber
1
Tolong, jangan gunakan ekspresi reguler untuk membagi string. Terima kasih.
kirelagin
@kirelagin, mengapa komentar ini? Saya belajar Haskell, dan saya ingin tahu alasan di balik komentar Anda.
Enrico Maria De Angelis
@ Andrew, apakah ada alasan mengapa saya bahkan tidak bisa menjalankan baris pertama di saya ghci?
Enrico Maria De Angelis
1
@EnricoMariaDeAngelis Ekspresi reguler adalah alat yang ampuh untuk pencocokan string. Masuk akal untuk menggunakannya saat Anda mencocokkan sesuatu yang tidak sepele. Jika Anda hanya ingin membagi string pada sesuatu yang sepele seperti string tetap lainnya, sama sekali tidak perlu menggunakan ekspresi reguler - itu hanya akan membuat kode lebih kompleks dan, sepertinya, lebih lambat.
Kirelagin
"Tolong, jangan gunakan ekspresi reguler untuk membagi string." WTF, mengapa tidak ??? Memisahkan string dengan ekspresi reguler adalah hal yang sangat masuk akal untuk dilakukan. Ada banyak kasus sepele di mana sebuah string perlu dipisah tetapi pembatasnya tidak selalu persis sama.
Andrew Koster
2

Saya menemukan ini lebih mudah untuk dipahami:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
mxs
sumber