项目Euler:问题1(可能的重构和运行时优化)

时间:2010-07-30 19:12:01

标签: c# algorithm optimization refactoring

我听过很多关于Project Euler的消息,所以我想我解决了C#中的一个问题。网站上说明的问题如下:

  

如果我们列出所有自然数字   10以下是3或5的倍数,   我们得到3,5,6和9.这些的总和   倍数是23。

     

求出3的所有倍数之和   或者低于1000。

我编写了如下代码:

  class EulerProblem1
    {
        public static void Main()
        {
            var totalNum = 1000;
            var counter = 1;
            var sum = 0;

            while (counter < totalNum)
            {
                if (DivisibleByThreeOrFive(counter))
                    sum += counter;

                counter++;
            }

            Console.WriteLine("Total Sum: {0}", sum);
            Console.ReadKey();
        }

        private static bool DivisibleByThreeOrFive(int counter)
        {
            return ((counter % 3 == 0) || (counter % 5 == 0));

        }
    } 

以更少的冗长/更清晰的语法和更好的优化来获得关于替代实现的一些想法会很棒。这些想法可能从快速和肮脏到带出大炮消灭蚊子。目的是探索计算机科学的深度,同时尝试改进这个特别简单的代码片段。

由于

13 个答案:

答案 0 :(得分:9)

更新为不重复计算3和5的倍数的数字:

int EulerProblem(int totalNum)
{
   int a = (totalNum-1)/3;
   int b = (totalNum-1)/5;
   int c = (totalNum-1)/15;
   int d = a*(a+1)/2;
   int e = b*(b+1)/2;
   int f = c*(c+1)/2;
   return 3*d + 5*e - 15*f;
}

答案 1 :(得分:4)

这是我原来的F#解决方案到C#的音译。编辑:它基本上是mbeckish的解决方案,作为循环而不是函数(我删除了双重计数)。我喜欢mbeckish更好。

static int Euler1 ()
{
  int sum = 0;

  for (int i=3; i<1000; i+=3) sum+=i;
  for (int i=5; i<1000; i+=5) sum+=i;
  for (int i=15; i<1000; i+=15) sum-=i;

  return sum;
}

这是原作:

let euler1 d0 d1 n =
  (seq {d0..d0..n}       |> Seq.sum) +
  (seq {d1..d1..n}       |> Seq.sum) -
  (seq {d0*d1..d0*d1..n} |> Seq.sum)

let result = euler1 3 5 (1000-1)

答案 2 :(得分:4)

使用LINQ(根据评论中的建议更新)

static void Main(string[] args)
{
    var total = Enumerable.Range(0,1000)
                    .Where(counter => (counter%3 == 0) || (counter%5 == 0))
                    .Sum();

    Console.WriteLine(total);
    Console.ReadKey();
}

答案 3 :(得分:3)

我有一段时间没有写过任何Java,但是这应该在很短的时间内以恒定的时间解决它:

public class EulerProblem1
{
    private static final int EULER1 = 233168;
    // Equal to the sum of all natural numbers less than 1000
    // which are multiples of 3 or 5, inclusive.

    public static void main(String[] args)
    {
        System.out.println(EULER1);
    }
}

编辑:如果每条指令都重要,这是一个C实现:

#define STDOUT     1
#define OUT_LENGTH 8

int main (int argc, char **argv)
{
    const char out[OUT_LENGTH] = "233168\n";
    write(STDOUT, out, OUT_LENGTH);
}

注意:

  • write的调用没有错误处理。如果需要真正的鲁棒性,则必须采用更复杂的错误处理策略。增加的复杂性是否值得更高的可靠性取决于用户的需求。
  • 如果您有内存限制,则可以使用直接字符数组而不是由多余空字符终止的字符串来保存字节。但实际上,out几乎肯定会被填充到8个字节。
  • 虽然可以通过在out调用中将字符串内联放置来避免声明write变量,但任何真正的编译器都会优化声明。
  • write系统调用优先于puts或类似使用,以避免额外开销。从理论上讲,您可以直接调用系统调用,可能会节省几个周期,但这会引发重大的可移植性问题。关于这是否是可接受的权衡,您的里程可能会有所不同。

答案 4 :(得分:3)

重构@ mbeckish非常聪明的解决方案:

public int eulerProblem(int max) {
    int t1 = f(max, 3);
    int t2 = f(max, 5);
    int t3 = f(max, 3 * 5);
    return t1 + t2 - t3;
}

private int f(int max, int n) {
    int a = (max - 1) / n;
    return n * a * (a + 1) / 2;
}

答案 5 :(得分:1)

这与我解决问题的方式基本相同。我知道在project-euler的论坛上还有其他解决方案(可能也更有效)。

一旦你输入你的答案回到问题,你就可以选择去论坛解决这个问题。你可能想看看那里!

答案 6 :(得分:1)

如果您按如下方式说明,DivisibleByThreeOrFive中的代码会稍快一些:

return ((counter % 3 == 0) || (counter % 5 == 0));

如果您不想依赖编译器来内联函数调用,您可以将此代码放入Main例程中来自行完成。

答案 7 :(得分:1)

您可以为此提出一个封闭的表单解决方案。诀窍是寻找模式。尝试列出总计最多10个或20个的术语,然后使用代数对它们进行分组。通过进行适当的替换,您可以将其推广到十个以外的数字。请注意边缘情况。

答案 8 :(得分:1)

