Apa yang dapat dilakukan untuk meningkatkan keterbacaan kode berorientasi matematika dalam C #, Java, dan yang serupa? [Tutup]

16

Baik sebagai programmer C dan programmer C #, salah satu hal yang saya tidak suka tentang C # adalah bagaimana fungsi matematika verbose. Setiap kali Anda harus menggunakan fungsi Sin, cosine, atau power, misalnya, Anda harus menambahkan kelas statis Matematika. Ini mengarah ke kode yang sangat panjang ketika persamaan itu sendiri cukup sederhana. Masalahnya menjadi lebih buruk jika Anda perlu mengetikkan tipe data. Akibatnya, menurut pendapat saya, keterbacaan menderita. Sebagai contoh:

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

Berbeda dengan sederhana

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

Ini juga terjadi di bahasa lain seperti Jawa.

Saya tidak yakin apakah pertanyaan ini benar-benar memiliki solusi, tetapi saya ingin tahu apakah ada trik C # atau programmer Java yang digunakan untuk meningkatkan keterbacaan kode Matematika. Namun saya menyadari bahwa C # / Java / etc. bukan bahasa yang berorientasi matematika seperti MATLAB atau sejenisnya, jadi masuk akal. Tetapi kadang-kadang orang masih perlu menulis kode matematika dan itu akan bagus jika seseorang bisa membuatnya lebih mudah dibaca.

9a3eedi
sumber
Saya tidak tahu secara spesifik, tetapi Anda mungkin bisa menemukan perpustakaan aljabar yang akan memungkinkan Anda untuk mendefinisikan fungsi matematika dengan string, meskipun akan ada beberapa penalti kinerja.
raptortech97
7
Anda khawatir tentang sedikit verbositas ekstra namun dengan senang hati menyembunyikan tanda '+' di antara '*' dengan operator unary - semuanya tanpa kawat gigi - saya menduga Anda memiliki prioritas yang salah.
mattnz
1
Itu hanya sebuah contoh, tetapi poin yang bagus
9a3eedi
6
Dalam C # 6.0, Anda akan dapat menulis: using System.Math; … double x = -Cos(X) * Sin(Z) + Sin(X) * Sin(Y) * Cos(Z);.
svick

Jawaban:

14

Anda dapat menentukan fungsi lokal yang memanggil fungsi statis global. Semoga kompiler akan memberikan pembungkus sejajar, dan kemudian kompiler JIT akan menghasilkan kode perakitan yang ketat untuk operasi yang sebenarnya. Sebagai contoh:

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

Anda juga bisa membuat fungsi yang menggabungkan operasi matematika umum menjadi operasi tunggal. Ini akan meminimalkan jumlah kejadian di mana fungsi suka sindan cosmuncul dalam kode Anda, sehingga membuat kekeraskepalaan memanggil fungsi statis global kurang terlihat. Sebagai contoh:

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

Anda bekerja pada level titik dan rotasi, dan fungsi trigonometri yang mendasarinya dikubur.

Randall Cook
sumber
... mengapa saya tidak memikirkan hal itu :)
9a3eedi
Saya menandai ini sebagai jawaban yang benar karena ini adalah solusi lintas platform yang cukup sederhana. Solusi lain juga benar. Saya benar-benar tidak percaya saya tidak memikirkan hal ini :) itu terlalu jelas
9a3eedi
31

Di Jawa, ada banyak alat yang tersedia untuk membuat hal-hal tertentu kurang bertele-tele, Anda hanya perlu menyadarinya. Salah satu yang berguna dalam hal ini adalah staticimpor ( halaman tutorial , wikipedia ).

Pada kasus ini,

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

berjalan cukup baik ( ideone ). Agak berat untuk melakukan impor statis semua kelas Matematika, tetapi jika Anda melakukan banyak matematika, maka mungkin diperlukan.

Impor statis memungkinkan Anda untuk mengimpor bidang atau metode statis ke namespace kelas ini dan menjalankannya tanpa memerlukan nama paket. Anda sering menemukan ini di Junit uji kasus di mana import static org.junit.Assert.*;ditemukan untuk mendapatkan semua menegaskan tersedia.


sumber
Jawaban yang sangat bagus. Saya tidak mengetahui fitur ini. Versi Java apa yang memungkinkan?
9a3eedi
@ 9a3eedi Ini pertama kali tersedia di Jawa 1.5.
Teknik yang bagus. Saya suka itu. +1.
Randall Cook
1
@Rallallook Di Java 1.4 hari, orang akan melakukan hal-hal seperti public interface Constants { final static public double PI = 3.14; }dan kemudian public class Foo implements Constantsdi semua kelas untuk mendapatkan akses ke konstanta di antarmuka. Ini membuat kekacauan besar . Jadi, dengan 1.5, impor statis ditambahkan untuk memungkinkan menarik konstanta tertentu dan fungsi statis tanpa harus mengimplementasikan antarmuka.
3
Anda dapat mengimpor fungsi tertentu secara selektifimport static java.lang.Math.cos;
ratchet freak
5

Dengan C # 6.0 Anda dapat menggunakan fitur Impor Statis.

Kode Anda bisa:

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

