For my first Raku program, I thought it might be a fun challange to port over a program I'd written in C# about two years ago, and I was right: it was.
There's just one problem: while the C# program runs in 5 seconds, the Raku program takes nearly 5 minutes. I think the Raku program is doing more or less the same as the C# code, and so it should be equally fast (or equally slow, depending on how you look at it).
Here's my code:
use v6;
class Point {
has Num $.x;
has Num $.y;
method is-inside-unit-circle(--> Bool) {
$!x²+$!y² <= 1;
}
}
sub random-points(Int $count --> Seq) {
gather for 0..^$count {
take Point.new(x => 1.rand.Num, y => 1.rand.Num);
}
}
sub compute-pi(Int $batch --> Seq) {
my Num $total = 0e0;
my Num $count = 0e0;
gather for 0..^∞ {
my Seq $pointsInside = random-points($batch).grep: *.is-inside-unit-circle;
$total += $batch;
$count += $pointsInside.elems;
my Num $ratio = $count / $total;
take $ratio * 4;
}
}
{ say "π ≈ $_" } for compute-pi(100_000).head(500);
And here's part of the output:
% time raku estimation.raku
π ≈ 3.12912
π ≈ 3.12874
π ≈ 3.1326533333333333
…
π ≈ 3.1419357429718877
π ≈ 3.1419323446893785
π ≈ 3.14193696
raku estimation.raku 272,98s user 1,78s system 100% cpu 4:34,76 total
My two questions are:
- How can I make it faster?
- Is my code idiomatic, and how could I make it more idiomatic?
For completeness' sake, here's the C# code:
using System;
using System.Collections.Generic;
using System.Linq;
internal class Program
{
internal readonly struct Point
{
private readonly double _x;
private readonly double _y;
internal Point(double x, double y)
{
_x = x;
_y = y;
}
internal bool IsInsideUnitCircle()
{
return Math.Pow(_x, 2) + Math.Pow(_y, 2) <= 1;
}
}
internal class PointGenerator
{
private readonly Random _random;
public PointGenerator() => _random = new Random();
public IEnumerable<Point> GeneratePoints(int count)
{
for (var i = 0; i < count; i++)
{
yield return new Point(_random.NextDouble(), _random.NextDouble());
}
}
}
private static IEnumerable<double> ComputePi(int batch)
{
var pointGenerator = new PointGenerator();
var total = 0.0;
var count = 0.0;
while (true)
{
var pointsInside = pointGenerator.GeneratePoints(batch)
.Where(point => point.IsInsideUnitCircle());
total += batch;
count += pointsInside.Count();
var ratio = count / total;
yield return ratio * 4;
}
}
private static void Main()
{
foreach (var estimate in ComputePi(100_000).Take(500))
{
Console.WriteLine($"π ≈ {estimate}");
}
}
}
And part of its output:
% time dotnet run -c Release
π ≈ 3,13948
π ≈ 3,13904
π ≈ 3,138786666666667
…
π ≈ 3,1415373493975904
π ≈ 3,14154501002004
π ≈ 3,14155416
dotnet run -c Release 5,43s user 0,30s system 103% cpu 5,555 total