在C中尝试这个。它是恒定的时间,并且只有一个除法(如果编译器没有优化div / mod,它应该是两个除法)。我确信它可以让它更明显一点,但这应该有效。

它基本上将总和分为两部分。较大部分(对于N> = 15)是一个简单的二次函数,它将N分成15的精确块。较小的部分是不适合块的最后一位。后一点比较麻烦,但只有少数可能性,所以LUT会立即解决它。

const unsigned long N = 1000 - 1;
const unsigned long q = N / 15;
const unsigned long r = N % 15;
const unsigned long rc = N - r;

unsigned long sum = ((q * 105 + 15) * q) >> 1;

switch (r) {
    case 3  : sum += 3  + 1*rc ; break;
    case 4  : sum += 3  + 1*rc ; break;
    case 5  : sum += 8  + 2*rc ; break;
    case 6  : sum += 14 + 3*rc ; break;
    case 7  : sum += 14 + 3*rc ; break;
    case 8  : sum += 14 + 3*rc ; break;
    case 9  : sum += 23 + 4*rc ; break;
    case 10 : sum += 33 + 5*rc ; break;
    case 11 : sum += 33 + 5*rc ; break;
    case 12 : sum += 45 + 6*rc ; break;
    case 13 : sum += 45 + 6*rc ; break;
    case 14 : sum += 45 + 6*rc ; break;
}

答案 9 :(得分:1)

您可以这样做:

Func<int,int> Euler = total=> 
    new List<int>() {3,5}
        .Select(m => ((int) (total-1) / m) * m * (((int) (total-1) / m) + 1) / 2)
        .Aggregate( (T, m) => T+=m);

你仍然有重复计算问题。我会再考虑一下这个。

修改

以下是LINQ中的一个有效(如果稍微不优雅)的解决方案:

        var li = new List<int>() { 3, 5 };
        Func<int, int, int> Summation = (total, m) => 
           ((int) (total-1) / m) * m * (((int) (total-1) / m) + 1) / 2;

        Func<int,int> Euler = total=> 
            li
                .Select(m => Summation(total, m))
                .Aggregate((T, m) => T+=m)
            - Summation(total, li.Aggregate((T, m) => T*=m));

你们中的任何人都可以改进吗?

<强>解释

记住线性进展的求和公式是n(n + 1)/ 2。在第一种情况下,你有3,5的倍数&lt; 10,你想要Sum(3 + 6 + 9,5)。设置total = 10,你创建一个整数序列1 ..(int)(total-1)/ 3,然后对序列求和并乘以3.你可以很容易地看到我们只是设置n =(int )(total-1)/ 3,然后使用求和公式并乘以3.一个小代数给出了求和函子的公式。

答案 10 :(得分:0)

我喜欢technielogys的想法,这是我对修改的想法

static int Euler1 () 
{ 
  int sum = 0; 

  for (int i=3; i<1000; i+=3) 
  {
    if (i % 5 == 0) continue;
    sum+=i; 
  }
  for (int i=5; i<1000; i+=5) sum+=i; 

  return sum; 
} 

虽然也想到了可能是一个小的启发式,这有什么改进吗?

static int Euler1 () 
{ 
  int sum = 0; 

  for (int i=3; i<1000; i+=3) 
  {
    if (i % 5 == 0) continue;
    sum+=i; 
  }
  for (int i=5; i<250; i+=5)
  {
    sum+=i;
  }
  for (int i=250; i<500; i+=5)
  {
    sum+=i;
    sum+=i*2;
    sum+=(i*2)+5;
  }

  return sum; 
} 

答案 11 :(得分:0)

Your approach is brute force apprach, The time complexity of the following approach is O(1), Here we     
are dividing the given (number-1) by 3, 5 and 15, and store in countNumOf3,countNumOf5, countNumOf15.
Now we can say that 3 will make AP, within the range of given (number-1) with difference of 3. 
suppose you are given number is 16, then 
3=> 3, 6, 9, 12, 15= sum1=>45
5=> 5, 10, 15  sum2=> 30
15=> 15 =>   sum3=15  
Add sum= sum1 and sum2


Here 15 is multiple of 3 and 5  so remove sum3 form sum, this will be your answer. **sum=sum-    
sum3** please check link of my solution on http://ideone.com/beXsam]

import java.util.*;
class Multiplesof3And5 {        
        public static void main(String [] args){
            Scanner scan=new Scanner(System.in);
            int num=scan.nextInt();
            System.out.println(getSum(num));
        }    
        public static  long getSum(int n){
            int countNumOf3=(n-1)/3;//             
            int countNumOf5=(n-1)/5;           
            int countNumOf15=(n-1)/15;            
            long sum=0;           
            sum=sumOfAP(3,countNumOf3,3)+sumOfAP(5,countNumOf5,5)-sumOfAP(15,countNumOf15,15);
            return sum;
        }
        public static int sumOfAP(int a, int n, int d){
            return (n*(2*a +(n -1)*d))/2;
        }
}

答案 12 :(得分:0)

new List<int>{3,5}.SelectMany(n =>Enumerable.Range(1,999/n).Select(i=>i*n))
                  .Distinct()
                  .Sum()

[更新](回应要求解释此algorothm的评论) 这为每个基值(在这种情况下为3和5)构建了一个扁平的倍数列表,然后删除重复(例如,在这种情况下,乘数可以被3 * 5 = 15除数),然后对剩余值求和。 (与我在这里看到的任何其他解决方案相比,这可以很容易地推广到具有两个以上的基本值IMHO。)