Lihat: Pernyataan Penggunaan Statis (Pratinjau Bahasa AC # 6.0)

Fitur “gula sintaksis” C # 6.0 lainnya adalah pengenalan penggunaan statis. Dengan fitur ini, dimungkinkan untuk menghilangkan referensi eksplisit ke tipe ketika menggunakan metode statis. Selain itu, menggunakan statis memungkinkan Anda memperkenalkan hanya metode ekstensi pada kelas tertentu, daripada semua metode ekstensi dalam ruang nama.

EDIT: Sejak Visual Studio 2015, CTP dirilis pada Januari 2015, impor statis memerlukan kata kunci eksplisit static. Suka:

using static System.Console;
Habib
sumber
4

Selain jawaban baik lainnya di sini, saya mungkin juga merekomendasikan DSL untuk situasi dengan kompleksitas matematika yang substansial (bukan kasus penggunaan rata-rata, tetapi mungkin beberapa proyek keuangan atau akademik).

Dengan alat generasi DSL seperti Xtext , Anda bisa menentukan tata bahasa matematika sederhana Anda sendiri, yang pada gilirannya dapat menghasilkan kelas dengan berisi representasi Java (atau bahasa lain) dari rumus Anda.

Ekspresi DSL:

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

Output yang dihasilkan:

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

Dalam contoh sederhana seperti itu, manfaat membuat tata bahasa dan plugin Eclipse tidak akan bermanfaat, tetapi untuk proyek yang lebih rumit, itu bisa menghasilkan manfaat besar, terutama jika DSL memungkinkan pelaku bisnis atau peneliti akademis untuk menjaga dokumen formal dengan nyaman bahasa, dan yakinlah bahwa pekerjaan mereka diterjemahkan secara akurat ke dalam bahasa implementasi proyek.

Dan1701
sumber
8
Ya, secara umum, dan menurut definisi, DSL mungkin berguna ketika Anda bekerja di domain tertentu. Namun, jika DSL ini tidak ada, atau jika tidak sesuai dengan kebutuhan, Anda harus memeliharanya , yang mungkin bermasalah. Juga, untuk pertanyaan spesifik ("Bagaimana saya bisa menggunakan metode / fungsi sin, cos, ... tanpa menulis kelas Matematika setiap saat"), DSL mungkin merupakan solusi yang terlalu besar.
mgoeminne
4

Di C # Anda bisa menggunakan metode ekstensi.

Di bawah ini terbaca cukup baik setelah Anda terbiasa dengan notasi "postfix":

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

Sayangnya operator diutamakan membuat hal-hal sedikit lebih buruk ketika berhadapan dengan angka negatif di sini. Jika Anda ingin menghitung Math.Cos(-X)alih-alih, -Math.Cos(X)Anda harus menyertakan nomor dalam tanda kurung:

var x = (-X).Cos() ...
rjnilsson
sumber
1
Kebetulan, ini akan membuat kasus penggunaan yang bagus untuk properti ekstensi, dan bahkan kasus penggunaan yang sah untuk menyalahgunakan properti sebagai metode!
Jörg W Mittag
Inilah yang saya pikirkan. x.Sin()akan mengambil beberapa penyesuaian, tetapi saya menyalahgunakan metode ekstensi dan ini akan, secara pribadi, kecenderungan pertama saya.
WernerCD
2

C #: Variasi pada jawaban Randall Cook , yang saya suka karena mempertahankan tampilan "matematis" kode lebih dari metode ekstensi, adalah dengan menggunakan pembungkus tetapi menggunakan referensi fungsi untuk panggilan daripada membungkusnya. Saya pribadi berpikir itu membuat kode terlihat lebih bersih, tetapi pada dasarnya melakukan hal yang sama.

Saya mengetuk program uji LINQPad kecil termasuk fungsi dibungkus Randall, referensi fungsi saya, dan panggilan langsung.

Fungsi panggilan referensi pada dasarnya mengambil waktu yang sama dengan panggilan langsung. Fungsi yang dibungkus secara konsisten lebih lambat - meskipun tidak dalam jumlah besar.

Ini kodenya:

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

Hasil:

Function reference:
00:00:06.5952113

Wrapped function:
00:00:07.2570828

Direct call:
00:00:06.6396096
Whelkaholism
sumber
1

Gunakan Scala! Anda dapat menentukan operator simbolik, dan Anda tidak perlu parens untuk metode Anda. Hal ini membuat matematika cara yang lebih mudah untuk menafsirkan.

Misalnya, perhitungan yang sama di Scala dan Java bisa jadi seperti:

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

Ini bertambah dengan cukup cepat.

Rex Kerr
sumber
2
Scala tidak tersedia di CLR, hanya JVM. Jadi itu bukan alternatif yang layak untuk C #.
ben rudgers
@benrudgers - C # tidak berjalan di JVM, jadi ini bukan alternatif yang layak untuk Java, yang juga ditanyakan oleh pertanyaan itu. Pertanyaannya tidak menentukan bahwa itu harus CLR!
Rex Kerr
Mungkin saya seorang Luddite, tetapi dua karakter tambahan untuk "dot" bukan "*", dengan manfaat bahwa kode lebih jelas, tampaknya harga yang murah untuk dibayar. Tetap saja, jawaban yang bagus.
user949300