如何计算有效连续编号的数字产品呢? [英] How to calculate the digit products of the consecutive numbers efficiently?

查看:353
本文介绍了如何计算有效连续编号的数字产品呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图来计算的数字序列的每个数字的数字产品,例如:




21,22 ,23 ... 98,99 ..




是:



<块引用>

2,4,6 ... 72,81 ...




要减少复杂性,我将从考虑的数字,例如有限的长度只有[连续数] 001 999 0001 9999



然而,当序列是大的,例如, 10亿,反复的提取数字,然后乘的每一个数字是低效的。



的基本思想是跳过连续零,我们将在计算过程中遇到的,是这样的:



 使用System.Collections.Generic; 
使用System.Linq的;
使用系统;

//注意,位产品不与迭代
给出//我们需要提供计算
公共静态部分类NumericExtensions {
委托公共静态无效NumberIteration(
这个int值,动作< INT,INT []> delg,诠释基数= 10){
VAR位数= DigitIterator(价值,基数).ToArray();
VAR最后= digits.Length-1;
变种emptyArray = INT新[] {};
VAR POW =(Func键< INT,INT,INT>)((X,Y)=>(INT)Math.Pow(X,1 + Y));
无功权重= Enumerable.Repeat(基数,最后1)。选择(POW).ToArray();

为(INT补=基数-1,I =值,J =; I> 0; I-= 1)
如果(I> j)条
delg(我,emptyArray);
,否则如果(0 ==位[0]){
delg(ⅰ,emptyArray);

变种K = 0;

为(; K<最后和放大器;&安培; 0 ==位数[K]; K + = 1)
;

变种Y =(位[K] - = 1);

如果(最后== ||ķ0!= Y){
如果(0 == Y){//暗示最后==ķ
位=新INT [持续];
=最后所1;
}

为(;的K - 大于0;位数[K] =补充)
;
}
,否则{
J =权重[K-1];
}
}
,否则{
//接收数不包含零
delg(I,数字)的数字;

位[0] - = 1;
}

delg(0,emptyArray);
}

静态的IEnumerable< INT> DigitIterator(int值,诠释基数){
如果(-2<&基数功放;&安培;基数2)
基数=基数℃,-2:2;

为(INT余数0 =价值;!){
值= Math.DivRem(价值,基数,淘汰其余);
产量剩余收益;
}
}
}

这是只为枚举号,以避免含有首先要计算,数字产品还未由代码给出零数;但通过提供委托来进行计算仍然需要时间产生的数字产品。



如何计算连续号码有效?


解决方案
<的数字产品p>编辑:从任何地方开始,扩展范围的版本...



这个版本有一个signficantly延伸范围,因此返回 IEnumerable的<长> 代替的IEnumerable< INT> - 乘足够的数字放在一起,你超过 int.MaxValue 。它也上升到10,000,000,000,000,000 - 不太全方位的,但相当大的:)你可以在任何地方你喜欢的启动,它将从那里进行到结束

 类DigitProducts 
{
私人静态只读INT [] =预装CreateFirst10000();

私有静态诠释[] CreateFirst10000()
{
//低效的,但简单,只执行一次。
INT [] =值新INT [10000]
的for(int i = 0; I< 10000;我++)
{
INT产品= 1;
的foreach(在i.ToString VAR数字())
{
产品* =数字-'0';
}
值[I] =产品;
}
返回值;
}

公共静态的IEnumerable<长>的GetProducts(长起点)
{
如果(起点> = || 10000000000000000L起点℃下)
{
抛出新ArgumentOutOfRangeException();
}
INT A =(INT)(起点/ 1000000000000L);
INT B =(INT)((起点%1000000000000L)/ 100000000);
INT C =(INT)((起点%100000000)/ 10000);
INT D =(INT)(起点%10000);

为(; A< 10000; A ++)
{
长aMultiplier =一== 0? 1:预装[A];
为(; B< 10000; b ++)
{
长bMultiplier =一== 0安培;&安培; b == 0? 1
:A = 0&放大器;&安培; B< 1000? 0
:预装[B]。
为(c的LT; 10000; C ++)
{
长cMultiplier =一== 0安培;&安培; b == 0安培;&安培; ç== 0? 1
:将(!一个= 0 || B = 0)及&放大器; C< 1000? 0
:预装[C];

长abcMultiplier = aMultiplier * bMultiplier * cMultiplier;
为(; D< 10000; d +)
{
长dMultiplier =
(A = 0 || B = 0 || C = 0!!!)及&安培; D< 1000? 0
:预灌封并[d];
收益率的回报abcMultiplier * dMultiplier;
}
D = 0;
}
C = 0;
}
B = 0;
}
}
}






编辑:性能分析



