31

我最近在“您有什么更具争议的编程观点”中发布了我最喜欢的面试白板编码问题之一,即编写一个使用莱布尼茨公式计算 Pi 的函数。

它可以通过多种不同的方式来处理,退出条件需要一些思考,所以我认为它可能会成为一个有趣的代码高尔夫问题。最短的代码获胜!

假设 Pi 可以使用函数 4 * (1 - 1/3 + 1/5 - 1/7 + ...) 来估计,更多的项可以提供更高的准确性,请编写一个计算 Pi 到 0.00001 以内的函数。

编辑:2008 年 1 月 3 日

正如评论中所建议的那样,我将退出条件更改为 0.00001 以内,因为这就是我真正的意思(由于四舍五入,精确到小数点后 5 位要困难得多,所以我不想在面试中问这个问题,而在 0.00001 以内是更容易理解和实现退出条件)。

另外,为了回答评论,我想我的意图是解决方案应该计算迭代次数,或者检查它何时完成了足够的工作,但是没有什么可以阻止您预先计算迭代次数并使用该数字。我真的很感兴趣地问了这个问题,看看人们会想出什么。

4

46 回答 46

61

J,14 个字符

4*-/%>:+:i.1e6

解释

  • 1e6是数字 1 后跟 6 个零 (1000000)。
  • i.y生成第一个y非负数。
  • +:是一个将列表参数中的每个元素加倍的函数。
  • >:是一个函数,它使列表参数中的每个元素加一。

因此,表达式>:+:i.1e6生成前一百万个奇数:

1 3 5 7 ...

  • %是倒数运算符(分子“1”可以省略)。
  • -/对列表参数中的每个元素进行交替求和。

因此,该表达式-/%>:+:i.1e6生成前一百万个奇数的倒数的交替和:

1 - 1/3 + 1/5 - 1/7 + ...

  • 4*是乘以四。如果将前一个总和乘以四,则得到 π。

就是这样!J是一种强大的数学语言。


编辑:因为生成9!(362880) 项的交替和足以具有 5 个十进制数字的精度,并且由于莱布尼茨公式也可以这样写:

4 - 4/3 + 4/5 - 4/7 + ...

...您可以编写一个较短的12 个字符版本的程序:

-/4%>:+:i.9!
于 2009-01-03T12:51:45.220 回答
36

语言:Brainfuck,字符数:51/59

这算不算?=]

因为 Brainfuck 中没有浮点数,所以很难让除法正常工作。Grr。

没有换行符(51):

+++++++[>+++++++<-]>++.-----.+++.+++.---.++++.++++.

使用换行符(59):

+++++++[>+++++++>+<<-]>++.-----.+++.+++.---.++++.++++.>+++.
于 2009-01-03T02:19:18.430 回答
24

Perl

26 个字符

26 只是函数,27 用于计算,31 用于打印。从评论到这个答案

sub _{$-++<1e6&&4/$-++-&_}       # just the sub
sub _{$-++<1e6&&4/$-++-&_}_      # compute
sub _{$-++<1e6&&4/$-++-&_}say _  # print

28 个字符

28 只是计算,34 用于打印。从评论。请注意,此版本不能使用“说”。

$.=.5;$\=2/$.++-$\for 1..1e6        # no print
$.=.5;$\=2/$.++-$\for$...1e6;print  # do print, with bonus obfuscation

36 个字符

36 只计算,42 打印。从评论中,哈德森对 dreeves 的重新安排的看法。

$/++;$\+=8/$//($/+2),$/+=4for$/..1e6
$/++;$\+=8/$//($/+2),$/+=4for$/..1e6;print

关于迭代次数:就我的数学记忆而言,400000 可证明足以精确到 0.00001。但是一百万(或低至 8e5)实际上使十进制扩展实际上匹配 5 个小数位,并且它是相同的字符数,所以我保留了它。

于 2009-01-02T18:59:39.503 回答
23

红宝石,33 个字符

