I think the generator is too strict. It is assumed that the optimal generator will give as many results as possible with minimal information about the result of the recursive application.
Consider the following line.
concatMap (\x -> map (\ys -> (x:ys)) nextgen) ['a'..'z']
Now let's check what happens if we only know that nextgen
is not an empty list. To illustrate this, we will replace the nextgen
variable nextgen
expression undefined:undefined
. The following equations illustrate the evaluation of the expression in question.
concatMap (\x -> map (\ys -> (x:ys)) (undefined:undefined)) ['a'..'z'] = concat (map (\x -> map (\ys -> (x:ys)) (undefined:undefined)) ['a'..'z']) = concat (map (\ys -> ('a':ys)) (undefined:undefined) : map (\x -> map (\ys -> (x:ys)) (undefined:undefined)) ['b'..'z']) = concat (('a':undefined) : undefined) : map (\x -> map (\ys -> (x:ys)) (undefined:undefined)) ['b'..'z']) = ('a':undefined) : undefined
Your particular application may already refuse many results by comparing the first character of the generated string and the search string. Therefore, we are looking for a generator that gives heads as quickly as possible.
Let's change the roles of static information (list of characters) and a recursive application. We get the following expression.
concatMap (\ys -> map (:ys) ['a'..'z']) nextgen
Now we make the same calculation as before.
concatMap (\ys -> map (:ys) ['a'..'z']) (undefined:undefined) = concat (map (\ys -> map (:ys) ['a'..'z']) (undefined:undefined)) = concat (map (:undefined) ['a'...'z'] : map (\ys -> map (:ys) ['a'..'z']) undefined) = map (:undefined) ['a'...'z'] ++ concat (map (\ys -> map (:ys) ['a'..'z']) undefined
The map (:undefined) ['a'...'z']
application map (:undefined) ['a'...'z']
already provides a list in which all chapters are defined. Thus, the test may already fail for most of these lines, only evaluating the recursive application to handle the normal form.
With this modified implementation, we get the following results.
$ ./brute +RTS -s zabcde 4,165,170,696 bytes allocated in the heap 5,569,320 bytes copied during GC 29,848 bytes maximum residency (5 sample(s)) 26,560 bytes maximum slop 2 MB total memory in use (0 MB lost due to fragmentation)
However, since this change is very specific to the application at hand, it may not apply to your actual application.