C #, Java ve benzeri matematik odaklı kod okunabilirliğini artırmak için ne yapabilirim? [kapalı]


16

Hem bir C programcısı hem de bir C # programcısı olarak, C # hakkında sevmediğim şeylerden biri ayrıntılı matematik işlevlerinin nasıl olduğudur. Örneğin Sin, kosinüs veya güç işlevini her kullanmanız gerektiğinde, Math statik sınıfının başına başlamanız gerekir. Bu, denklemin kendisi oldukça basit olduğunda çok uzun bir koda yol açar. Veri türlerini tahmin etmeniz gerekiyorsa sorun daha da kötüleşir. Sonuç olarak, bence, okunabilirlik acı çekiyor. Örneğin:

double x =  -Math.Cos(X) * Math.Sin(Z) + Math.Sin(X) * Math.Sin(Y) * Math.Cos(Z);

Aksine

double x = -cos(X) * sin(Z) + sin(X) * sin(Y) * cos(Z);

Bu, Java gibi diğer dillerde de geçerlidir.

Bu sorunun aslında bir çözümü olup olmadığından emin değilim, ancak matematik kodunun okunabilirliğini artırmak için C # veya Java programcılarının kullandığı herhangi bir hile olup olmadığını bilmek istiyorum. Ancak C # / Java / etc farkında. MATLAB veya benzeri matematik odaklı diller değildir, bu yüzden mantıklıdır. Ancak bazen matematik kodunu yazmaya ihtiyaç duyulur ve daha okunabilir hale gelmesi harika olur.


Herhangi bir özel bilmiyorum, ama muhtemelen bazı performans cezası olsa da, dizeleri ile matematik fonksiyonları tanımlamak için izin verecek bir cebir kütüphanesi bulabilirsiniz.
raptortech97


7
Biraz ekstra ayrıntıdan endişe duyuyorsunuz, ancak tekli operatörler ile '*' arasında mutlu bir şekilde '+' gizleyin - hepsi parantez olmadan - Önceliklerinizin yanlış olduğundan şüpheleniyorum.
mattnz

1
Bu sadece bir örnek, ama iyi bir nokta
9a3eedi

6
C # 6.0, sen yazma mümkün olacaktır: using System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);.
svick

Yanıtlar:


14

Genel statik işlevleri çağıran yerel işlevleri tanımlayabilirsiniz. Umarım derleyici sargıları sıraya koyar ve sonra JIT derleyicisi gerçek işlemler için sıkı montaj kodu üretir. Örneğin:

class MathHeavy
{
    private double sin(double x) { return Math.sin(x); }
    private double cos(double x) { return Math.cos(x); }

    public double foo(double x, double y)
    {
        return sin(x) * cos(y) - cos(x) * sin(y);
    }
}

Ayrıca, ortak matematik işlemlerini tek işlemlerde birleştiren işlevler de oluşturabilirsiniz. Bu, işlevlerin kodunuzda olduğu sinve cosgöründüğü örnek sayısını en aza indirir , böylece genel statik işlevleri çağırmanın zorluğunu daha az fark edilir hale getirir. Örneğin:

public Point2D rotate2D(double angle, Point2D p)
{
    double x = p.x * Math.cos(angle) - p.y * Math.sin(angle);
    double y = p.x * Math.sin(angle) + p.y * Math.cos(angle);

    return new Point2D(x, y)
}

Noktalar ve dönüşler seviyesinde çalışıyorsunuz ve alttaki tetikleme fonksiyonları gömülü.


... neden böyle düşünmedim :)
9a3eedi

Bunu doğru cevap olarak işaretledim çünkü yeterince basit bir platformlar arası çözüm. Diğer çözümler de doğrudur. Gerçekten bu olsa düşünmedim inanamıyorum :) çok açık
9a3eedi

31

Java içinde, bazı şeyleri daha az ayrıntılı hale getirmek için birçok araç vardır, bunların farkında olmanız yeterlidir. Bu durumda yararlı olan bir tanesi, staticiçe aktarma işlemidir ( eğitim sayfası , wikipedia ).

Bu durumda,

import static java.lang.Math.*;

class Demo {
    public static void main (String[] args) {
        double X = 42.0;
        double Y = 4.0;
        double Z = PI;

        double x =  -cos(X) * sin(Z) + sin(X) * sin(Y) * cos(Z);
        System.out.println(x);
    }
}

oldukça güzel çalışır ( ideone ). Tüm Math sınıfının statik bir ithalatını yapmak biraz ağırdır , ancak çok fazla matematik yapıyorsanız, bunun için çağrılabilir.

