私は、コンマで区切られた整数の文字列を受け取り、それを整数のリストに変換し、各数値を1ずつインクリメントするプログラムをHaskellで作成しようとしました。
例:"1,2,-5,-23,15" -> [2,3,-4,-22,16]
以下は結果のプログラムです
import Data.List
main :: IO ()
main = do
n <- return 1
putStrLn . show . map (+1) . map toInt . splitByDelimiter delimiter
$ getList n
getList :: Int -> String
getList n = foldr (++) [] . intersperse [delimiter] $ replicate n inputStr
delimiter = ','
inputStr = "1,2,-5,-23,15"
splitByDelimiter :: Char -> String -> [String]
splitByDelimiter _ "" = []
splitByDelimiter delimiter list =
map (takeWhile (/= delimiter) . tail)
(filter (isPrefixOf [delimiter])
(tails
(delimiter : list)))
toInt :: String -> Int
toInt = read
私にとって最も難しい部分は、文字列を受け取り、文字列のリストを返す関数splitByDelimiter
のプログラミングでした。
"1,2,-5,-23,15" -> ["1","2","-5","-23","15"]
それは機能しているとは思いますが、私はそれが書かれている方法に満足していません。かっこがたくさんあるので、LISPのように見えます。また、アルゴリズムはやや人工的です。
文字列の先頭に区切り文字を付加する",1,2,-5,-23,15"
すべてのテールのリストを生成する[",1,2,-5,-23,15", "1,2,-5,-23,15", ",2,-5,-23,15", .... ]
区切り文字[",1,2,-5,-23,15", ",2,-5,-23,15", .... ]
で始まる文字列のみをフィルタリングして残します
最初の区切り文字を削除し、次の区切り文字が満たされるまで記号を取得します["1", "2", .... ]
したがって、質問は次のとおりです。
関数splitByDelimiter
を改善するにはどうすればよいですか?
区切り文字の先頭とドロップを削除して、文字列を直接分割できますか?
かっこが少なくなるように関数を書き直すにはどうすればよいですか?
何かが足りなくて、この機能を備えた標準機能がすでにあるのでしょうか?
Data.List.Split.splitOn これを行いませんか?
splitBy delimiter = foldr f [[]]
where f c l@(x:xs) | c == delimiter = []:l
| otherwise = (c:x):xs
編集:元の作者によるものではありませんが、これがどのように機能するかを明確にするために、以下はより(過度に?)冗長で柔軟性の低いバージョン(Char
/String
に固有)です。上記のバージョンは、Eq
インスタンスを持つタイプの任意のリストで機能するため使用してください。
splitBy :: Char -> String -> [String]
splitBy _ "" = [];
splitBy delimiterChar inputString = foldr f [""] inputString
where f :: Char -> [String] -> [String]
f currentChar allStrings@(partialString:handledStrings)
| currentChar == delimiterChar = "":allStrings -- start a new partial string at the head of the list of all strings
| otherwise = (currentChar:partialString):handledStrings -- add the current char to the partial string
-- input: "a,b,c"
-- fold steps:
-- first step: 'c' -> [""] -> ["c"]
-- second step: ',' -> ["c"] -> ["","c"]
-- third step: 'b' -> ["","c"] -> ["b","c"]
-- fourth step: ',' -> ["b","c"] -> ["","b","c"]
-- fifth step: 'a' -> ["","b","c"] -> ["a","b","c"]
これはちょっとしたハックですが、まあ、それは機能します。
yourFunc str = map (+1) $ read ("[" ++ str ++ "]")
unfoldr
を使用した非ハッキングバージョンは次のとおりです。
import Data.List
import Control.Arrow(second)
-- break' is like break but removes the
-- delimiter from the rest string
break' d = second (drop 1) . break d
split :: String -> Maybe (String,String)
split [] = Nothing
split xs = Just . break' (==',') $ xs
yourFunc :: String -> [Int]
yourFunc = map ((+1) . read) . unfoldr split
楽しみのために、Parsecを使用して簡単なパーサーを作成する方法を次に示します。
module Main where
import Control.Applicative hiding (many)
import Text.Parsec
import Text.Parsec.String
line :: Parser [Int]
line = number `sepBy` (char ',' *> spaces)
number = read <$> many digit
1つの利点は、受け入れる内容に柔軟性のあるパーサーを簡単に作成できることです。
*Main Text.Parsec Text.Parsec.Token> :load "/home/mikste/programming/Temp.hs"
[1 of 1] Compiling Main ( /home/mikste/programming/Temp.hs, interpreted )
Ok, modules loaded: Main.
*Main Text.Parsec Text.Parsec.Token> parse line "" "1, 2, 3"
Right [1,2,3]
*Main Text.Parsec Text.Parsec.Token> parse line "" "10,2703, 5, 3"
Right [10,2703,5,3]
*Main Text.Parsec Text.Parsec.Token>
これは、小さな変更を加えた元の質問に対するHaskellElephantの回答の適用です。
splitByDelimiter :: Char-> String-> [String]
splitByDelimiter = unfoldr。 splitSingle
splitSingle :: Char-> String-> Maybe(String、String)
splitSingle "1,2,-5,-23,15" -> Just ("1", "2,-5,-23,15")
splitBy del str = helper del str []
where
helper _ [] acc = let acc0 = reverse acc in [acc0]
helper del (x:xs) acc
| x==del = let acc0 = reverse acc in acc0 : helper del xs []
| otherwise = let acc0 = x : acc in helper del xs acc0
import qualified Text.Regex as RegExp
myRegexSplit :: String -> String -> [String]
myRegexSplit regExp theString =
let result = RegExp.splitRegex (RegExp.mkRegex regExp) theString
in filter (not . null) result
-- using regex has the advantage of making it easy to use a regular
-- expression instead of only normal strings as delimiters.
-- the splitRegex function tends to return an array with an empty string
-- as the last element. So the filter takes it out
-- how to use in ghci to split a sentence
let timeParts = myRegexSplit " " "I love ponies a lot"
このコードはうまく機能します:-「Yourstring」[]を分割し、「、」を任意の区切り文字に置き換えます
split [] t = [t]
split (a:l) t = if a==',' then (t:split l []) else split l (t++[a])
輸入のない別のもの:
splitBy :: Char -> String -> [String]
splitBy _ [] = []
splitBy c s =
let
i = (length . takeWhile (/= c)) s
(as, bs) = splitAt i s
in as : splitBy c (if bs == [] then [] else tail bs)