(0..1e6).inject{|a,b|2/(0.5-b)-a}
于 2009-01-03T03:20:46.213 回答
20

另一个 C# 版本:

(60 个字符)

4*Enumerable.Range(0, 500000).Sum(x => Math.Pow(-1, x)/(2*x + 1));  // = 3,14159
于 2009-01-02T18:19:39.363 回答
14

Python中的 52 个字符:

print 4*sum(((-1.)**i/(2*i+1)for i in xrange(5**8)))

(51 从 xrange 中删除了“x”。)

Octave(或 Matlab)中的 36 个字符:

l=0:5^8;disp((-1).^l*(4./(2.*l+1))')

(执行“format long;”以显示所有有效数字。)省略 'disp' 我们达到 30 个字符:

octave:5> l=0:5^8;(-1).^l*(4./(2.*l+1))'
ans = 3.14159009359631
于 2009-01-02T17:50:20.080 回答
13

Oracle SQL 73 个字符

select -4*sum(power(-1,level)/(level*2-1)) from dual connect by level<1e6
于 2009-01-03T14:52:29.330 回答
10

语言:C,字符数:71

float p;main(i){for(i=1;1E6/i>5;i+=2)p-=(i%4-2)*4./i;printf("%g\n",p);}

语言:C99,字符数:97(包括必需的换行符)

#include <stdio.h>
float p;int main(){for(int i=1;1E6/i>5;i+=2)p-=(i%4-2)*4./i;printf("%g\n",p);}

我应该注意,上述版本(相同)会跟踪额外的迭代是否会影响结果。因此,它执行最少数量的操作。要添加更多数字,请替换1E61E(num_digits+1)4E5替换为4E(num_digits)(取决于版本)。对于完整的程序,%g可能需要更换。 float可能也需要更改double为。

语言:C,字符数:67(见注释)

double p,i=1;main(){for(;i<1E6;i+=4)p+=8/i/(i+2);printf("%g\n",p);}

此版本使用已发布算法的修改版本,正如其他一些答案所使用的那样。此外,它不像前两个解决方案那样干净/高效,因为它强制进行 100 000 次迭代,而不是检测迭代何时变得毫无意义。

语言:C,字符数:24(作弊

main(){puts("3.14159");}

但是,不适用于数字计数> 6。

于 2009-01-03T01:46:22.503 回答
10

哈斯克尔

我把它缩小到 34 个字符:

foldl subtract 4$map(4/)[3,5..9^6]

此表达式在评估时产生 3.141596416935556。

编辑:这是一个较短的版本(33 个字符),它使用 foldl1 而不是 foldl:

foldl1 subtract$map(4/)[1,3..9^6]

编辑 2:9^6 而不是 10^6。一个必须是经济的;)

编辑 3:分别用 foldl 和 foldl1 替换为 foldl' 和 foldl1' - 由于编辑 2,它不再溢出。感谢 ShreevatsaR 注意到这一点。

于 2009-01-03T10:57:28.883 回答
10

MATLAB 中的 23 个字符:

a=1e6;sum(4./(1-a:4:a))
于 2009-01-22T19:47:57.427 回答
9

F#

尝试#1:

let pi = 3.14159

作弊?不,它以风格取胜!

尝试#2:


let pi =
    seq { 0 .. 100 }
    |> Seq.map (fun x -> float x)
    |> Seq.fold (fun x y -> x + (Math.Pow(-1.0, y)/(2.0 * y + 1.0))) 0.0
    |> (fun x -> x * 4.0)

它不像它可能得到的那么紧凑,但是非常地道的 F#。

于 2009-01-02T18:44:42.327 回答
7

普通的 lisp,55 个字符。

(loop for i from 1 upto 4e5 by 4 sum (/ 8d0 i (+ i 2)))
于 2009-01-05T03:53:43.917 回答
6

Mathematica,27 个字符(可以说低至 26,或高达 33)

NSum[8/i/(i+2),{i,1,9^9,4}]

如果您删除最初的“N”,那么它会以(巨大的)分数形式返回答案。

如果 Mathematica 不需要打印语句来输出其结果是作弊,则在前面加上“ Print@”,总共 33 个字符。

注意:

如果硬编码条款的数量是作弊,那么我认为还没有任何答案可以做到这一点。检查当前术语何时低于某个阈值并不比硬编码术语的数量更好。仅仅因为当前项仅更改第 6 位或第 7 位,并不意味着足够后续项的总和不会更改第 5 位。

于 2009-01-03T02:04:04.590 回答
5

使用交替序列中的误差项的公式(因此,实现所需精度所需的迭代次数不会硬编码到程序中):

public static void Main(string[] args) {
    double tolerance = 0.000001;
    double piApproximation = LeibnizPi(tolerance);
    Console.WriteLine(piApproximation);
}

private static double LeibnizPi(double tolerance) {
    double quarterPiApproximation = 0;

    int index = 1;
    double term;
    int sign = 1;
    do {
        term = 1.0 / (2 * index - 1);
        quarterPiApproximation += ((double)sign) * term;
        index++;
        sign = -sign;
    } while (term > tolerance);

    return 4 * quarterPiApproximation;
}
于 2009-01-03T02:18:24.420 回答
4

C#:

public static double Pi()
{
    double pi = 0;
    double sign = 1;
    for (int i = 1; i < 500002; i += 2)
    {
        pi += sign / i;
        sign = -sign;
    }
    return 4 * pi;
}
于 2009-01-02T18:16:11.370 回答
4

珀尔:

$i+=($_&1?4:-4)/($_*2-1)for 1..1e6;print$i

总共42个字符。

于 2009-01-02T18:30:27.080 回答
4

Ruby,41 个字符(使用 irb):

s=0;(3..3e6).step(4){|i|s+=8.0/i/(i-2)};s

或者这个稍长的非 irb 版本:

s=0;(3..3e6).step(4){|i|s+=8.0/i/(i-2)};p s

这是修改后的莱布尼茨:

  1. 组合成对的术语。这给了你 2/3 + 2/35 + 2/99 + ...
  2. Pi 变为 8 * (1/(1 * 3) + 1/(5 * 7) + 1/(9 * 11) + ...)
于 2009-01-02T19:37:42.547 回答
4

F#(交互模式)(59 个字符)

{0.0..1E6}|>Seq.fold(fun a x->a+ -1.**x/(2.*x+1.))0.|>(*)4.

(产生警告但省略演员表)

于 2009-01-03T14:19:50.643 回答
3

这是 MUMPS 中的一个解决方案。

pi(N)
 N X,I
 S X=1 F I=3:4:N-2 S X=X-(1/I)+(1/(I+2))
 Q 4*X

参数 N 表示要使用多少个重复分数。也就是说,如果您传入 5,它将评估 4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11)