Statik içe aktarma, bu sınıfın ad alanına statik bir alan veya yöntem içe aktarmanıza ve paket adını gerektirmeden çağırmanıza izin verir. Nerede Sık sık JUnit testi durumlarda bu bulmak import static org.junit.Assert.*;tüm almak bulunmuştur iddia kullanılabilir.


Mükemmel cevap. Bu özelliğin farkında değildim. Bunun altında hangi Java sürümü mümkündür?
9a3eedi

@ 9a3eedi İlk olarak Java 1.5 sürümünde kullanıma sunuldu.

Güzel teknik. Bunu sevdim. +1.
Randall Cook

1
@RandallCook Java 1.4 gün içinde, insanlar arabirimdeki sabitlere erişmek için tüm sınıflarda public interface Constants { final static public double PI = 3.14; }ve sonra public class Foo implements Constantstüm şeyleri yaparlardı . Bu büyük bir karmaşa yarattı. Bu nedenle, 1.5 ile, bir arabirimi uygulamak zorunda kalmadan belirli sabitleri ve statik fonksiyonları çekmeye izin vermek için statik içe aktarma eklendi.

3
Belirli işlevleri seçerek içe aktarabilirsinizimport static java.lang.Math.cos;
cırcır ucube

5

C # 6.0 ile Statik İçe Aktarma özelliğini kullanabilirsiniz.

Kodunuz şöyle olabilir:

using static System.Math;
using static System.Console;
namespace SomeTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            double X = 123;
            double Y = 5;
            double Z = 10;
            double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);
            WriteLine(x); //Without System, since it is imported 
        }
    }
}

