Humble numbers are positive integers which have   no   prime factors    >    7.



Humble numbers are also called   7-smooth numbers,   and sometimes called   highly composite,

although this conflicts with another meaning of   highly composite numbers.



Another way to express the above is:

  humble  =  2i × 3j × 5k × 7m 

           where     i, j, k, m ≥ 0 



Task

   show the first   50   humble numbers   (in a horizontal list)
   show the number of humble numbers that have   x   decimal digits for all   x's   up to   n   (inclusive).
   show   (as many as feasible or reasonable for above)   on separate lines
   show all output here on this page


Related tasks

   Hamming numbers


References

   Wikipedia: Smooth numbers, see the 2nd paragraph.
   OEIS A002473: humble numbers
   University of Ulm, The first 5842 terms of humble numbers


(() => {
    'use strict';
 
    // ------------------ HUMBLE NUMBERS -------------------
 
    // humbles :: () -> [Int]
    function* humbles() {
        // A non-finite stream of Humble numbers.
        // OEIS A002473
        const hs = new Set([1]);
        while (true) {
            let nxt = Math.min(...hs)
            yield nxt;
            hs.delete(nxt);
            [2, 3, 5, 7].forEach(
                x => hs.add(nxt * x)
            );
        }
    };
 
    // ----------------------- TEST ------------------------
    // main :: IO ()
    const main = () => {
        console.log('First 50 humble numbers:\n')
        chunksOf(10)(take(50)(humbles())).forEach(
            row => console.log(
                concat(row.map(compose(justifyRight(4)(' '), str)))
            )
        );
        console.log(
            '\nCounts of humble numbers of given digit lengths:'
        );
        const
            counts = map(length)(
                group(takeWhileGen(x => 11 > x)(
                    fmapGen(x => str(x).length)(
                        humbles()
                    )
                ))
            );
        console.log(
            fTable('\n')(str)(str)(
                i => counts[i - 1]
            )(enumFromTo(1)(10))
        );
    };
 
 
    // ----------------- GENERIC FUNCTIONS -----------------
 
    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n =>
        xs => enumFromThenTo(0)(n)(
            xs.length - 1
        ).reduce(
            (a, i) => a.concat([xs.slice(i, (n + i))]),
            []
        );
 
 
    // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
    const compose = (...fs) =>
        // A function defined by the right-to-left
        // composition of all the functions in fs.
        fs.reduce(
            (f, g) => x => f(g(x)),
            x => x
        );
 
 
    // concat :: [[a]] -> [a]
    // concat :: [String] -> String
    const concat = xs =>
        0 < xs.length ? (() => {
            const unit = 'string' !== typeof xs[0] ? (
                []
            ) : '';
            return unit.concat.apply(unit, xs);
        })() : [];
 
 
    // enumFromThenTo :: Int -> Int -> Int -> [Int]
    const enumFromThenTo = x1 =>
        x2 => y => {
            const d = x2 - x1;
            return Array.from({
                length: Math.floor(y - x2) / d + 2
            }, (_, i) => x1 + (d * i));
        };
 
 
    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = m =>
        n => Array.from({
            length: 1 + n - m
        }, (_, i) => m + i);
 
 
    // fTable :: String -> (a -> String) -> (b -> String)
    //                      -> (a -> b) -> [a] -> String
    const fTable = s => xShow => fxShow => f => xs => {
        // Heading -> x display function ->
        //           fx display function ->
        //    f -> values -> tabular string
        const
            ys = xs.map(xShow),
            w = Math.max(...ys.map(length));
        return s + '\n' + zipWith(
            a => b => a.padStart(w, ' ') + ' -> ' + b
        )(ys)(
            xs.map(x => fxShow(f(x)))
        ).join('\n');
    };
 
 
    // fmapGen <$> :: (a -> b) -> Gen [a] -> Gen [b]
    const fmapGen = f =>
        function*(gen) {
            let v = take(1)(gen);
            while (0 < v.length) {
                yield(f(v[0]))
                v = take(1)(gen)
            }
        };
 
 
    // group :: Eq a => [a] -> [[a]]
    const group = xs => {
        // A list of lists, each containing only equal elements,
        // such that the concatenation of these lists is xs.
        const go = xs =>
            0 < xs.length ? (() => {
                const
                    h = xs[0],
                    i = xs.findIndex(x => h !== x);
                return i !== -1 ? (
                    [xs.slice(0, i)].concat(go(xs.slice(i)))
                ) : [xs];
            })() : [];
        const v = go(list(xs));
        return 'string' === typeof xs ? (
            v.map(x => x.join(''))
        ) : v;
    };
 
 
    // justifyRight :: Int -> Char -> String -> String
    const justifyRight = n =>
        // The string s, preceded by enough padding (with
        // the character c) to reach the string length n.
        c => s => n > s.length ? (
            s.padStart(n, c)
        ) : s;
 
 
    // length :: [a] -> Int
    const length = xs =>
        // Returns Infinity over objects without finite length.
        // This enables zip and zipWith to choose the shorter
        // argument when one is non-finite, like cycle, repeat etc
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;
 
 
    // list :: StringOrArrayLike b => b -> [a]
    const list = xs =>
        // xs itself, if it is an Array,
        // or an Array derived from xs.
        Array.isArray(xs) ? (
            xs
        ) : Array.from(xs);
 
 
    // map :: (a -> b) -> [a] -> [b]
    const map = f =>
        // The list obtained by applying f
        // to each element of xs.
        // (The image of xs under f).
        xs => [...xs].map(f);
 
 
    // str :: a -> String
    const str = x =>
        x.toString();
 
 
    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = n => xs =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));
 
 
    // takeWhileGen :: (a -> Bool) -> Gen [a] -> [a]
    const takeWhileGen = p => xs => {
        const ys = [];
        let
            nxt = xs.next(),
            v = nxt.value;
        while (!nxt.done && p(v)) {
            ys.push(v);
            nxt = xs.next();
            v = nxt.value
        }
        return ys;
    };
 
 
    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f =>
        // Use of `take` and `length` here allows zipping with non-finite lists
        // i.e. generators like cycle, repeat, iterate.
        xs => ys => {
            const n = Math.min(length(xs), length(ys));
            return Infinity > n ? (
                (([as, bs]) => Array.from({
                    length: n
                }, (_, i) => f(as[i])(
                    bs[i]
                )))([xs, ys].map(
                    compose(take(n), list)
                ))
            ) : zipWithGen(f)(xs)(ys);
        };
 
    // MAIN ---
    return main();
})();