一些经验测试表明,N=272241 是在截断到小数点后 5 位时给出正确值 3.14159 的最低值。您必须转到 N=852365 才能得到一个四舍五入为 3.14159 的值。

于 2009-01-02T18:52:42.777 回答
3

Javascript:

a=0,b=-1,d=-4,c=1e6;while(c--)a+=(d=-d)/(b+=2)

在 JavaScript 中。51 个字符。显然不会赢,但是嗯。:P

编辑——感谢 Strager,现在更新为 46 个字符。:)


更新 (2010 年 3 月 30 日)

大卫默多克更快(精确到小数点后 5 位)的 43 个字符版本

for(a=0,b=1,d=4,c=~4e5;++c;d=-d)a-=d/(b-=2)
于 2009-01-03T02:27:13.483 回答
3

C# 使用迭代器块:

static IEnumerable<double> Pi()
{
    double i = 4, j = 1, k = 4;
    for (;;)
    {
        yield return k;
        k += (i *= -1) / (j += 2);
    }
}
于 2009-01-03T09:00:31.660 回答
3

作为记录,这个 Scheme 实现有 95 个字符,忽略了不必要的空格。

(define (f)
  (define (p a b)
    (if (> a b)
      0
      (+ (/ 1.0 (* a (+ a 2))) (p (+ a 4) b))))
  (* 8 (p 1 1e6)))
