如何根据长度将[String]拆分为[[String]]

时间:2015-08-23 12:27:21

标签: haskell functional-programming

我正在尝试将字符串列表拆分为字符串列表列表 就像在标题nextSibling

中一样

这必须根据字符的长度来完成,这样输出中的列表不会超过10.因此,如果输入的长度为20,则会将其分解为2个列表,如果长度为21到3个列表

我不知道该怎么做才能做到这一点,我甚至不知道如何将一个列表列入一个列表,而不是基于一定长度的列表。

例如,如果限制为<script> function editMediaInfo(mediaID){ var x = document.getElementById(mediaID); alert(x.nextSibling.id); } </script> 且输入为:

[String] -> [[String]]

输出结果为:

5

我想指出正确的方向和使用的方法,列表理解?递归?

4 个答案:

答案 0 :(得分:4)

这是一个直观的解决方案:

import Data.List (foldl')

breakup :: Int -> [[a]] -> [[[a]]]
breakup size = foldl' accumulate [[]]
  where accumulate broken l
         | length l > size = error "Breakup size too small."
         | sum (map length (last broken ++ [l])) <= size
               = init broken ++ [last broken ++ [l]]
         | otherwise = broken ++ [[l]]

现在,让我们逐行完成:

breakup :: Int -> [[a]] -> [[[a]]]

由于您暗示您可能希望将函数概括为接受不同的大小限制,因此我们的类型签名反映了这一点。我们还推广了[String](即[[Char]]),因为我们的问题并非针对[[Char]],并且可能同样适用于任何[[a]]

breakup size = foldl' accumulate [[]]

我们正在使用左侧折叠,因为我们希望将一个从左到右的列表转换为我们的目标,这将是一个子列表列表。即使我们不关心效率,我们仍然使用Data.List.foldl'而不是Prelude自己的foldl,因为这是标准做法。您可以详细了解foldlfoldl' here

我们的折叠功能称为accumulate。它将考虑一个新项目并决定是将其放在最后创建的子列表中还是开始一个新的子列表。为了做出判断,它使用我们传入的size。我们从初始值[[]]开始,即带有一个空子列表的列表。

现在的问题是,你应该如何accumulate你的目标?

  where accumulate broken l

到目前为止,我们使用broken来引用我们构建的目标,并l(对于&#34; list&#34;)来引用要处理的下一个项目。我们会针对不同情况使用警卫:

         | length l > size = error "Breakup size too small."

如果项目超出了自身的大小限制,我们需要引发错误,因为无法将其放在满足大小限制的子列表中。 (或者,我们可以通过将我们的返回值包装在Maybe monad中来构建一个安全的函数,这是你应该自己尝试的东西。)

         | sum (map length (last broken ++ [l])) <= size
               = init broken ++ [last broken ++ [l]]

警卫条件为sum (map length (last broken ++ [l])) <= size,此警卫的返回值为init broken ++ [last broken ++ [l]]。翻译成简单的英语,我们可能会说,&#34;如果项目可以放在最后一个子列表中而不超过大小限制,请将其附加到那里。&#34;

         | otherwise = broken ++ [[l]]

另一方面,如果没有足够的&#34;房间&#34;在此项目的最后一个子列表中,我们启动一个新的子列表,仅包含此项目。当accumulate帮助器应用于输入列表中的下一个项目时,它将决定是将该项目放在子列表中还是启动另一个子列表,逻辑。

你有它。别忘了import Data.List (foldl')在顶部。另一个答案指出,如果您计划处理100,000个字符串,这不是一个高性能的解决方案。但是,我相信这个解决方案更容易阅读和理解。在许多情况下,可读性是更重要的优化。

感谢有趣的问题。祝Haskell好运,编码愉快!

答案 1 :(得分:3)

您可以这样做:

*Main> splitByLen 5 ["abc","cd","abcd","ab"]
[["abc","cd"],["abcd"],["ab"]]

然后:

n

如果字符串长于breakup,则此函数将失败。现在,在这些情况下你想要做什么取决于你的要求,而你的问题没有说明。

<强> [更新]

根据@ amar47shah的要求,我做了一个基准比较他的解决方案(splitByLen)和我的(import Data.List import Data.Time.Clock import Control.DeepSeq import System.Random main :: IO () main = do s <- mapM (\ _ -> randomString 10) [1..10000] test "breakup 10000" $ breakup 10 s test "splitByLen 10000" $ splitByLen 10 s putStrLn "" r <- mapM (\ _ -> randomString 10) [1..100000] test "breakup 100000" $ breakup 10 r test "splitByLen 100000" $ splitByLen 10 r test :: (NFData a) => String -> a -> IO () test s a = do time1 <- getCurrentTime time2 <- a `deepseq` getCurrentTime putStrLn $ s ++ ": " ++ show (diffUTCTime time2 time1) randomString :: Int -> IO String randomString n = do l <- randomRIO (1,n) mapM (\ _ -> randomRIO ('a', 'z')) [1..l] ):

breakup    10000: 0.904012s
splitByLen 10000: 0.005966s

breakup    100000: 150.945322s
splitByLen 100000: 0.058658s

结果如下:

{{1}}

答案 2 :(得分:0)

这是另一种方法。从问题中可以清楚地看出,结果是一个列表列表,我们需要一个运行长度和一个内部列表来跟踪我们积累了多少(我们使用foldl'这两个作为输入)。然后我们基本上描述了我们想要的东西:

  1. 如果当前输入字符串本身的长度超过输入长度,我们将忽略该字符串(如果您想要不同的行为,可以更改此字符串。)
  2. 如果我们在添加当前字符串的长度之后的新长度在我们的输入长度内,我们将其添加到当前结果列表。
  3. 如果新长度超过输入长度,我们将结果添加到输出并开始新的结果列表。
  4. chunks len = reverse  . map reverse . snd . foldl' f (0, [[]]) where
      f (resSoFar@(lenSoFar, (currRes: acc)) curr
        | currLength > len = resSoFar -- ignore
        | newLen <= len    = (newLen, (curr: currRes):acc)
        | otherwise        = (currLength, [curr]:currRes:acc) 
        where
          newLen = lenSoFar + currLength
          currLength = length curr
    

    每次我们将结果添加到输出列表时,我们都会将其添加到前面,因此我们最后需要reverse . map reverse

    > chunks 5 ["abc","cd","abcd","ab"]
    [["abc","cd"],["abcd"],["ab"]]
    
    > chunks 5 ["abc","cd","abcdef","ab"]
    [["abc","cd"],["ab"]]
    

答案 3 :(得分:-1)

这是一种基本方法。首先,类型String并不重要,因此我们可以根据一般类型a来定义我们的函数:

breakup :: [a] -> [[a]]

我将以3的限制而不是10来说明。如果用另一个限制来实现它,那将是显而易见的。

第一个模式将处理大小为&gt; = 3的列表,第二个模式处理所有其他情况:

breakup (a1 : a2 : a3 : as) = [a1, a2, a3] : breakup as
breakup as = [ as ]

按此顺序设置模式非常重要。这样第二种模式只会在第一种模式不匹配时使用,即当列表中的元素少于3时。

在某些输入上运行此操作的示例:

breakup [1..5]       -> [ [1,2,3], [4,5] ]
breakup [1..4]       -> [ [1,2,3], [4] ]
breakup [1..2]       -> [ [1,2] ]
breakup [1..3]       -> [ [1,2,3], [] ]

当我们在[]上运行该功能时,我们发现这些是额外的[1..3]。幸运的是,通过在最后一个规则之前插入另一个规则很容易解决:

breakup [] = []

完整的定义是:

breakup :: [a] -> [[a]]
breakup [] = []
breakup (a1 : a2 : a3 : as) = [a1, a2, a3] : breakup as
breakup as = [ as ]