我还没看着的详细信息的表现,但我相信在这一点上散工作都只是简单地遍历超过十亿值。一个简单的循环刚刚返回本身需要我的笔记本电脑5秒以上的价值,并遍历数字产品只需要有点过6秒,所以不认为有优化更多的空间 - 如果你想从一开始去。如果你想从不同的位置(有效的)开始,更多的调整是必需的。






好吧,这里是一个尝试这使用迭代器块产生的结果,并预计算第一千结果使事情有点快。



我测试过它高达约1.5亿,它的到目前为止纠正。这只能返回第一个十亿结果 - 如果你需要比这更多,你可以在末尾添加另一块...

 静态的IEnumerable< INT> GetProductDigitsFast()
{
//首先产生第一个1000的值对其进行缓存。
INT [] = productPerThousand新INT [1000];

//截至9
的for(int x = 0; X小于10; X ++)
{
productPerThousand [X] = X;
产量返回X;
}
//高达99
的for(int y = 1; Y小于10; Y ++)
{
的for(int x = 0; X< 10,X ++)
{
productPerThousand [Y * 10 + X] = X * Y;
收益率的回报X * Y;
}
}
//高达999
的for(int x = 1; X小于10; X ++)
{
为(INTÿ = 0; Y小于10; Y ++)
{
为(INT Z = 0; z,其中10; Z ++)
{
INT结果= X * Y * Z ;
productPerThousand [X * 100 + Y * 10 + Z = X * Y * Z;
产量返回结果;
}
}
}

//现在使用的缓存值,其余
的for(int x = 0; X< 1000; X ++ )
{
INT xMultiplier = X == 0? 1:productPerThousand [X];
为(INT Y = 0; Y< 1000,Y ++)
{
//我们已经取得了第一千
如果(X == 0安培;&放; Y == 0)
{
继续;
}
//如果x是非零并且y小于100,我们已
//肯定有一个0,所以结果是0。否则,
//我们只使用productPerThousand。
INT yMultiplier = X == 0 || Ÿ> = 100? productPerThousand [Y]
:0;
INT XY = xMultiplier * yMultiplier;
为(INT Z = 0; z,其中1000; Z ++)
{
如果(z,其中1,100)
{
产量返回0;
}
,否则
{
收益率的回报XY * productPerThousand [Z]。
}
}
}
}
}

我已经通过它与一个令人难以置信的天真的版本,并将结果与​​测试这样的:

 静态的IEnumerable< INT> GetProductDigitsSlow()
{
的for(int i = 0; I< 10亿;我++)
{
INT产品= 1;
的foreach(在i.ToString VAR数字())
{
产品* =数字-'0';
}
收益回报的产品;
}
}



希望这种想法是有些用处......我。不知道如何比较在性能方面此处显示的其他



编辑:稍微扩大这一点,使用简单的循环,我们知道结果会是0 ,我们最终用较少的条件而发愁,但由于某种原因它实际上是稍微慢一些。 (这真让我吃惊)此代码较长,但可能更容易一些遵循

 静态的IEnumerable< INT> GetProductDigitsFast()
{
//首先产生前1000个值对其进行缓存。
INT [] = productPerThousand新INT [1000];

//截至9
的for(int x = 0; X小于10; X ++)
{
productPerThousand [X] = X;
产量返回X;
}
//高达99
的for(int y = 1; Y小于10; Y ++)
{
的for(int x = 0; X< 10,X ++)
{
productPerThousand [Y * 10 + X] = X * Y;
收益率的回报X * Y;
}
}
//高达999
的for(int x = 1; X小于10; X ++)
{
为(INTÿ = 0; Y小于10; Y ++)
{
为(INT Z = 0; z,其中10; Z ++)
{
INT结果= X * Y * Z ;
productPerThousand [X * 100 + Y * 10 + Z = X * Y * Z;
产量返回结果;
}
}
}

//使用缓存值高达999,999
的for(int x = 1; X< 1000; X ++)
{
INT xMultiplier = productPerThousand [X];
为(INT Y = 0; Y< 100; Y ++)
{
产量返回0;
}
为(INT Y = 100; Y< 1000; Y ++)
{
收益率的回报xMultiplier * Y;
}
}

//现在使用的缓存值,其余
的for(int x = 1; X< 1000; X ++)
{
INT xMultiplier = productPerThousand [X];
//在每十亿,前10万的值都将有
// 0第二位,这样我们就可以得到0
为(INT Y = 0; Y< 100 * 1000; Y ++)
{
产量返回0;
}
为(INT Y = 100; Y< 1000,Y ++)
{
INT yMultiplier = productPerThousand [Y];
INT XY = xMultiplier * yMultiplier;
//在每个万辆,前100个值都将有
// 0防penulimate数字,所以我们就可以产生对0
(INT Z = 0; Z < 100; Z ++)
{
产量返回0;
}
为(INT Z = 100; z,其中1000; Z ++)
{
收益率的回报XY * productPerThousand [Z]。
}
}
}
}


I'm trying to calculate the product of digits of each number of a sequence of numbers, for example:

21, 22, 23 ... 98, 99 ..

would be:

2, 4, 6 ... 72, 81 ..

To reduce the complexity, I would consider only the [consecutive numbers] in a limited length of digits, such as from 001 to 999 or from 0001 to 9999.

However, when the sequence is large, for example, 1000000000, repeatedly extract the digits and then multiply for every number would be inefficient.

The basic idea is to skip the consecutive zeros we will encounter during the calculation, something like:

using System.Collections.Generic;
using System.Linq;
using System;

// note the digit product is not given with the iteration
// we would need to provide a delegate for the calculation
public static partial class NumericExtensions {
    public static void NumberIteration(
            this int value, Action<int, int[]> delg, int radix=10) {
        var digits=DigitIterator(value, radix).ToArray();
        var last=digits.Length-1;
        var emptyArray=new int[] { };
        var pow=(Func<int, int, int>)((x, y) => (int)Math.Pow(x, 1+y));
        var weights=Enumerable.Repeat(radix, last-1).Select(pow).ToArray();

        for(int complement=radix-1, i=value, j=i; i>0; i-=1)
            if(i>j)
                delg(i, emptyArray);
            else if(0==digits[0]) {
                delg(i, emptyArray);

                var k=0;

                for(; k<last&&0==digits[k]; k+=1)
                    ;

                var y=(digits[k]-=1);

                if(last==k||0!=y) {
                    if(0==y) { // implied last==k
                        digits=new int[last];
                        last-=1;
                    }

                    for(; k-->0; digits[k]=complement)
                        ;
                }
                else {
                    j=i-weights[k-1];
                }
            }
            else {
                // receives digits of a number which doesn't contain zeros 
                delg(i, digits);

                digits[0]-=1;
            }

        delg(0, emptyArray);
    }

    static IEnumerable<int> DigitIterator(int value, int radix) {
        if(-2<radix&&radix<2)
            radix=radix<0?-2:2;

        for(int remainder; 0!=value; ) {
            value=Math.DivRem(value, radix, out remainder);
            yield return remainder;
        }
    }
}

This is only for the enumeration of numbers, to avoid numbers which contain zeros to be calculated in the first place, the digit products are not yet given by the code; but generate the digit products by providing a delegate to perform the calculation will still take time.

How to calculate the digit products of the consecutive numbers efficiently?

解决方案

EDIT: The "start from anywhere, extended range" version...

This version has a signficantly extended range, and therefore returns an IEnumerable<long> instead of an IEnumerable<int> - multiply enough digits together and you exceed int.MaxValue. It also goes up to 10,000,000,000,000,000 - not quite the full range of long, but pretty big :) You can start anywhere you like, and it will carry on from there to its end.

class DigitProducts
{
    private static readonly int[] Prefilled = CreateFirst10000();

    private static int[] CreateFirst10000()
    {
        // Inefficient but simple, and only executed once.
        int[] values = new int[10000];
        for (int i = 0; i < 10000; i++)
        {
            int product = 1;
            foreach (var digit in i.ToString())
            {
                product *= digit -'0';
            }
            values[i] = product;
        }
        return values;
    }

    public static IEnumerable<long> GetProducts(long startingPoint)
    {
        if (startingPoint >= 10000000000000000L || startingPoint < 0)
        {
            throw new ArgumentOutOfRangeException();
        }
        int a = (int) (startingPoint / 1000000000000L);
        int b = (int) ((startingPoint % 1000000000000L) / 100000000);
        int c = (int) ((startingPoint % 100000000) / 10000);
        int d = (int) (startingPoint % 10000);

        for (; a < 10000; a++)
        {
            long aMultiplier = a == 0 ? 1 : Prefilled[a];
            for (; b < 10000; b++)
            {
                long bMultiplier = a == 0 && b == 0 ? 1
                                 : a != 0 && b < 1000 ? 0
                                 : Prefilled[b];
                for (; c < 10000; c++)
                {
                    long cMultiplier = a == 0 && b == 0 && c == 0 ? 1
                                     : (a != 0 || b != 0) && c < 1000 ? 0
                                     : Prefilled[c];

                    long abcMultiplier = aMultiplier * bMultiplier * cMultiplier;
                    for (; d < 10000; d++)
                    {
                        long dMultiplier = 
                            (a != 0 || b != 0 || c != 0) && d < 1000 ? 0
                            : Prefilled[d];
                        yield return abcMultiplier * dMultiplier;
                    }
                    d = 0;
                }
                c = 0;
            }
            b = 0;
        }
    }
}


EDIT: Performance analysis

I haven't looked at the performance in detail, but I believe at this point the bulk of the work is just simply iterating over a billion values. A simple for loop which just returns the value itself takes over 5 seconds on my laptop, and iterating over the digit products only takes a bit over 6 seconds, so I don't think there's much more room for optimization - if you want to go from the start. If you want to (efficiently) start from a different position, more tweaks are required.


Okay, here's an attempt which uses an iterator block to yield the results, and precomputes the first thousand results to make things a bit quicker.

I've tested it up to about 150 million, and it's correct so far. It only goes returns the first billion results - if you needed more than that, you could add another block at the end...

static IEnumerable<int> GetProductDigitsFast()
{
    // First generate the first 1000 values to cache them.
    int[] productPerThousand = new int[1000];

    // Up to 9
    for (int x = 0; x < 10; x++)
    {
        productPerThousand[x] = x;
        yield return x;
    }
    // Up to 99
    for (int y = 1; y < 10; y++)
    {
        for (int x = 0; x < 10; x++)
        {
            productPerThousand[y * 10 + x] = x * y;
            yield return x * y;
        }
    }
    // Up to 999
    for (int x = 1; x < 10; x++)
    {
        for (int y = 0; y < 10; y++)
        {
            for (int z = 0; z < 10; z++)
            {
                int result = x * y * z;
                productPerThousand[x * 100 + y * 10 + z] = x * y * z;
                yield return result;
            }
        }
    }

    // Now use the cached values for the rest
    for (int x = 0; x < 1000; x++)
    {
        int xMultiplier = x == 0 ? 1 : productPerThousand[x];
        for (int y = 0; y < 1000; y++)
        {
            // We've already yielded the first thousand
            if (x == 0 && y == 0)
            {
                continue;
            }
            // If x is non-zero and y is less than 100, we've
            // definitely got a 0, so the result is 0. Otherwise,
            // we just use the productPerThousand.
            int yMultiplier = x == 0 || y >= 100 ? productPerThousand[y]
                                                 : 0;
            int xy = xMultiplier * yMultiplier;
            for (int z = 0; z < 1000; z++)
            {
                if (z < 100)
                {
                    yield return 0;
                }
                else
                {
                    yield return xy * productPerThousand[z];
                }
            }
        }
    }
}

I've tested this by comparing it with the results of an incredibly naive version:

static IEnumerable<int> GetProductDigitsSlow()
{
    for (int i = 0; i < 1000000000; i++)
    {
        int product = 1;
        foreach (var digit in i.ToString())
        {
            product *= digit -'0';
        }
        yield return product;
    }
}

Hope this idea is of some use... I don't know how it compares to the others shown here in terms of performance.

EDIT: Expanding this slightly, to use simple loops where we know the results will be 0, we end up with fewer conditions to worry about, but for some reason it's actually slightly slower. (This really surprised me.) This code is longer, but possibly a little easier to follow.

static IEnumerable<int> GetProductDigitsFast()
{
    // First generate the first 1000 values to cache them.
    int[] productPerThousand = new int[1000];

    // Up to 9
    for (int x = 0; x < 10; x++)
    {
        productPerThousand[x] = x;
        yield return x;
    }
    // Up to 99
    for (int y = 1; y < 10; y++)
    {
        for (int x = 0; x < 10; x++)
        {
            productPerThousand[y * 10 + x] = x * y;
            yield return x * y;
        }
    }
    // Up to 999
    for (int x = 1; x < 10; x++)
    {
        for (int y = 0; y < 10; y++)
        {
            for (int z = 0; z < 10; z++)
            {
                int result = x * y * z;
                productPerThousand[x * 100 + y * 10 + z] = x * y * z;
                yield return result;
            }
        }
    }

    // Use the cached values up to 999,999
    for (int x = 1; x < 1000; x++)
    {
        int xMultiplier = productPerThousand[x];
        for (int y = 0; y < 100; y++)
        {
            yield return 0;
        }
        for (int y = 100; y < 1000; y++)
        {
            yield return xMultiplier * y;
        }
    }

    // Now use the cached values for the rest
    for (int x = 1; x < 1000; x++)
    {
        int xMultiplier = productPerThousand[x];
        // Within each billion, the first 100,000 values will all have
        // a second digit of 0, so we can just yield 0.
        for (int y = 0; y < 100 * 1000; y++)
        {
            yield return 0;
        }
        for (int y = 100; y < 1000; y++)
        {
            int yMultiplier = productPerThousand[y];
            int xy = xMultiplier * yMultiplier;
            // Within each thousand, the first 100 values will all have
            // an anti-penulimate digit of 0, so we can just yield 0.
            for (int z = 0; z < 100; z++)
            {
                yield return 0;
            }
            for (int z = 100; z < 1000; z++)
            {
                yield return xy * productPerThousand[z];
            }
        }
    }
}

这篇关于如何计算有效连续编号的数字产品呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