于 2009-01-04T06:33:17.543 回答
2

这是使用 C# 的递归答案。它只能在发布模式下使用 x64 JIT 工作,因为这是唯一应用尾调用优化的 JIT,并且由于系列收敛如此缓慢,它会导致StackOverflowException没有它。

IteratePi函数用作匿名 lambda 会很好,但由于它是自递归的,我们必须开始使用 Y 组合器做各种可怕的事情,所以我把它作为一个单独的函数。

public static double CalculatePi()
{
    return IteratePi(0.0, 1.0, true);
}

private static double IteratePi(double result, double denom, bool add)
{
    var term = 4.0 / denom;
    if (term < 0.00001) return result;    
    var next = add ? result + term : result - term;
    return IteratePi(next, denom + 2.0, !add);
}
于 2009-01-02T17:46:29.587 回答
2

语言:dc,字符数:35

dc -e '9k0 1[d4r/r2+sar-lad274899>b]dsbxrp'
于 2009-01-02T21:49:37.677 回答
2

语言:C99(隐式返回 0),字符数:99(95 + 4 个必需空格)

退出条件取决于当前值,而不是固定计数

#include <stdio.h>

float p, s=4, d=1;
int main(void) {
  for (; 4/d > 1E-5; d += 2)
        p -= (s = -s) / d;
  printf("%g\n", p);
}

压缩版

#include<stdio.h>
float
p,s=4,d=1;int
main(void){for(;4/d>1E-5;d+=2)p-=(s=-s)/d;printf("%g\n",p);}
于 2009-01-03T00:55:05.807 回答
2

当前的大多数答案都假设他们将在一定次数的迭代中获得 5 位数的准确度,并且这个数字被硬编码到程序中。我对这个问题的理解是,程序本身应该弄清楚它何时得到精确到 5 位数的答案并停在那里。基于这个假设,这是我的 C# 解决方案。我没有费心尽量减少字符的数量,因为它无法与已经存在的一些答案竞争,所以我想我会让它变得可读。:)

    private static double GetPi()
    {
        double acc = 1, sign = -1, lastCheck = 0;

        for (double div = 3; ; div += 2, sign *= -1)
        {
            acc += sign / div;

            double currPi = acc * 4;
            double currCheck = Math.Round(currPi, 5);

            if (currCheck == lastCheck)
                return currPi;

            lastCheck = currCheck;
        }
    }
于 2009-01-03T02:59:03.043 回答
1

红宝石:

irb(main):031:0> 4*(1..10000).inject {|s,x| s+(-1)**(x+1)*1.0/(2*x-1)}
=> 3.14149265359003
于 2009-01-02T18:13:55.443 回答
1

C# 作弊 - 50 个字符

static single Pi(){
  return Math.Round(Math.PI, 5));
}

它只说“考虑到公式编写一个函数......”它没有说以编程方式重现公式:) 跳出框框思考......

C# LINQ - 78 个字符

static double pi = 4 * Enumerable.Range(0, 1000000)
               .Sum(n => Math.Pow(-1, n) / (2 * n + 1));

C# 备用 LINQ - 94 个字符

static double pi = return 4 * (from n in Enumerable.Range(0, 1000000)
                               select Math.Pow(-1, n) / (2 * n + 1)).Sum();

最后 - 这采用前面提到的算法并在数学上对其进行压缩,因此您不必担心不断变化的符号。

C# 速记 - 89 个字符(不包括不需要的空格)

static double pi()
{
  var t = 0D;
  for (int n = 0; n < 1e6; t += Math.Pow(-1, n) / (2 * n + 1), n++) ;
  return 4 * t;
}
于 2009-01-03T01:51:51.840 回答
1

AWK 中的 64 个字符:

