【问题标题】:Best way to find all factors of a given number查找给定数字的所有因子的最佳方法
【发布时间】:2008-10-27 13:28:52
【问题描述】:

所有能除以 x 的数。

我输入 4 它返回:4、2、1

编辑:我知道这听起来很家庭作业。我正在编写一个小应用程序来用半随机测试数据填充一些产品表。其中两个属性是 ItemMaximum 和 Item Multiplier。我需要确保乘数不会造成不合逻辑的情况,即再购买 1 件商品会使订单超过允许的最大值。因此,这些因素将为我的测试数据提供一个有效值列表。

编辑++: 在大家的帮助下,这就是我所做的。再次感谢!

edit#:我写了 3 个不同的版本来看看我更喜欢哪个版本,并针对分解小数字和非常大的数字对它们进行了测试。我会粘贴结果。

static IEnumerable<int> GetFactors2(int n)
{
    return from a in Enumerable.Range(1, n)
                  where n % a == 0
                  select a;                      
}

private IEnumerable<int> GetFactors3(int x)
{            
    for (int factor = 1; factor * factor <= x; factor++)
    {
        if (x % factor == 0)
        {
            yield return factor;
            if (factor * factor != x)
                yield return x / factor;
        }
    }
}

private IEnumerable<int> GetFactors1(int x)
{
    int max = (int)Math.Ceiling(Math.Sqrt(x));
    for (int factor = 1; factor < max; factor++)
    {
        if(x % factor == 0)
        {
            yield return factor;
            if(factor != max)
                yield return x / factor;
        }
    }
}

在滴答声中。 将数字 20 分解时,每个 5 次:

  • GetFactors1-5,445,881
  • GetFactors2-4,308,234
  • GetFactors3-2,913,659

当因式分解数字 20000 时,每次 5 次:

  • GetFactors1-5,644,457
  • GetFactors2-12,117,938
  • GetFactors3-3,108,182

【问题讨论】:

  • 您确实知道,我希望,对于他的问题没有已知的通用高性能解决方案。现代密码学依赖于没有这样的解决方案。
  • 是的,但我不确定是否有比简单地逐个测试数字更好的方法,因为我已经有一段时间没有上过数学课了。
  • python中的相关问题
  • 一个简单的winform应用程序,可以提供帮助findingnumberfactors.codeplex.com

标签: c# .net math


【解决方案1】:

伪代码:

  • 从 1 循环到数字的平方根,称为索引“i”。
  • 如果 number mod i 为 0,则将 i 和 number / i 添加到因子列表中。

重新编码:

public List<int> Factor(int number) 
{
    var factors = new List<int>();
    int max = (int)Math.Sqrt(number);  // Round down

    for (int factor = 1; factor <= max; ++factor) // Test from 1 to the square root, or the int below it, inclusive.
    {  
        if (number % factor == 0) 
        {
            factors.Add(factor);
            if (factor != number/factor) // Don't add the square root twice!  Thanks Jon
                factors.Add(number/factor);
        }
    }
    return factors;
}

正如 Jon Skeet 所提到的,您也可以将其实现为 IEnumerable&lt;int&gt; - 使用 yield 而不是添加到列表中。 List&lt;int&gt; 的优势在于,如果需要,它可以在返回之前进行排序。再说一次,您可以使用混合方法获得一个排序的枚举器,产生第一个因子并在循环的每次迭代中存储第二个因子,然后产生以相反顺序存储的每个值。

您还需要做一些事情来处理将负数传递给函数的情况。

