import           Control.Monad (guard)
import           Data.List     (intersect, unfoldr, delete, nub, group, sort)
import           Text.Printf   (printf)

type Fraction = (Int, Int)
type Reduction = (Fraction, Fraction, Int)

validIntegers :: [Int] -> [Int]
validIntegers xs = [x | x <- xs, not $ hasZeros x, hasUniqueDigits x]
  where
    hasZeros = elem 0 . digits 10
    hasUniqueDigits n = length ds == length ul
      where
        ds = digits 10 n
        ul = nub ds

possibleFractions :: [Int] -> [Fraction]
possibleFractions = (\ys -> [(n,d) | n <- ys, d <- ys, n < d, gcd n d /= 1]) . validIntegers

digits :: Integral a => a -> a -> [a]
digits b = unfoldr (\n -> guard (n /= 0) >> pure (n `mod` b, n `div` b))

digitsToIntegral :: Integral a => [a] -> a
digitsToIntegral = sum . zipWith (*) (iterate (*10) 1)

findReductions :: Fraction -> [Reduction]
findReductions z@(n1, d1) = [ (z, (n2, d2), x)
                              | x <- digits 10 n1 `intersect` digits 10 d1,
                                let n2 = dropDigit x n1
                                    d2 = dropDigit x d1
                                    decimalWithDrop = realToFrac n2 / realToFrac d2,
                                decimalWithDrop == decimal ]
   where dropDigit d = digitsToIntegral . delete d . digits 10
         decimal = realToFrac n1 / realToFrac d1

findGroupReductions :: [Int] -> [Reduction]
findGroupReductions = (findReductions =<<) . possibleFractions

showReduction :: Reduction -> IO ()
showReduction ((n1,d1),(n2,d2),d) = printf "%d/%d =%d/%d by dropping%d\n" n1 d1 n2 d2 d

showCount :: [Reduction] -> Int -> IO ()
showCount xs n = do
  printf "There are%d%d-digit fractions of which:\n" (length xs) n
  mapM_ (uncurry (printf "%5d have%d's omitted\n")) (countReductions xs) >> printf "\n"
  where
    countReductions = fmap ((,) . length <*> head) . group . sort . fmap (\(_, _, x) -> x)

main :: IO ()
main = do
  mapM_ (\g -> mapM_ showReduction (take 12 g) >> printf "\n") groups
  mapM_ (uncurry showCount) $ zip groups [2..]
  where
    groups = [ findGroupReductions [10^1..99],   findGroupReductions [10^2..999]
             , findGroupReductions [10^3..9999], findGroupReductions [10^4..99999] ]