~# awk 'BEGIN {p=1;for(i=3;i<10^6;i+=4){p=p-1/i+1/(i+2)}print p*4}'
3.14159
于 2009-01-03T14:02:16.340 回答
1
#!/usr/bin/env python
from math import *
denom = 1.0
imm = 0.0
sgn = 1
it = 0
for i in xrange(0, int(1e6)):
    imm += (sgn*1/denom)
    denom += 2
    sgn *= -1    
print str(4*imm)
于 2009-01-05T10:23:53.987 回答
1

C++

double LeibnizPi( double tolerance )
{
    double sum = 1.0;
    for( int plus_div = 5, minus_div = -3, limit = 10 / tolerance; plus_div < limit ; plus_div += 4, minus_div -= 4 )
        sum += 1./plus_div + 1./minus_div;
    return 4 * sum;
}
于 2009-01-09T21:56:06.890 回答
1

注意到之后

(= (- (/ 4 n)
      (/ 4 (+ n 2)))
   (/ 8 n (+ n 2)))

或者,用更熟悉的表示法:

4 4 8
- - --- = ------
n n+2 n(n+2)

Common Lisp,带有do*循环(62 个基本字符):

(do* ((n 1 (+ n 4))
      (p 8/3 (+ p (/ 8 n (+ n 2)))))
     ((< (- pi p) 1e-6)
      p)

带有尾递归函数(70 个基本字符):

(defun l (n p)
  (if (< (- pi p) 1e-6)
      p
      (l (+ n 4)
          (+ p (/ 8 n (+ n 2))))))
(l 1 0)

并使用扩展循环(86 个基本字符):

(loop for n from 1 by 4
      sum (/ 8 n (+ n 2)) into p
      until (< (- pi p) 1e-6)
      finally (return p))

所有这些都假设初步检查我们必须走多远才能获得所需的准确性是作弊。

于 2009-01-10T20:59:20.483 回答
1
double d = 1;
double s = 1;
double pi = 0;

while(4.0 / d > 0.000001){
    pi += s*4.0/d;
    d+=2;
    s = -s;        
}
printf("%f\n", pi);
于 2009-01-19T04:10:17.960 回答
1

爪哇

    double pi=0,s=-1,i=1;
    for (;i<1E6;i+=2)pi+=((1d/i)*(s=-s)); 
    pi*=4;
于 2010-09-14T16:51:59.767 回答
0

VB 117 个字符

Function Pi()
  Dim t = 0D
  For n = 0 To 1000000
    t += Math.Pow(-1, n) / (2 * n + 1)
  Next
  Return 4 * t
End Function

VB LINQ 115 字符(省略不必要的续行)

Function Pi()
  Return 4 * Enumerable.Range(0, 1000000) _
             .Sum(Function(n) Math.Pow(-1, n) / (2 * n + 1))
End Function

然后调用:

Sub Main()
  Console.WriteLine("{0:n5}", Pi)
End Sub
于 2009-01-03T04:23:32.187 回答
0

另一个 VB 解决方案,使用相当酷的聚合语法:

Public ReadOnly Pi As Double = 4 * Aggregate i In Enumerable.Range(0, 100000) _
                                   Select (-1) ^ i / (i * 2 + 1) Into Sum()

仅表达式:74 个字符,没有不必要的空格。

于 2009-01-03T11:14:27.240 回答
0

Erlang,~126 个字符:

-module (pi).
-export ([pi/0]).

pi() -> 4 * pi(0,1,1).
pi(T,M,D) ->
    A = 1 / D,
    if A > 0.00001 
              -> pi(T+(M*A), M*-1, D+2);
        true  -> T
    end.
于 2009-01-04T11:45:42.700 回答
0

这是我用 C++ 编写的,可能是最长的方法:P

double pi(){
   bool add = true;
   double rPi = 0;
   for(long i = 1; i < 99999999; i=i+2)
   {
            double y = (double) i;
            double x = (double) 1;
            if(add)
            {
                   rPi = rPi + (x/y);
                   add = false;
            }
            else
            {
                    rPi = rPi - (x/y);
                    add = true;
            }
   }
            return (rPi * (double) 4);
   }
于 2009-01-05T14:04:59.987 回答
0

我只是在阅读了关于有争议的观点的话题中的采访问题后才写了这篇文章。这不是很漂亮,但我花了大约 3-4 分钟,我正在检查每个循环的准确性。C++。我明天会醒来并发布一个不糟糕的解决方案:)

