C#、Javaなどの数学指向コードの読みやすさを改善するにはどうすればよいですか?[閉まっている]


16

CプログラマとC#プログラマの両方として、私がC#で気に入らないことの1つは、数学関数の冗長性です。たとえば、サイン関数、コサイン関数、またはべき関数を使用する必要があるたびに、Math静的クラスを追加する必要があります。方程式自体が非常に単純な場合、これは非常に長いコードにつながります。データ型を型キャストする必要がある場合、問題はさらに悪化します。その結果、私の意見では、読みやすさが低下します。例えば:

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

単純に反対

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

これは、Javaのような他の言語にも当てはまります。

この質問に実際に解決策があるかどうかはわかりませんが、C#またはJavaプログラマーがMathコードの可読性を向上させるために使用するトリックがあるかどうかを知りたいと思います。ただし、C#/ Java / etc。MATLABなどの数学指向の言語ではないため、理にかなっています。ただし、場合によっては数学コードを記述する必要があり、読みやすくすることができれば素晴らしいことです。


具体的にはわかりませんが、パフォーマンスの低下はありますが、おそらく文字列で数学関数を定義できる代数ライブラリを見つけることができます。
raptortech97 14年


7
あなたは少し余分な冗長性を心配しますが、単項演算子で「*」の中に「+」を隠します-すべてブレースなし-優先順位が間違っていると思います。
mattnz 14年

1
それは単なる例でしたが、良い点
9a3eedi 14年

6
C#6.0では、次のように記述できますusing System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);
svick 14年

回答:


14

グローバルな静的関数を呼び出すローカル関数を定義できます。コンパイラーがラッパーをインライン化して、JITコンパイラーが実際の操作用のタイトなアセンブリコードを生成することを願っています。例えば:

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);
    }
}

また、一般的な数学演算を単一の演算にまとめる関数を作成することもできます。これにより、コード内で関数が好きsincosコード内に現れるインスタンスの数が最小限に抑えられ、グローバルな静的関数を呼び出す不格好さが目立たなくなります。例えば:

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)
}

ポイントと回転のレベルで作業しており、基礎となるトリガー関数は埋まっています。


...なぜ私はそれを考えなかった:)
9a3eedi 14年

クロスプラットフォームのソリューションであるため、これを正解としてマークしました。他のソリューションも同様に正しいです。私はこれを考えていなかったとは本当に信じられません:)それはあまりにも明白です
9a3eedi 14年

31

Javaの中には、特定のものをより冗長にするために利用できる多くのツールがあります、あなたはそれらを知っているだけです。この場合に役立つのは、staticインポートのチュートリアルですチュートリアルページウィキペディア)。

この場合、

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);
    }
}

非常にうまく動作します(ideone)。すべてのMathクラスの静的インポートを行うのは少し手間がかかりますが、大量の数学を実行している場合は、必要になる可能性があります。

静的インポートでは、静的フィールドまたはメソッドをこのクラスの名前空間にインポートし、パッケージ名を必要とせずに呼び出すことができます。これは多くの場合、import static org.junit.Assert.*;すべてのアサートを利用できることがわかっているJunitテストケースで見つかります。


素晴らしい答え。私はこの機能を知りませんでした。これはどのバージョンのJavaで可能ですか?
9a3eedi 14年

@ 9a3eedi最初にJava 1.5で利用可能になりました。

素敵なテクニック。私はそれが好きです。+1。
ランドールクック14年

1
@RandallCook Java 1.4では、人々はインターフェース内の定数にアクセスするために、すべてのクラスでpublic interface Constants { final static public double PI = 3.14; }次のようなpublic class Foo implements Constantsことをします。これは大きな混乱をもたらしました。そのため、1.5では、静的インポートが追加され、インターフェイスを実装せずに特定の定数と静的関数を取り込むことができました。

3
特定の機能を選択的にインポートできますimport static java.lang.Math.cos;
ラチェットフリーク14年

5

C#6.0では、静的インポートの機能を使用できます。

