【问题标题】:Haskell split string on last occurenceHaskell 在最后一次出现时拆分字符串
【发布时间】:2017-02-23 03:15:54
【问题描述】:

有什么方法可以在给定字符的最后一次出现时将 Haskell 中的字符串拆分为 2 个列表? 例如,我想将空间上的列表“a b c d e”拆分为(“a b c d”,“e”)。 谢谢你的回答。

【问题讨论】:

  • breakLastSpace str = (reverse (drop 1 y), reverse x) where (x, y) = break (== ' ') $ reverse str 是一个相对幼稚的实现。
  • 另请注意,您可以通过 words 函数将字符串拆分为空格
  • 请注意,对于它的价值,text 具有breakOnEnd :: Text -> Text -> (Text, Text),因此T.breakOnEnd " " "a b c d e" 给出了所需的结果("a b c d ","e")

标签: list haskell split


【解决方案1】:

我不确定为什么建议的解决方案如此复杂。只需要一次两次遍历

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)

请注意,这是total,清楚地表明它的失败。当无法拆分时(因为指定的字符不在字符串中),它会返回带有原始列表的Left。否则,它会返回包含两个组件的Right

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

【讨论】:

  • 这可能是迄今为止我见过的最简单的,但它实际上执行了两次传递,一次解构输入,另一次构造输出。因此它比必要的更严格,并且对于很长的列表来说更慢。您可以解决此问题,但避免内存泄漏的风险可能有点棘手。
  • @dfeuer 感谢您的指正。我总是忘记必须重建结构。你提到的另一种选择是左弃牌?
  • Hrmmm... 我的想法是行不通的。我觉得唯一偷懒的办法就是积累一个chunk,等着看是不是应该附加到最后。
【解决方案2】:

我的想法是在每次出现时进行拆分,然后将初始部分与最后一部分分开。

指出:

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

【讨论】:

  • 我不确定免费版本是否值得一提。
  • 需要说明的是Data.List.Split是由split包提供的。
  • 我喜欢寻找无积分的版本,我喜欢它们,所以我给它们:)
【解决方案3】:

我会选择更多的模式匹配。

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) 之类的合理替代方案。

【讨论】:

    【解决方案4】:

    我可以提出以下解决方案:

    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 替换为 length list

    【讨论】:

      【解决方案5】:

      它不漂亮,但它有效:

      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 -&gt; [a] -&gt; ([a], [a]),不是吗?
      • @chris martin 当然,随心所欲,我只是想通过提供该类型签名来明确如何在这种情况下使用该功能。
      猜你喜欢
      • 2022-01-25
      • 2014-01-12
      • 1970-01-01
      • 2011-01-28
      • 2019-01-06
      • 2014-01-21
      • 2016-04-11
      • 2014-10-08
      • 2017-04-29
      相关资源
      最近更新 更多