double get_pi(int acc)
{

  double pi;
  double dynamicpart;
  int operationCoeff = 1;
  int denom = 3;
  while(1)
  { 
      dynamicpart =
         1/denom + operationCoeff*(denom+2);
      pi = 4*(1-dynamicpart);
      if(!(pi*acc*10-(int)pi*acc*10)) break;
)
      denom+=2;
      operationCoeff = -operationCoeff;
  }



}
于 2009-01-18T08:53:26.740 回答
0

呃....作为数值处理的一般规则,应该从最小项到最大项对系列求和,以避免精度损失的麻烦。所以在

fortran77

精简(248 个字符)

      function pi(n)
      pi=0.
      t=10**(-n-0.5)
      i=int(4/t)
      i=i/2
      s=-1.                     
      do 1 j=i*2+1,1,-2
         pi = pi + s/j
         s=-s
 1    continue
      pi=abs(pi)*4              
      return
      end

带脚手架和评论(600 个字符)

      program leibnitz

      n=5
      p=int(pi(n)*10.**n)/10.**n
      write(6,*)p 

      stop
      end

c     Returns pi computed to <n> digits by the leibniz formula
      function pi(n)
      pi=0.
c     find the smallest term we need by insuring that it is too small to
c     effect the interesting digits.
      t=10**(-n-0.5)
      i=int(4/t)
      i=i/2
      s=-1.                     ! sign of term, might be off by one, but
      do 1 j=i*2+1,1,-2
         pi = pi + s/j
         s=-s
 1    continue
      pi=abs(pi)*4              ! we fix the sign problem here
      return
      end

输出:

   3.1415901

它似乎适用于精度高达 6ish 的任意位数real。它没有针对速度或最少操作次数进行优化。

于 2009-08-19T00:46:32.077 回答
0

爪哇

void pi(){
    double x=1,y=1,d=1;
    for(;x<1E6;) { y=-y;d+=y/((2*x++)+1); }
    System.out.println(d*4);
}
于 2009-08-21T15:43:20.067 回答
0

Python 3(40 字节)

sum(8/(n*(n+2))for n in range(1,5**8,4))

此版本使用来自@Svante 的答案的优化。

打印 +7 字节

print(sum(8/(n*(n+2))for n in range(1,5**8,4)))

Python 2.x +1 字节

sum(8./(n*(n+2))for n in range(1,5**8,4))

打印 +6 字节

print sum(8./(n*(n+2))for n in range(1,5**8,4))

http://codepad.org/amtxUxKp

于 2009-10-11T21:13:20.517 回答
0

Lua,46 个字符

p=4 for i=3,9^6,4 do p=p-8/i/(i+2)end print(p)
于 2010-02-16T17:47:31.780 回答
0

PHP,99 个字符

$output =

${!${''}=function(){$pi=1;for($i=0;$i<pow(10,6);$i++){$pi+=($i%2?1:-1)/(3+$i*2);}return $pi*4;}}();

var_dump($output);

我想有一些我不知道的漂亮技巧可以减少这个答案!从这篇文章中获取了单行输出技巧。

于 2010-12-27T18:22:50.373 回答
0

R - 27 个字符

sum(4/seq(1,1e6,2)*c(1,-1))
于 2011-01-18T19:41:47.663 回答
-1

1 个字符:. 写在“MySuperDuperDomainSpecificLanguageThatOnlyReturnsThisOneAnswerAndNothingElse”中。

是的,这只是个玩笑,但说真的,除非您禁止 DSL,否则每场 Code Golf 比赛都可能由某个编写自己的语言的 goober 赢得,该语言使用一个字符来返回一个结果......

于 2009-12-03T14:39:13.050 回答