Bakınız: Statik Kullanım İfadeleri (AC # 6.0 Dil Önizlemesi)

Bir diğer C # 6.0 “sözdizimsel şeker” özelliği statik kullanımın tanıtılmasıdır. Bu özellik sayesinde, statik bir yöntem çağrılırken türe açık bir başvuruyu ortadan kaldırmak mümkündür . Ayrıca, static kullanmak, bir ad alanındaki tüm uzantı yöntemleri yerine yalnızca belirli bir sınıftaki uzantı yöntemlerini tanıtmanıza olanak tanır.

EDIT: Ocak 2015'te yayımlanan Visual Studio 2015, CTP'den beri statik içe aktarma için açık anahtar kelime gerekir static. sevmek:

using static System.Console;

4

Buradaki diğer iyi cevaplara ek olarak, önemli matematiksel karmaşıklığa sahip durumlar için bir DSL önerebilirim (ortalama kullanım durumları değil, belki de bazı finansal veya akademik projeler).

Xtext gibi bir DSL oluşturma aracıyla , formüllerinizin Java (veya başka bir dil) temsilini içeren bir sınıf oluşturabilen kendi basitleştirilmiş matematiksel dilbilginizi tanımlayabilirsiniz.

DSL İfadesi:

domain GameMath {
    formula CalcLinearDistance(double): sqrt((x2 - x1)^2 + (y2 - y1)^2)
}

Üretilen Çıktı:

public class GameMath {
    public static double CalcLinearDistance(int x1, int x2, int y1, int y2) {
        return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
    }
}

Böyle basit bir örnekte, dilbilgisi ve Eclipse eklentisini oluşturmanın faydaları faydalı olmayacaktır, ancak daha karmaşık projeler için, özellikle DSL, iş adamlarının veya akademik araştırmacıların resmi belgeleri rahat bir şekilde tutmasını sağladıysa büyük faydalar sağlayabilir. ve çalışmalarının projenin uygulama diline doğru tercüme edildiğinden emin olun.


8
Evet, genel olarak ve tanım gereği, belirli bir alanda çalışırken DSL yararlı olabilir. Bununla birlikte, bu DSL yoksa veya ihtiyaçlara uymuyorsa, onu korumak zorundasınız , bu da sorunlu olabilir. Ayrıca, belirli bir soru için ("her zaman Math sınıfını yazmadan günah, cos,… yöntemlerini / fonksiyonlarını nasıl kullanabilirim"), bir DSL belki de çok büyük bir çözümdür.
mgoeminne

4

C # 'da uzantı yöntemlerini kullanabilirsiniz.

"Postfix" notasyonuna alıştıktan sonra aşağıdakiler oldukça iyi okunur:

public static class DoubleMathExtensions
{
    public static double Cos(this double n)
    {
        return Math.Cos(n);
    }

    public static double Sin(this double n)
    {
        return Math.Sin(n);
    }

    ...
}

var x =  -X.Cos() * Z.Sin() + X.Sin() * Y.Sin() * Z.Cos();

Ne yazık ki operatör önceliği, burada negatif sayılarla uğraşırken işleri biraz daha çirkin hale getiriyor. Math.Cos(-X)Bunun yerine hesaplamak -Math.Cos(X)istiyorsanız, sayıyı parantez içine almanız gerekir:

var x = (-X).Cos() ...

1
Bu arada, bu, uzantı özellikleri için iyi bir kullanım durumu ve hatta yöntemleri yöntem olarak kötüye kullanmak için meşru bir kullanım durumu yapar!
Jörg W Mittag

Ben de öyle düşünmüştüm. x.Sin()bazı ayarlamalar yapardı, ancak uzatma yöntemlerini kötüye kullanıyorum ve bu şahsen ilk eğilimim olacak.
WernerCD

2

C #: Genişletme yöntemlerinden daha fazla kod matematiksel "görünümünü" koruduğu için sevdiğim Randall Cook'ın cevabı üzerinde bir varyasyon, bir sarıcı kullanmak ama aramalar yerine onları sarmak için işlev başvuruları kullanmaktır. Şahsen kodun daha temiz görünmesini sağlıyor, ancak temelde aynı şeyi yapıyor.

Randall'ın sarılmış fonksiyonları, fonksiyon referanslarımı ve doğrudan çağrıları içeren küçük bir LINQPad test programını devirdim.

Çağrılara başvurulan işlev temel olarak doğrudan çağrılarla aynı zamanı alır. Sarılı işlevler, büyük miktarda olmasa da, sürekli olarak daha yavaştır.

İşte kod:

void Main()
{
    MyMathyClass mmc = new MyMathyClass();

    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    for(int i = 0; i < 50000000; i++)
        mmc.DoStuff(1, 2, 3);

    "Function reference:".Dump();
    sw.Elapsed.Dump();      
    sw.Restart();

    for(int i = 0; i < 50000000; i++)
        mmc.DoStuffWrapped(1, 2, 3);

    "Wrapped function:".Dump();
    sw.Elapsed.Dump();      
    sw.Restart();

    "Direct call:".Dump();
    for(int i = 0; i < 50000000; i++)
        mmc.DoStuffControl(1, 2, 3);

    sw.Elapsed.Dump();
}

public class MyMathyClass
{
    // References
    public Func<double, double> sin;
    public Func<double, double> cos;
    public Func<double, double> tan;
    // ...

    public MyMathyClass()
    {
        sin = System.Math.Sin;
        cos = System.Math.Cos;
        tan = System.Math.Tan;
        // ...
    }

    // Wrapped functions
    public double wsin(double x) { return Math.Sin(x); }
    public double wcos(double x) { return Math.Cos(x); }
    public double wtan(double x) { return Math.Tan(x); }

    // Calculation functions
    public double DoStuff(double x, double y, double z)
    {
        return sin(x) + cos(y) + tan(z);
    }

    public double DoStuffWrapped(double x, double y, double z)
    {
        return wsin(x) + wcos(y) + wtan(z);
    }

    public double DoStuffControl(double x, double y, double z)
    {
        return Math.Sin(x) + Math.Cos(y) + Math.Tan(z);
    }
}

Sonuçlar:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096

1

Scala kullan! Sembolik operatörler tanımlayabilirsiniz ve yöntemleriniz için parenlere ihtiyacınız yoktur. Bu matematik yapar şekilde yorumlamak daha kolay.

Örneğin, Scala ve Java'daki aynı hesaplama şöyle olabilir:

// Scala
def angle(u: Vec, v: Vec) = (u*v) / sqrt((u*u)*(v*v))

// Java
public double angle(u: Vec, v: Vec) {
  return u.dot(v) / sqrt(u.dot(u)*v.dot(v));
}

Bu oldukça hızlı bir şekilde toplanır.


2
Scala CLR'de mevcut değil, sadece JVM. Dolayısıyla C # 'a gerçekten uygun bir alternatif değildir.
ben rudgers

@benrudgers - C #, JVM'de çalışmaz, bu yüzden sorunun da sorduğu Java'ya gerçekten uygun bir alternatif değildir. Soru CLR olması gerektiğini belirtmiyor!
Rex Kerr

Belki ben bir Luddite'ım, ama kodun daha net olmasıyla "*" yerine "nokta" için iki ekstra karakter ödemek için küçük bir fiyat gibi görünüyor. Yine de iyi bir cevap.
user949300
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.