2010-12-21 23 views
19

Traté de escribir el programa en Haskell, que tendrá una cadena de números enteros delimitados por comas, convertirlo a la lista de números enteros y el incremento por cada número 1.¿Cuál es la mejor manera de dividir una cadena por un delimitador de manera funcional?

Por ejemplo "1,2,-5,-23,15" -> [2,3,-4,-22,16]

A continuación se el programa resultante

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 

la parte más difícil para mí fue la programación de la función splitByDelimiter que toman una cadena y la lista de regreso de Cuerdas

"1,2,-5,-23,15" -> ["1","2","-5","-23","15"]

Pensé que funcionaba, no estoy contento con la forma en que está escrito. Hay muchos paréntesis, así que luce como Lisp. Asimismo, el algoritmo es algo artificial:

  1. Prepend delimitador de comienzo de la cadena de ",1,2,-5,-23,15"

  2. lista de todas las colas [",1,2,-5,-23,15", "1,2,-5,-23,15", ",2,-5,-23,15", .... ]

  3. Filtrar Generar y dejó sólo cadenas que comienza con delimitador [",1,2,-5,-23,15", ",2,-5,-23,15", .... ]

  4. Suelta el primer delimitador y toma los símbolos hasta que se cumpla el siguiente delimitador ["1", "2", .... ]

Así que las preguntas son:

¿Cómo puedo mejorar la función splitByDelimiter?

¿Puedo eliminar anteponer y soltar el delimitador y hacer una división directa de la cadena?

¿Cómo puedo reescribir la función para que haya menos paréntesis?

¿Puedo perder algo y ya hay una función estándar con esta funcionalidad?

+0

'foldr (++) [] 'también se conoce como' concat', 'putStrLn. show' también se conoce como 'print'. Además, 'n <- return 1' es un poco extraño; simplemente puede hacer 'let n = 1' y evitar envolver y desenvolver la mónada. – pat

+0

posible duplicado de [¿Cómo dividir una cadena en Haskell?] (Http://stackoverflow.com/questions/4978578/how-to-split-a-string-in-haskell) –

Respuesta

29

¿No Data.List.Split.splitOn hace esto?

+6

Considerando que este paquete no es parte del instalación básica (Plataforma Haskell), creo que tiende a pasarse por alto. –

+0

Gracias. Hace exactamente lo que necesito. – sign

11

Esto es un truco, pero diablos, funciona.

yourFunc str = map (+1) $ read ("[" ++ str ++ "]") 

Aquí es una versión no-hack usando 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 
+0

Gracias. Este es un buen punto de vista. Me gusta cómo se usa unfoldr aquí. – sign

+0

Su división es más rápida que splitOn por 43ns en mi comp en ghci :) – CoR

+0

Esta implementación de la función dividida funciona de manera diferente a lo esperado, no divide correctamente cadenas con comas al final, falta uno "". Si desea asegurarse de que una función dividida sea 100% funcional, debería ser reversible intercalando con el mismo delimitador todas las permutaciones de una cadena delimitada, por ej. "a B C". – ljedrz

4

Ésta es la aplicación de la respuesta de HaskellElephant a la pregunta original con cambios menores

 
splitByDelimiter :: Char -> String -> [String] 
splitByDelimiter = unfoldr . splitSingle 

splitSingle :: Char -> String -> Maybe (String,String) 
splitSingle _ [] = Nothing 
splitSingle delimiter xs = 
    let (ys, zs) = break (== delimiter) xs in 
    Just (ys, drop 1 zs) 

Cuando el splitSingle función de división de la lista en dos subcadenas por primer delimitador.

Por ejemplo: "1,2,-5,-23,15" -> Just ("1", "2,-5,-23,15")

7

Sólo por diversión, aquí es cómo se puede crear un programa de análisis sencillo con 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 

Una de las ventajas es que es fácilmente crear un analizador que es flexible en lo que aceptará:

*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> 
+0

Menor, pero podría usar 'many1' como en' number = read <$> many1 digit' para que la entrada no válida como "1,, 2" tenga como resultado un valor Left en lugar de una excepción de Prelude.read. – rob

22
splitBy delimiter = foldr f [[]] 
      where f c [email protected](x:xs) | c == delimiter = []:l 
          | otherwise = (c:x):xs 

Editar: no por el autor original, pero a continuación es una versión más (¿demasiado?) Detallada y menos flexible (específica de Char/String) para ayudar a aclarar cómo funciona esto. Utilice la versión anterior porque funciona en cualquier lista de un tipo con una instancia Eq.

splitBy :: Char -> String -> [String] 
splitBy _ "" = []; 
splitBy delimiterChar inputString = foldr f [""] inputString 
    where f :: Char -> [String] -> [String] 
     f currentChar [email protected](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"] 
+1

Esto es brillante; me llevó demasiado tiempo entender cómo funciona, pero me encanta. – ljedrz

+0

No funciona para cadenas vacías, es decir, se evalúa como '[" "]' en lugar de '[]'. – fotNelton

+1

Estoy de acuerdo con @ljedrz - me llevó mucho tiempo entender, ¡pero es brillante! Espero que no te importe, pero agregué un apéndice menos flexible pero extremadamente detallado a tu respuesta para ayudar a otras personas a entender lo que está sucediendo. –

2
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 
1

Este código funciona bien uso: - split "Su cadena" [] y reemplazar '' con cualquier delimitador

split [] t = [t] 
split (a:l) t = if a==',' then (t:split l []) else split l (t++[a]) 
1
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" 
Cuestiones relacionadas