コードは次のとおりです。

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 
        }
    }
}

参照:Static Usingステートメント(AC#6.0言語プレビュー)

もう1つのC#6.0の「構文糖」機能は、静的を使用することの導入です。この機能を使用すると、静的メソッドを呼び出すときに、型への明示的な参照を削除できます。さらに、静的を使用すると、名前空間内のすべての拡張メソッドではなく、特定のクラスの拡張メソッドのみを導入できます。

編集:Visual Studio 2015、2015年1月にリリースされたCTP以降、静的インポートには明示的なキーワードが必要staticです。お気に入り:

using static System.Console;

4

ここでの他の良い答えに加えて、かなりの数学的な複雑さ(平均的なユースケースではなく、おそらくいくつかの財政的または学術的なプロジェクト)の状況にもDSLをお勧めします。

XtextなどのDSL生成ツールを使用すると、独自の単純化された数学文法を定義できます。これにより、式のJava(またはその他の言語)表現を含むクラスを生成できます。

DSL式:

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

生成された出力:

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));
    }
}

このような単純な例では、文法とEclipseプラグインを作成する利点は価値がありませんが、より複雑なプロジェクトでは、特にDSLによりビジネススタッフや学術研究者が正式なドキュメントを快適に維持できる場合、大きな利点が得られますそして、彼らの仕事がプロジェクトの実装言語に正確に翻訳されたことを保証されます。


8
はい、一般的に、そして定義により、特定のドメインで作業する場合、DSLが役立つ場合があります。ただし、このDSLが存在しない場合、またはニーズに合わない場合は、それを維持する必要があり、問題が生じる可能性があります。また、特定の質問(「Mathクラスを毎回記述せずにsin、cos、...メソッド/関数を使用する方法」)については、DSLは多分大きなソリューションです。
mgoeminne 14年

4

C#では、拡張メソッドを使用できます。

以下は、「後置」表記法に慣れると、かなり読みやすくなります。

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();

残念ながら、ここで負の数を処理する場合、演算子の優先順位は少し面倒になります。Math.Cos(-X)代わりに計算する-Math.Cos(X)場合は、数値を括弧で囲む必要があります。

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

1
ちなみに、これは拡張プロパティの良いユースケースになり、プロパティをメソッドとして悪用する正当なユースケースにさえなります!
ヨルグWミットタグ14年

これが私が思ったことです。x.Sin()多少の調整が必要になりますが、拡張方法を悪用し、個人的にはこれが最初の傾向です。
WernerCD 14年

2

C#:Randall Cookの回答のバリエーションは、拡張メソッドよりもコードの数学的な「外観」を維持するため、私は気に入っています。ラッパーを使用しますが、呼び出しではラップではなく関数参照を使用します 個人的にはコードがきれいに見えると思いますが、基本的には同じことをしています。

私は、Randallのラップされた関数、私の関数参照、直接呼び出しを含む小さなLINQPadテストプログラムをノックアップしました。

関数が参照する呼び出しは、基本的に直接呼び出しと同じ時間がかかります。ラップされた関数は一貫して遅くなります-大量ではありませんが。

コードは次のとおりです。

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);
    }
}

結果:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096

1

Scalaを使用してください!シンボリック演算子を定義でき、メソッドに括弧を必要としません。これにより、数学方法で解釈しやすくなります。

たとえば、ScalaとJavaでの同じ計算は次のようになります。

// 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));
}

これはすぐに追加されます。


2
ScalaはCLRでは使用できず、JVMでのみ使用できます。したがって、実際にはC#の実行可能な代替手段ではありません。
ベンラッジャーズ

@benrudgers-C#はJVMで実行されないため、実際にはJavaの実行可能な代替ではありません。質問は、それがCLRでなければならないことを指定していません!
レックスカー

私はLudditeかもしれませんが、「*」の代わりに「ドット」の2つの余分な文字を使用すると、コードがより明確になるという利点があり、少々の代償を払うように思えます。それでも、良い答えです。
user949300
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.