Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/256.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何有效地计算连续数字的数字乘积?_C#_Math_Language Agnostic - Fatal编程技术网

C# 如何有效地计算连续数字的数字乘积?

C# 如何有效地计算连续数字的数字乘积?,c#,math,language-agnostic,C#,Math,Language Agnostic,我试图计算数字序列中每个数字的位数乘积,例如: 21、22、23。。。98, 99 .. 将是: 2,4,6。。。72, 81 .. < P >为了减少复杂性,我只考虑有限长度的数字[],如从 001 >代码> 999 > /代码>或从 0001 到 9999 < /代码> . 但是,当序列较大时(例如,100000000),重复提取数字,然后对每个数字进行乘法将效率低下 基本思想是跳过计算过程中遇到的连续零,如: using System.Collections.Generic; usi

我试图计算数字序列中每个数字的位数乘积,例如:

21、22、23。。。98, 99 ..

将是:

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

< P >为了减少复杂性,我只考虑有限长度的数字[],如从<代码> 001 >代码> <代码> 999 > /代码>或从<代码> 0001 <代码>到<代码> 9999 < /代码> . 但是,当序列较大时(例如,
100000000
),重复提取数字,然后对每个数字进行乘法将效率低下

基本思想是跳过计算过程中遇到的连续零,如:

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;
        }
    }
}
使用System.Collections.Generic;
使用System.Linq;
使用制度;
//注:迭代中未给出数字乘积
//我们需要为计算提供一名代表
公共静态部分类NumericExtensions{
公共静态无效数(
此int值,动作delg,int基数=10){
var digits=数字计算器(值,基数).ToArray();
var last=位数。长度-1;
var emptyArray=newint[]{};
var-pow=(Func)((x,y)=>(int)Math.pow(x,1+y));
var-weights=Enumerable.Repeat(基数,last-1)。选择(pow.ToArray();
对于(整数补码=基数-1,i=值,j=i;i>0;i-=1)
如果(i>j)
delg(i,空数组);
如果(0==数字[0]){
delg(i,空数组);
var k=0;
对于(;k0;数字[k]=补码)
;
}
否则{
j=i-权重[k-1];
}
}
否则{
//接收不包含零的数字的位数
delg(i,数字);
数字[0]-=1;
}
delg(0,空数组);
}
静态IEnumerable数字计算器(int值,int基数){

如果(-2I以非常简单的代码结束,如下所示:

  • 代码:

    public delegate void R(
        R delg, int pow, int rdx=10, int prod=1, int msd=0);
    
    R digitProd=
        default(R)!=(digitProd=default(R))?default(R):
        (delg, pow, rdx, prod, msd) => {
            var x=pow>0?rdx:1;
    
            for(var call=(pow>1?digitProd:delg); x-->0; )
                if(msd>0)
                    call(delg, pow-1, rdx, prod*x, msd);
                else
                    call(delg, pow-1, rdx, x, x);
        };
    
    msd
    是最重要的数字,就像二进制一样

我没有选择使用迭代器模式的原因是它比方法调用花费更多的时间

请注意,行
default(R)!=(digitProd=default(R))?default(R):…
仅用于分配
digitProd
,因为在分配代理之前不能使用它。实际上,我们可以将其编写为:

  • 替代语法:

    var digitProd=default(R);
    
    digitProd=
        (delg, pow, rdx, prod, msd) => {
            var x=pow>0?rdx:1;
    
            for(var call=(pow>1?digitProd:delg); x-->0; )
                if(msd>0)
                    call(delg, pow-1, rdx, prod*x, msd);
                else
                    call(delg, pow-1, rdx, x, x);
        };
    
这种实现的缺点是它不能从特定的数字开始,而是从最大的完整数字开始

我有一些简单的想法可以解决这个问题:

  • 递归

    委托(
    Action
    R
    是一个递归委托定义,用于递归算法和接收数字乘积结果的委托

    下面的其他观点解释了递归的原因

  • 无分割

    对于连续的数字,使用除法提取每个数字被认为效率低下,因此我选择以倒计时的方式直接对数字进行递归运算

    例如,对于编号为
    123
    的3位数字,它是从
    999
    开始的3位数字之一:

    98765432[1]0——递归的第一级

    9876543[2]10——递归的第二级

    9877654[3]2110——递归的第三级

  • 不缓存

    我们可以看到这个答案

    建议使用缓存机制,但对于连续数字,我们不建议使用,因为它是缓存

    对于数字
    123、132、213、231、312、321
    ,数字产品是相同的。因此,对于缓存,我们可以减少要存储的项目,这些项目只是具有不同顺序(排列)的相同数字,我们可以将其视为同一密钥

    然而,对数字进行排序也需要时间。使用
    HashSet
    实现的密钥集合,我们需要为更多的项支付更多的存储空间;即使我们减少了项,我们仍然需要花费时间进行相等性比较。似乎没有比使用其值进行相等性比较更好的哈希函数,而这正是结果我们正在计算。例如,除0和1外,两位数的乘法表中只有36个组合

    因此,只要计算效率足够高,我们就可以认为算法本身是一个<强>虚拟缓存< /强>而不必花费一个存储。

  • 减少计算包含零的数字的时间。

    对于连续数字的数字乘积,我们将遇到:

    十分之一

    每100个连续0个

    每1000个连续零

    依此类推。请注意,在
    每100次
    中,每10次
    仍会遇到9个零。可以使用以下代码计算零的计数:

    static int CountOfZeros(int n, int r=10) {
        var powerSeries=n>0?1:0;
    
        for(var i=0; n-->0; ++i) {
            var geometricSeries=(1-Pow(r, 1+n))/(1-r);
            powerSeries+=geometricSeries*Pow(r-1, 1+i);
        }
    
        return powerSeries;
    }
    
    对于
    n
    是数字计数,
    r
    是基数。数字将是a,从a开始计算,
    0
    加1

    例如,4位数,我们将遇到的零是:

    (1) +((1*9)+11)*9+111)*9=(1)+(1*9*9)+(11*9*9)+(111*9)=2620

    对于此实现,我们确实跳过了包含零的数字的计算。而不是真正跳过了包含零的数字的计算。原因是浅层递归的结果与递归实现一起重用,我们可以将其视为缓存。在执行之前,可以检测到并避免尝试与单个零相乘,我们可以把一个零传递给下一级递归
    public static partial class TestClass {
        public delegate void R(
            R delg, int pow, int rdx=10, int prod=1, int msd=0);
    
        public static void TestMethod() {
            var power=9;
            var radix=10;
            var total=Pow(radix, power);
    
            var value=total;
            var count=0;
    
            R doNothing=
                (delg, pow, rdx, prod, msd) => {
                };
    
            R countOnly=
                (delg, pow, rdx, prod, msd) => {
                    if(prod>0)
                        count+=1;
                };
    
            R printProd=
                (delg, pow, rdx, prod, msd) => {
                    value-=1;
                    countOnly(delg, pow, rdx, prod, msd);
                    Console.WriteLine("{0} = {1}", value.ToExpression(), prod);
                };
    
            R digitProd=
                default(R)!=(digitProd=default(R))?default(R):
                (delg, pow, rdx, prod, msd) => {
                    var x=pow>0?rdx:1;
    
                    for(var call=(pow>1?digitProd:delg); x-->0; )
                        if(msd>0)
                            call(delg, pow-1, rdx, prod*x, msd);
                        else
                            call(delg, pow-1, rdx, x, x);
                };
    
            Console.WriteLine("--- start --- ");
    
            var watch=Stopwatch.StartNew();
            digitProd(printProd, power);
            watch.Stop();
    
            Console.WriteLine("  total numbers: {0}", total);
            Console.WriteLine("          zeros: {0}", CountOfZeros(power-1));
    
            if(count>0)
                Console.WriteLine("      non-zeros: {0}", count);
    
            var seconds=(decimal)watch.ElapsedMilliseconds/1000;
            Console.WriteLine("elapsed seconds: {0}", seconds);
            Console.WriteLine("--- end --- ");
        }
    
        static int Pow(int x, int y) {
            return (int)Math.Pow(x, y);
        }
    
        static int CountOfZeros(int n, int r=10) {
            var powerSeries=n>0?1:0;
    
            for(var i=0; n-->0; ++i) {
                var geometricSeries=(1-Pow(r, 1+n))/(1-r);
                powerSeries+=geometricSeries*Pow(r-1, 1+i);
            }
    
            return powerSeries;
        }
    
        static String ToExpression(this int value) {
            return (""+value).Select(x => ""+x).Aggregate((x, y) => x+"*"+y);
        }
    }
    
    n                   n <= 9
    a[n/10] * (n % 10)  n >= 10
    
    a = range(10)
    for i in range(10, 100):
        a.append(a[i / 10] * (i % 10))
    
    int[] GenerateDigitProducts( int max )
    {
        int sweep = 1;
        var results = new int[max+1];
        for( int i = 1; i <= 9; ++i ) results[i] = i;
        // loop invariant: all values up to sweep * 10 are filled in
        while (true) {
            int prior = results[sweep];
            if (prior > 0) {
                for( int j = 1; j <= 9; ++j ) {
                    int k = sweep * 10 + j; // <-- the key, generating number from digits is much faster than decomposing number into digits
                    if (k > max) return results;
                    results[k] = prior * j;
                    // loop invariant: all values up to k are filled in
                }
            }
            ++sweep;
        }
    }
    
    static void VisitDigitProductsImpl(int min, int max, System.Action<int, int> visitor, int build_n, int build_ndp)
    {
        if (build_n >= min && build_n <= max) visitor(build_n, build_ndp);
    
        // bound
        int build_n_min = build_n;
        int build_n_max = build_n;
    
        do {
            build_n_min *= 10;
            build_n_max *= 10;
            build_n_max +=  9;
    
            // prune
            if (build_n_min > max) return;
        } while (build_n_max < min);
    
        int next_n = build_n * 10;
        int next_ndp = 0;
        // branch
        // if you need to visit zeros as well: VisitDigitProductsImpl(min, max, visitor, next_n, next_ndp);
        for( int i = 1; i <= 9; ++i ) {
            next_n++;
            next_ndp += build_ndp;
            VisitDigitProductsImpl(min, max, visitor, next_n, next_ndp);
        }
    
    }
    
    static void VisitDigitProducts(int min, int max, System.Action<int, int> visitor)
    {
        for( int i = 1; i <= 9; ++i )
            VisitDigitProductsImpl(min, max, visitor, i, i);
    }
    
    var Input = 42;
    var Product = 1;
    var Result = 0;
    
    // Iteration - step 1: 
    Result = Input % 10; // = 2
    Input -= Result;
    Product *= Result;
    
    // Iteration - step 2:
    Result = Input % 100 / 10; // = 4
    Input -= Result;
    Product *= Result;
    
    digits = 3, 2, 1      products = 3, 6, 6
    incrementing then changes the outer right digit and therefore only the outer right product is recalculated
    digits = 3, 2, 2      products = 3, 6, 12
    This goes up until the second digit is incremented:
    digits = 3, 3, 0      products = 3, 9, 0 (two products recalculated)
    
    using System;
    using System.Diagnostics;
    
    namespace Numbers2
    {
        class Program
        {
            /// <summary>
            /// Maximum of supported digits. 
            /// </summary>
            const int MAXLENGTH = 20;
            /// <summary>
            /// Contains the number in a decimal format. Index 0 is the righter number. 
            /// </summary>
            private static byte[] digits = new byte[MAXLENGTH];
            /// <summary>
            /// Contains the products of the numbers. Index 0 is the righther number. The left product is equal to the digit on that position. 
            /// All products to the right (i.e. with lower index) are the product of the digit at that position multiplied by the product to the left.
            /// E.g.
            /// 234 will result in the product 2 (=first digit), 6 (=second digit * 2), 24 (=third digit * 6)
            /// </summary>
            private static long[] products = new long[MAXLENGTH];
            /// <summary>
            /// The length of the decimal number. Used for optimisation. 
            /// </summary>
            private static int currentLength = 1;
            /// <summary>
            /// The start value for the calculations. This number will be used to start generated products. 
            /// </summary>
            const long INITIALVALUE = 637926372435;
            /// <summary>
            /// The number of values to calculate. 
            /// </summary>
            const int NROFVALUES = 10000;
    
            static void Main(string[] args)
            {
                Console.WriteLine("Started at " + DateTime.Now.ToString("HH:mm:ss.fff"));
    
                // set value and calculate all products
                SetValue(INITIALVALUE);
                UpdateProducts(currentLength - 1);
    
                for (long i = INITIALVALUE + 1; i <= INITIALVALUE + NROFVALUES; i++)
                {
                    int changedByte = Increase();
    
                    Debug.Assert(changedByte >= 0);
    
                    // update the current length (only increase because we're incrementing)
                    if (changedByte >= currentLength) currentLength = changedByte + 1;
    
                    // recalculate products that need to be updated
                    UpdateProducts(changedByte);
    
                    //Console.WriteLine(i.ToString() + " = " + products[0].ToString());
                }
                Console.WriteLine("Done at " + DateTime.Now.ToString("HH:mm:ss.fff"));
                Console.ReadLine();
            }
    
            /// <summary>
            /// Sets the value in the digits array (pretty blunt way but just for testing)
            /// </summary>
            /// <param name="value"></param>
            private static void SetValue(long value)
            {
                var chars = value.ToString().ToCharArray();
    
                for (int i = 0; i < MAXLENGTH; i++)
                {
                    int charIndex = (chars.Length - 1) - i;
                    if (charIndex >= 0)
                    {
                        digits[i] = Byte.Parse(chars[charIndex].ToString());
                        currentLength = i + 1;
                    }
                    else
                    {
                        digits[i] = 0;
                    }
                }
            }
    
            /// <summary>
            /// Recalculate the products and store in products array
            /// </summary>
            /// <param name="changedByte">The index of the digit that was changed. All products up to this index will be recalculated. </param>
            private static void UpdateProducts(int changedByte)
            {
                // calculate other products by multiplying the digit with the left product
                bool previousProductWasZero = false;
                for (int i = changedByte; i >= 0; i--)
                {
                    if (previousProductWasZero)
                    {
                        products[i] = 0;
                    }
                    else
                    {
                        if (i < currentLength - 1)
                        {
                            products[i] = (int)digits[i] * products[i + 1];
                        }
                        else
                        {
                            products[i] = (int)digits[i];
                        }
                        if (products[i] == 0)
                        {
                            // apply 'zero optimisation'
                            previousProductWasZero = true;
                        }
                    }
                }
            }
    
            /// <summary>
            /// Increases the number and returns the index of the most significant byte that changed. 
            /// </summary>
            /// <returns></returns>
            private static int Increase()
            {
                digits[0]++;
                for (int i = 0; i < MAXLENGTH - 1; i++)
                {
                    if (digits[i] == 10)
                    {
                        digits[i] = 0;
                        digits[i + 1]++;
                    }
                    else
                    {
                        return i;
                    }
                }
                if (digits[MAXLENGTH - 1] == 10)
                {
                    digits[MAXLENGTH - 1] = 0;
                }
                return MAXLENGTH - 1;
            }
        }
    }
    
    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;
            }
        }
    }
    
    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];
                    }
                }
            }
        }
    }
    
    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;
        }
    }
    
    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];
                }
            }
        }
    }