Haskell如何在最后一个出现位置分割字符串

3

在Haskell中,有没有一种方法可以在给定字符的最后一个出现位置上将字符串分割成两个列表?例如,我想在空格上将列表"a b c d e"分割成 ("a b c d", "e") 两部分。
感谢您的回答。


2
breakLastSpace str = (reverse (drop 1 y), reverse x) where (x, y) = break (== ' ') $ reverse str 是一个相对比较简单的实现。 - Alexis King
请注意,您可以使用“words”函数按空格拆分字符串。 - fxvdh
请注意,text具有breakOnEnd :: Text -> Text -> (Text, Text),因此T.breakOnEnd " " "a b c d e"会给出所需的结果("a b c d ","e") - Michael
5个回答

5
我不确定为什么提出的解决方案如此复杂。只需要 一个 两个遍历 即可:
splitLast :: Eq a => a -> [a] -> Either [a] ([a],[a])
splitLast c' = foldr go (Left [])
    where
        go c (Right (f,b)) = Right (c:f,b)
        go c (Left s) | c' == c = Right ([],s)
                      | otherwise = Left (c:s)

请注意,这是完整的并明显表示它的失败。当无法进行拆分(因为指定的字符不在字符串中)时,它会返回一个带有原始列表的Left。否则,它将返回一个带有两个组件的Right

ghci> splitLast ' ' "hello beautiful world"
Right ("hello beautiful","world")
ghci> splitLast ' ' "nospaceshere!"
Left "nospaceshere!"

1
这可能是我见过的最简单的方法,但它实际上执行了两次遍历,一次解构输入,另一次构建输出。因此它比必要的情况更严格,并且在非常长的列表上速度较慢。你可以修复这个问题,但避免内存泄漏的风险可能有点棘手。 - dfeuer
@dfeuer 谢谢您的纠正。我总是忘记结构必须重新构建。您提到的替代方案将是左折叠吗? - Alec
嗯...我之前想的方法行不通。我认为唯一的懒惰方式就是累积一块,等待看看是否应该附加到末尾。 - dfeuer

2

它不够美观,但是它能够正常工作:

import Data.List
f :: Char -> String -> (String, String)
f char str = let n = findIndex (==char) (reverse str) in
                case n of
                  Nothing -> (str, [])
                  Just n  -> splitAt (length str - n -1) str

我是说f 'e' "a b c d e" = ("a b c d ", "e"),但我自己不会删除结尾的空格。

不妨将其改为 a -> [a] -> ([a], [a]),是吧? - Chris Martin
@chris martin 当然,你随意,我只是想通过提供类型签名来清楚地说明如何在这种情况下使用该函数。 - fxvdh

2
我建议使用更多的模式匹配。
import Data.List

splitLast = contract . words
    where contract [] = ("", "")
          contract [x] = (x, "")
          contract [x,y] = (x, y)
          contract (x:y:rest) = contract $ intercalate " " [x,y] : rest  

对于长列表,我们只需将前两个字符串用空格连接起来,然后再尝试较短的列表。一旦长度减少到2,我们就返回这对字符串。
对于没有空格的字符串,(x,"")似乎是一个合理的选择,但我想你可以返回("",x)
对于空字符串,("","")不清楚是否是最佳选择,但它似乎是将返回类型更改为Maybe (String, String)或引发错误的合理替代方案。

1
我可以提出以下解决方案:
splitLast list elem = (reverse $ snd reversedSplit, reverse $ fst reversedSplit)
  where 
  reversedSplit = span (/= elem) $ reverse list

可能不是最快的方法(有两个不必要的反转),但我喜欢它的简单性。

如果你坚持要去掉我们分割的空格,可以选择:

import qualified Data.List as List
splitLast list elem = splitAt (last $ List.elemIndices elem list) list

然而,这个版本假设至少有一个元素与该模式匹配。如果您不喜欢这个假设,代码会稍微变长(但没有双重反转):
import qualified Data.List as List
splitLast list elem = splitAt index list where
    index = if null indices then 0 else last indices
    indices = List.elemIndices elem list

当然,选择在开头分割是任意的,也许在结尾分割更直观,那么你可以简单地将0替换为list长度

1
我的想法是在每个出现的地方分割,然后将初始部分与最后一部分分开。
指出:
import Control.Arrow   -- (&&&)
import Data.List       -- intercalate
import Data.List.Split -- splitOn
breakOnLast :: Eq a => a -> [a] -> ([a], [a])
breakOnLast x = (intercalate x . init &&& last) . splitOn x

无参函数:

liftA2 (.) ((&&& last) . (. init) . intercalate) splitOn

(.) <$> ((&&&) <$> ((.) <$> pure init <*> intercalate) <*> pure last) <*> splitOn

5
我不确定无点版本值不值得提及。 - chepner
需要注意的是,Data.List.Split 是由 split 包提供的。 - chepner
1
我喜欢寻找无参考版本,并且我喜欢它们,所以我分享它们 :) - erisco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接