【讨论】:

  • 一个额外的检查添加 - 上面将添加 2 两次 :)
  • Math.Sqrt 返回一个双精度值。此外,这需要四舍五入。尝试以 20 为例。
  • 可以重构循环而不是平方根:for(int factor = 1; factor*factor
  • True - 我想有一点过去性能会降低,因为你是在每个循环上计算它?它可能不如循环的其他部分重要。我想,必须进行基准测试。
  • 好主意马克。当你屈服时,你必须针对 factor * factor != x 进行测试。
【解决方案2】:

%(余数)运算符是此处使用的运算符。如果x % y == 0x 可以被y 整除。 (假设0 &lt; y &lt;= x

我个人会将此实现为使用迭代器块返回 IEnumerable&lt;int&gt; 的方法。

【讨论】:

  • % 实际上是 modulus operator ,而不是“余数”。 IEnumerable&lt;int&gt; 块有一个上限,这使得这对于分解“小”(计算)数字很实用。
  • @SamuelJackson:C# 5 语言规范第 5.8.3 节和Eric Lippert's blog post 不一致。您的 MSDN 参考是 JScript,而不是 C#。
  • @SamuelJackson:(鉴于 MSDN 页面谈到“模数或余数运算符”,无论如何它看起来有点破损,好像它认为两者是等价的......)
  • 参见C# Operators - 在乘法运算符部分下,它被命名为“模数”,在赋值和Lambda运算符下,它声明“模数赋值”。将 x 的值除以 y 的值,将余数存储在 x 中,并返回新值。”虽然模数计算在技术上是除数的所有可能除法之后剩下的内容,但此操作(以及在 c# 文档中)的正确数学术语是 modulus,有些人将其称为 modulo。 :)
  • 虽然可以使用术语“余数”,但运算符是“模数”,计算结果是所有除法完成后的余数。轻微的技术性,但即使是技术人员也会混淆计算结果和运算符本身之间的术语。
【解决方案3】:

很晚,但接受的答案(不久前)没有给出正确的结果。

多亏了 Merlyn,我现在得到了平方的原因,因为它位于校正样本下方的“最大值”。虽然 Echostorm 的答案似乎更完整。

public static IEnumerable<uint> GetFactors(uint x)
{
    for (uint i = 1; i * i <= x; i++)
    {
        if (x % i == 0)
        {
            yield return i;
            if (i != x / i)
                yield return x / i;
        }
    }
}

【讨论】:

  • 需要在for循环后添加以下内容才能返回x本身。数字始终是其自身的一个因素。 yield return x;
  • 我认为以前的错误现在都是正确的。它停在平方根的原因是因为它已经经历了平方根以下的可能因素(为了乘以“max”以上的任何数字需要),以及平方根以上的任何数字(因为它输出自身和乘以得到结果的数字)。在每次循环迭代中输出对的两侧时,尚未输出的最大可能值是平方根。
  • 例如,如果你得到了 81 的因子,那么 10 到 40 之间的循环迭代就被浪费了。当你输出 3 时你已经输出了 27,如果你没有输出两边,如果你停在 81/2 (40),你永远不会得到 81 作为因子。
  • 干杯!现在确实有道理。
  • 我会缓存 x / i 而不是计算两次
【解决方案4】:

作为扩展方法:

    public static bool Divides(this int potentialFactor, int i)
    {
        return i % potentialFactor == 0;
    }

    public static IEnumerable<int> Factors(this int i)
    {
        return from potentialFactor in Enumerable.Range(1, i)
               where potentialFactor.Divides(i)
               select potentialFactor;
    }

这是一个使用示例:

        foreach (int i in 4.Factors())
        {
            Console.WriteLine(i);
        }

请注意,我已经优化了清晰度,而不是性能。对于较大的 i 值,此算法可能需要很长时间。

【讨论】:

    【解决方案5】:

    另一种 LINQ 样式和绑定以保持 O(sqrt(n)) 复杂度

            static IEnumerable<int> GetFactors(int n)
            {
                Debug.Assert(n >= 1);
                var pairList = from i in Enumerable.Range(1, (int)(Math.Round(Math.Sqrt(n) + 1)))
                        where n % i == 0
                        select new { A = i, B = n / i };
    
                foreach(var pair in pairList)
                {
                    yield return pair.A;
                    yield return pair.B;
                }
    
    
            }
    

    【讨论】:

      【解决方案6】:

      就像其他人提到的那样,这里又是一次,只计算平方根。如果您希望提高性能,我想人们会被这个想法所吸引。我宁愿先写优雅的代码,然后在测试我的软件后优化性能。

      仍然,作为参考,这里是:

          public static bool Divides(this int potentialFactor, int i)
          {
              return i % potentialFactor == 0;
          }
      
          public static IEnumerable<int> Factors(this int i)
          {
              foreach (int result in from potentialFactor in Enumerable.Range(1, (int)Math.Sqrt(i))
                                     where potentialFactor.Divides(i)
                                     select potentialFactor)
              {
                  yield return result;
                  if (i / result != result)
                  {
                      yield return i / result;
                  }
              }
          }
      

      不仅结果的可读性大大降低,而且这些因素也会以这种方式出现混​​乱。

      【讨论】:

      • 因为它们是两个不同的答案,具有不同的优点。
      【解决方案7】:

      我是偷懒的。我知道的不多,但有人告诉我,简单有时也意味着优雅。这是一种可能的方法:

          public static IEnumerable<int> GetDivisors(int number)
          {
              var searched = Enumerable.Range(1, number)
                   .Where((x) =>  number % x == 0)
                   .Select(x => number / x);
      
              foreach (var s in searched)          
                  yield return s;
          }
      

      编辑:正如 Kraang Prime 指出的那样,这个函数不能超过整数的限制,并且(诚然)不是处理这个问题的最有效方法。

      【讨论】:

      • 这只能处理不超过整数限制的因子。
      • @spencer 只是好奇你为什么在 foreach 循环中使用 yield return ,因为你已经有一个 IEnumerable 为什么不只返回 Linq 查询的结果而不是将其分配给搜索?
      【解决方案8】:

      从 2 开始并朝着根据您刚刚检查的数字不断重新计算的上限值前进不是也有意义吗?请参阅 N/i(其中 N 是您要查找的因数,i 是要检查的当前数字...)理想情况下,您可以使用一个除法函数而不是 mod,它也返回 N/i就像它可能有的任何剩余部分一样。这样,您将执行一次除法运算来重新创建上限以及检查偶数除法的余数。

      数学.DivRem http://msdn.microsoft.com/en-us/library/wwc1t3y1.aspx

      【讨论】:

        【解决方案9】:

        如果您使用双精度数,则以下方法有效:使用 for 循环从 1 迭代到您想要分解的数字。在每次迭代中,将要分解的数字除以 i。如果 (number / i) % 1 == 0,则 i 是一个因数,number / i 的商也是如此。将其中一项或两项放在一个列表中,您就拥有了所有因素。

        【讨论】:

          【解决方案10】:

          还有一个解决方案。不知道它除了可读性之外是否还有其他优点..:

          List<int> GetFactors(int n)
          {
              var f = new List<int>() { 1 };  // adding trivial factor, optional
              int m = n;
              int i = 2;
              while (m > 1)
              {
                  if (m % i == 0)
                  {
                      f.Add(i);
                      m /= i;
                  }
                  else i++;
              }
              // f.Add(n);   // adding trivial factor, optional
              return f;
          }
          

          【讨论】:

          • 我建议提供更具描述性的变量名称以进一步提高可读性。
          • 这个看起来不太对劲。例如,因子 42 应该返回一个包含 8 个值的列表。 1,2,3,6,7,14,21 和 42……但对我来说,这个函数只返回 1,2,3 和 7。只是想让你知道。
          【解决方案11】:

          我来这里只是为自己寻找解决此问题的方法。在检查了之前的回复之后,我认为即使我参加聚会可能会迟到一点,也可以自己给出一个答案。 一个数的最大因子数将不超过该数的一半。无需处理浮点值或平方根之类的超越运算。此外,找到一个数的一个因数会自动找到另一个因数。只需找到一个,只需将原始数字除以找到的数字即可返回两者。

          我怀疑我是否需要对自己的实现使用检查,但我将它们包括在内只是为了完整性(至少部分)。

          public static IEnumerable<int>Factors(int Num)
          {
            int ToFactor = Num;
          
            if(ToFactor == 0)
            { // Zero has only itself and one as factors but this can't be discovered through division
              // obviously. 
               yield return 0;
               return 1;
            }
              
            if(ToFactor < 0)
            {// Negative numbers are simply being treated here as just adding -1 to the list of possible 
             // factors. In practice it can be argued that the factors of a number can be both positive 
             // and negative, i.e. 4 factors into the following pairings of factors:
             // (-4, -1), (-2, -2), (1, 4), (2, 2) but normally when you factor numbers you are only 
             // asking for the positive factors. By adding a -1 to the list it allows flagging the
             // series as originating with a negative value and the implementer can use that
             // information as needed.
              ToFactor = -ToFactor;
              
              yield return -1;
             }
              
            int FactorLimit = ToFactor / 2; // A good compiler may do this optimization already.
                                            // It's here just in case;
              
            for(int PossibleFactor = 1; PossibleFactor <= FactorLimit; PossibleFactor++)
            {
              if(ToFactor % PossibleFactor == 0)
              {
                yield return PossibleFactor;
                yield return ToFactor / PossibleFactor;
              }
            }
          }
          

          【讨论】:

            【解决方案12】:

            在 javascript 代码中获取整数的素因子的程序。

            function getFactors(num1){
                var factors = [];
                var divider = 2;
                while(num1 != 1){
                    if(num1 % divider == 0){
                        num1 = num1 / divider;
                        factors.push(divider);
                    }
                    else{
                        divider++;
                    }
                }
                console.log(factors);
                return factors;
            }
            
            getFactors(20);
            

            【讨论】:

              【解决方案13】:

              事实上,我们不必从chrisJon 修复的接受的答案中检查每次迭代中不是平方根的因子,这可能会在整数很大时通过添加来减慢该方法不必要的布尔检查和除法。只需将 max 保持为双精度(不要将其转换为 int),然后将循环更改为独占不包含。

                  private static List<int> Factor(int number)
                  {
                      var factors = new List<int>();
                      var max = Math.Sqrt(number);  // (store in double not an int) - Round down
                      if (max % 1 == 0)
                          factors.Add((int)max);
              
                      for (int factor = 1; factor < max; ++factor) //  (Exclusice) - Test from 1 to the square root, or the int below it, inclusive.
                      {
                          if (number % factor == 0)
                          {
                              factors.Add(factor);
                              //if (factor != number / factor) // (Don't need check anymore) - Don't add the square root twice!  Thanks Jon
                              factors.Add(number / factor);
                          }
                      }
                      return factors;
                  }
              

              用法

              Factor(16)
              // 4 1 16 2 8
              Factor(20)
              //1 20 2 10 4 5
              

              这是int类型的方法的扩展版本:

              public static class IntExtensions
              {
                  public static IEnumerable<int> Factors(this int value)
                  {
                      // Return 2 obvious factors
                      yield return 1;
                      yield return value;
                      
                      // Return square root if number is prefect square
                      var max = Math.Sqrt(value);
                      if (max % 1 == 0)
                          yield return (int)max;
              
                      // Return rest of the factors
                      for (int i = 2; i < max; i++)
                      {
                          if (value % i == 0)
                          {
                              yield return i;
                              yield return value / i;
                          }
                      }
                  }
              }
              

              用法

              16.Factors()
              // 4 1 16 2 8
              20.Factors()
              //1 20 2 10 4 5
              

              【讨论】:

                【解决方案14】:

                Linq 解决方案:

                IEnumerable<int> GetFactors(int n)
                {
                  Debug.Assert(n >= 1);
                  return from i in Enumerable.Range(1, n)
                         where n % i == 0
                         select i;
                }
                

                【讨论】:

                • 这只会得到前 1/2 的因子。例如,对于 10,它将返回 1 和 2,但不返回 5 和 10。
                • 建议更改:Math.Sqrt 将返回一个双精度数,这不适用于 Enumerable.Range。这也不会返回 4 - 只有 1 和 2。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-01-28
                • 2011-10-11
                • 2021-05-03
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多