【问题标题】:How do I make a list of substrings?如何制作子字符串列表?
【发布时间】:2015-02-15 03:15:05
【问题描述】:

我正在尝试列出所有子字符串,其中每个子字符串都少一个原始字符串的元素。

例如“1234”将导致 ["1234","123","12","1"]

我想只使用前奏(不导入)来实现这一点,所以不能使用子序列。

我是 Haskell 的新手,我知道我的代码存在一些问题,但目前不知道如何解决它们。

slist :: String -> [String]
slist (x:xs) = (take (length (x:xs)) (x:xs)) ++ slist xs

我怎样才能递归地使用

编辑:希望通过递归使用 init 来实现

【问题讨论】:

  • 所以你想要init . reverse . inits?这需要Data.List,但是你可以去看看源代码来弄清楚如何自己实现它。
  • 另外,hoogle 建议 inits 作为我的第一个结果,相关的 tails 函数位于 #4。
  • 编写这个函数的惰性版本是一个有趣的挑战。

标签: string haskell recursion


【解决方案1】:
slist :: String -> [String]
slist [] = []
-- slist xs = [xs] ++ (slist $ init xs)
slist xs = xs : (slist $ init xs)

main = do 
    print $ slist "1234"

【讨论】:

  • 建议编辑:slist xs = xs : (slist $ init xs)++:
  • 这列出了从 1 开始的所有子字符串,但不是所有子字符串。
【解决方案2】:

这是一个有点复杂的版本:

slist xs = go (zip (repeat xs) [lenxs, lenxs - 1..1])
  where lenxs = length xs
        go [] = []
        go (x:xs) = (take (snd x) (fst x)) : go xs

main = do 
    print $ slist "1234"

【讨论】:

    【解决方案3】:

    这是一个非常懒惰的版本,适合处理无限列表。第一个之后的每个结果列表的每个元素只需要O(1) 摊销时间来计算它,无论我们查看列表有多远。

    总体思路是:对于每个长度n,我们打算将列表拆分为长度为n 的项目队列和列表的其余部分。为了产生结果,我们首先检查列表中是否有另一个项目可以在队列中占有一席之地,然后产生队列中的第一个项目。当我们到达列表的末尾时,我们会从队列中丢弃剩余的项目。

    import Data.Sequence (Seq, empty, fromList, ViewL (..), viewl, (|>))
    
    starts :: [a] -> [[a]]
    starts = map (uncurry shiftThrough) . splits
    
    shiftThrough :: Seq a -> [a] -> [a]
    shiftThrough queue [] = []
    shiftThrough queue (x:xs) = q1:shiftThrough qs xs 
        where
            (q1 :< qs) = viewl (queue |> x)
    

    splits 查找列表的所有初始序列以及尾列表。

    splits :: [a] -> [(Seq a, [a])]
    splits = go empty
        where
            go s []     = []
            go s (x:xs) = (s,x:xs):go (s |> x) xs
    

    我们可以用相同的策略写出从列表末尾删除。

    dropEnd :: Int -> [a] -> [a]
    dropEnd n = uncurry (shiftThrough . fromList) . splitAt n
    

    这些使用Data.Sequence 的摊销O(n) 构造一个序列fromListO(1)|&gt;O(1) 附加到序列的末尾,用viewl 检查序列的开头。

    这足以在几秒钟内快速查询(starts [1..]) !! 80000(starts [1..]) !! 8000000 之类的内容。

    看,没有进口

    队列的一个简单的纯函数实现是一对列表,一个包含要按顺序输出的事物next,另一个包含最近的事物added。每当添加某些内容时,它都会添加到 added 列表的开头。当需要某些东西时,该项目将从next 列表的开头删除。当next 列表中没有剩余要删除的项目时,它会以相反的顺序被added 列表替换,并且added 列表设置为[]。这已经摊销了 O(1) 运行时间,因为每个项目将被添加一次、删除一次和反转一次,但是许多反转将同时发生。

    delay 使用上面描述的队列逻辑来实现与上一节中的shiftThrough 相同的事情。 xs 是最近使用的事物列表 addedys 是要使用的事物列表 next

    delay :: [a] -> [a] -> [a]
    delay ys = traverse step ([],ys)
        where
            step (xs, ys) x = step' (x:xs) ys
            step' xs []     = step' [] (reverse xs)
            step' xs (y:ys) = (y, (xs, ys))
    

    traverse 差点被扫描了

    traverse :: (s -> a -> (b, s)) -> s -> [a] -> [b]
    traverse f = go
        where
            go _ []     = []
            go s (x:xs) = y : go s' xs
                where (y, s') = f s x
    

    我们可以根据delay 和返回列表的splits 的另一个版本来定义starts

    starts :: [a] -> [[a]]
    starts = map (uncurry delay) . splits
    
    splits :: [a] -> [([a], [a])]
    splits = go []
        where
            go s []     = []
            go s (x:xs) = (reverse s, x:xs):go (x:s) xs
    

    这与使用Seq 的实现具有非常相似的性能。

    【讨论】:

    • OP 指定仅使用 Prelude(无导入)。不过,答案很好。
    【解决方案4】:

    更新了列出所有可能子字符串的答案(不仅仅是从根开始)。

    slist :: [t] -> [[t]]
    slist [] = []
    slist xs = xs : (slist $ init xs )  # Taken from Pratik Deoghare's post
    
    
    all_substrings:: [t] -> [[t]]
    all_substrings (x:[]) = [[x]]
    all_substrings (x:xs)  = slist z ++ all_substrings xs
                             where z = x:xs
    
    λ> all_substrings "1234"
    ["1234","123","12","1","234","23","2","34","3","4"]
    

    【讨论】:

      猜你喜欢
      • 2019-02-25
      • 1970-01-01
      • 2015-01-04
      • 1970-01-01
      • 2022-11-13
      • 2023-03-28
      • 1970-01-01
      • 1970-01-01
      • 2011-12-28
      相关资源
      最近更新 更多