Haskell generating all combinations of n numbers

前端 未结 3 1390
深忆病人
深忆病人 2021-02-15 17:38

I am trying to generate all possible combinations of n numbers. For example if n = 3 I would want the following combinations:

(0,0,0), (0,0,1), (0,0,2)... (0,0,9         


        
3条回答
  •  萌比男神i
    2021-02-15 18:28

    What are all the combinations of three digits? Let's write a few out manually.

    000, 001, 002 ... 009, 010, 011 ... 099, 100, 101 ... 998, 999
    

    We ended up simply counting! We enumerated all the numbers between 0 and 999. For an arbitrary number of digits this generalises straightforwardly: the upper limit is 10^n (exclusive), where n is the number of digits.

    Numbers are designed this way on purpose. It would be jolly strange if there was a possible combination of three digits which wasn't a valid number, or if there was a three-digit number which couldn't be expressed by combining three digits!

    This suggests a simple plan to me, which just involves arithmetic and doesn't require a deep understanding of Haskell*:

    1. Generate a list of numbers between 0 and 10^n
    2. Turn each number into a list of digits.

    Step 2 is the fun part. To extract the digits (in base 10) of a three-digit number, you do this:

    1. Take the quotient and remainder of your number with respect to 100. The quotient is the first digit of the number.
    2. Take the remainder from step 1 and take its quotient and remainder with respect to 10. The quotient is the second digit.
    3. The remainder from step 2 was the third digit. This is the same as taking the quotient with respect to 1.

    For an n-digit number, we take the quotient n times, starting with 10^(n-1) and ending with 1. Each time, we use the remainder from the last step as the input to the next step. This suggests that our function to turn a number into a list of digits should be implemented as a fold: we'll thread the remainder through the operation and build a list as we go. (I'll leave it to you to figure out how this algorithm changes if you're not in base 10!)


    Now let's implement that idea. We want calculate a specified number of digits, zero-padding when necessary, of a given number. What should the type of digits be?

    digits :: Int -> Int -> [Int]
    

    Hmm, it takes in a number of digits and an integer, and produces a list of integers representing the digits of the input integer. The list will contain single-digit integers, each one of which will be one digit of the input number.

    digits numberOfDigits theNumber = reverse $ fst $ foldr step ([], theNumber) powersOfTen
        where step exponent (digits, remainder) =
                  let (digit, newRemainder) = remainder `divMod` exponent
                  in (digit : digits, newRemainder)
              powersOfTen = [10^n | n <- [0..(numberOfDigits-1)]]
    

    What's striking to me is that this code looks quite similar to my English description of the arithmetic we wanted to perform. We generate a powers-of-ten table by exponentiating numbers from 0 upwards. Then we fold that table back up; at each step we put the quotient on the list of digits and send the remainder to the next step. We have to reverse the output list at the end because of the right-to-left way it got built.

    By the way, the pattern of generating a list, transforming it, and then folding it back up is an idiomatic thing to do in Haskell. It's even got its own high-falutin' mathsy name, hylomorphism. GHC knows about this pattern too and can compile it into a tight loop, optimising away the very existence of the list you're working with.

    Let's test it!

    ghci> digits 3 123
    [1, 2, 3]
    ghci> digits 5 10101
    [1, 0, 1, 0, 1]
    ghci> digits 6 99
    [0, 0, 0, 0, 9, 9]
    

    It works like a charm! (Well, it misbehaves when numberOfDigits is too small for theNumber, but never mind about that.) Now we just have to generate a counting list of numbers on which to use digits.

    combinationsOfDigits :: Int -> [[Int]]
    combinationsOfDigits numberOfDigits = map (digits numberOfDigits) [0..(10^numberOfDigits)-1]
    

    ... and we've finished!

    ghci> combinationsOfDigits 2
    [[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],[0,9],[1,0],[1,1] ... [9,7],[9,8],[9,9]]
    

    * For a version which does require a deep understanding of Haskell, see my other answer.

提交回复
热议问题