How to generate a list of all possible strings from shortest to longest

前端 未结 4 1132
滥情空心
滥情空心 2021-02-09 05:03

I need to generate an infinite list of strings using numbers and letters. The first string should simply be \"a\", then \"b\" through to \"z\", then \"0\" through to \"9\", the

4条回答
  •  误落风尘
    2021-02-09 05:39

    So, suppose we already had this list of all possible strings:

     allStrings :: [String]
     allStrings = ...
    

    Given allStrings, how could we create another list of all possible strings?

     alsoAllStrings :: [String]
    

    Let's look at each possible string as a single character prefix, and a string suffix

     alsoAllStrings = [ c : s 
    

    The string suffix is either the empty string or a member of the list of all possible strings.

                      | s <- "" : allStrings
    

    The single character prefix is in 'a' thru 'z' or '0' thru '9'.

                      , c <- ['a'..'z'] ++ ['0'..'9']
                      ]
    

    (this is using list comprehensions - we could also do the same using concatMap:

    alsoAllStrings = concatMap (\s -> map (:s) $ ['a'..'z'] ++ ['0'..'9']) $ "" : allStrings
    

    )

    Now let's go back to the original question. How do we find allStrings?

    In most languages, we couldn't - it's an infinite list of strings, the program would never finish. But since Haskell is lazy, it's cool with generating only as much of allStrings as we actually use.

    One surprising thing that this lets us do is we can define allStrings in terms of alsoAllStrings.

     allStrings = alsoAllStrings
    

    Or, more likely, in terms of itself.

     allStrings = [ c : s | s <- "" : allStrings, c <- ['a'..'z'] ++ ['0'..'9'] ]
    

    This is called corecursive data - data that is defined in terms of itself. Try it out in ghci:

     ghci> let allStrings = [ c : s | s <- "": allStrings, c <- ['a'..'z'] ++ ['0'..'9'] ]
     ghci> take 100 allStrings
     ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","aa","ba","ca","da","ea","fa","ga","ha","ia","ja","ka","la","ma","na","oa","pa","qa","ra","sa","ta","ua","va","wa","xa","ya","za","0a","1a","2a","3a","4a","5a","6a","7a","8a","9a","ab","bb","cb","db","eb","fb","gb","hb","ib","jb","kb","lb","mb","nb","ob","pb","qb","rb","sb","tb","ub","vb","wb","xb","yb","zb","0b","1b"]
    

    The reason it works (and doesn't just loop infinitely) is that it defines part of itself before it uses itself. The first elements we include in allStrings are the single character extensions to the empty string "". So by the time we start iterating through the elements of allStrings to use as suffixes, the first couple elements allStrings are already defined and available. And the more suffixes we process, the more elements of allStrings are defined and available to use as suffixes.

    It's very similar to a common haskellism of defining the fibonacci numbers corecursively:

     fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
    

    Don't be surprised if corecursion takes a while to wrap your head around. It's worth the effort to understand, though.

提交回复
热议问题