2013-05-03 22 views
10

Tôi đang làm việc trên một thư viện toán học đơn giản cho các mục đích giáo dục và tôi đã triển khai struct đại diện cho Rational Number. Rất mã cơ bản cho thấy các lĩnh vực cốt lõi của cấu trúc là:Giá trị mặc định cho Rational Number struct

public struct RationalNumber 
{ 
    private readonly long numerator; 
    private readonly long denominator; 
    private bool isDefinitelyCoprime; 
    private static RationalNumber zero = 0; 

    public RationalNumber(long numerator, long denominator) 
    { 
     this.numerator = numerator; 
     this.denominator = denominator; 
     this.isDefinitelyCoprime = false; 
    } 

    ... 
} 

Hiện nay tôi đang thực hiện một RationalMatrix đó, như bạn đã có thể đoán, sẽ được tạo thành từ RationalNumber gõ yếu tố.

Ma trận hữu ích mà tôi đang tạo một trình tạo tĩnh cho là ma trận danh tính. Mã là như sau:

public static RationalMatrix GetIdentityMatrix(int dimension) 
{ 
    RationalNumber[,] values = new RationalNumber[dimension, dimension]; 

    for (int i = 0; i < dimension; i++) 
     values[i, i] = 1; 

    return new RationalMatrix(values); 
} 

Vấn đề là điều này sẽ không làm việc vì các giá trị mặc định của RationalNumber tôi không phải là 0/1 nhưng 0/0 mà là một loại đặc biệt của giá trị (Indeterminate form).

Rõ ràng một giải pháp rất đơn giản và nó chỉ đơn giản là thay đổi phương pháp để:

public static RationalMatrix GetIdentityMatrix(int dimension) 
{ 
    RationalNumber[,] values = new RationalNumber[dimension, dimension]; 

    for (int i = 0; i < dimension; i++) 
     for (int j = i+1 ; j < dimension; j++) 
     { 
      values[i, i] = 1; 
      values[i, j] = RationalNumber.Zero; 
      values[j, i] = RationalNumber.Zero; 
     } 

     return new RationalMatrix(values); 
} 

Nhưng điều này dường như bằng cách nào đó một sự lãng phí công sức như tôi về cơ bản khởi tạo các giá trị của toàn bộ mảng hai lần. Tôi nghĩ rằng nó sẽ thanh lịch hơn bằng cách nào đó làm cho giá trị mặc định là RationalNumber bằng 0/1. Điều này sẽ dễ dàng thực hiện nếu RationalNumber là class, nhưng tôi không thể nghĩ ra cách để thực hiện khi đó là struct. Tôi có thiếu điều gì đó hiển nhiên hay không có cách nào để tránh có giá trị mặc định của tôi là 0/0?

Tôi muốn chỉ ra rằng tôi không quan tâm chút nào về hiệu suất mã (nếu đây là nút cổ chai của tôi thì tôi đã vượt quá mục tiêu của tôi). Tôi chỉ tò mò muốn biết nếu có một số cấu trúc (không biết đến tôi) cho phép bạn áp đặt các giá trị mặc định tùy ý trong một struct.

EDIT: Typos

EDIT 2: Mở rộng phạm vi của câu hỏi

OK, có vẻ như không có cách nào để áp đặt giá trị mặc định tùy ý trong một struct từ đầu vào tôi nhận được và từ kết luận của riêng tôi dựa trên kiến ​​thức C# giới hạn của tôi.

Ai đó có thể cho tôi manh mối về lý do tại sao cấu trúc phải hoạt động theo cách này? Có phải vì một lý do hoặc nó được thực hiện theo cách này bởi vì không ai nghĩ rằng để xác định các tùy chọn để xác định giá trị mặc định?

+1

Khá ghê tởm, nhưng bạn có thể thay đổi 'mẫu số' thành' mẫu sốMinusOne'! Vì vậy, khi 'denominatorMinusOne == 0' có nghĩa là' mẫu số == 1'. Tôi thấy khó để đề xuất điều này như một ý tưởng hay! –

+0

@DavidHeffernan Có lẽ không phải 'mẫu sốMinusOne' vì nó ngụ ý một phạm vi khác, nhưng có thể là' denominatorXorOne'? Thậm chí nếu nó không phải là một ý tưởng hay, nó có thể là ý tưởng _only_ hoạt động! – Rawling

+0

Bạn có thể thêm một cái gì đó như 'bool riêng isInitialized;' và sử dụng tài sản đó một cách sáng tạo trong các phương pháp của bạn. – Arvo

Trả lời

0

Nó sẽ được tốt đẹp để cung cấp một constructor mặc định cho struct:

public RationalNumber() 
{ 
    this.numerator = 0; 
    this.denominator = 1; 
    this.isDefinitelyCoprime = false; 
} 

Tuy nhiên điều này là không được phép. Cũng không phải bạn có thể khởi tạo instance.

Câu trả lời đơn giản là bạn phải chấp nhận rằng trường nội bộ phải initalise bằng không, nhưng điều này không có nghĩa là hành vi phải tuân theo.

public struct Rational 
    { 
     private int _numerator; 
     private int _denominator; 
     public Rational(int numerator, int denominator) 
     { 
      // Check denominator is positive. 
      if(denominator < 0){ 
        denominator *= -1; 
        numerator *= -1; 
      } 
      _numerator = numerator; 
      _denominator = denominator== 0? -1: 
       denominator; 
     } 
     public int Numerator 
     { 
      get { return _numerator; } 
     } 
     public int Denominator 
     { 
      get { return 
       _denominator == 0?1: 
       _denominator == -1?0: 
       _denominator; } 
     } 
    } 

(Lưu ý: Tôi thực sự rất ngạc nhiên khi thấy bạn không thể có initalisers tĩnh trong cấu trúc!)

+3

Từ khi nào điều này được cho phép? – jure

+2

-1 Điều đó sẽ không hoạt động. Việc tạo một mảng 'RationalNumber' sẽ không gọi hàm tạo này và vì vậy nó không được cho phép: *" Structs không thể chứa các hàm tạo không có tham số rõ ràng "* –

+3

Chỉnh sửa thứ hai của bạn sai một lần nữa. Không có bộ khởi tạo nào trong cấu trúc ... – jure

5

Nếu bạn không phải phân biệt giữa giá trị 0/0 và 0/N không xác định, thì bạn có thể xử lý tất cả 0/N bằng 0. Tức là, tất cả các số 0 đều bằng nhau, có ý nghĩa (0/2 bằng 0/1), và tất cả các phân số bằng 0 đều bằng nhau, vì vậy 1/0 == 2/0.

public struct RationalNumber : IEquatable<RationalNumber> 
{ 
    private readonly long numerator; 
    private readonly long denominator; 

    public RationalNumber(long numerator, long denominator) 
    { 
     this.numerator = numerator; 
     this.denominator = denominator; 
    } 

    public bool IsZero 
    { 
     get { return numerator == 0; } 
    } 

    public bool IsInvalid 
    { 
     get { return denominator == 0 && numerator != 0; } 
    } 

    public bool Equals(RationalNumber r) 
    { 
     if (r.IsZero && IsZero) 
     return true; 
     if (r.IsInvalid && IsInvalid) 
     return true; 
     return denominator == r.denominator && numerator == r.numerator; 
    } 

    public bool Equals(object o) 
    { 
     if (!(o is RationalNumber)) 
     return false; 
     return Equals((RationalNumber)o); 
    } 

    public int GetHashCode() 
    { 
     if (IsZero) 
     return 0; 
     if (IsInvalid) 
     return Int32.MinValue; 
     return ((float)numerator/denominator).GetHashCode(); 
    } 
} 
+0

Cảm ơn ý tưởng. Đáng buồn thay, 0/0 là hoàn toàn khác nhau từ 0/N và tôi phải xem xét nó theo cách này trong thư viện của tôi vì vậy đây không phải là một lựa chọn. Tôi nghĩ cách tiếp cận tốt nhất hiện nay là làm theo lời khuyên của Arvo và tạo ra một trường boolean đánh dấu cấu trúc như được khởi tạo một cách rõ ràng hay không. – InBetween

+0

Có, nếu bạn phải tách biệt hai trạng thái đó, bạn cần thêm một chút thông tin. Ngoài ra: Tôi sẽ lấy một sneak peak tại nguồn của http://msdn.microsoft.com/en-us/library/microsoft.solverfoundation.common.rational(v=vs.93).aspx –

+0

Cảm ơn! Không biết điều này đã được triển khai trong .NET Framework. Tôi chắc chắn sẽ kiểm tra xem nó ra. Dù sao thì mục đích giáo dục cũng là cho bản thân tôi. Tôi có thể thực hành một số mã C# và nhớ một số toán học "cơ bản" cùng một lúc;) – InBetween

0

Tốt khi có thể thiết kế cấu trúc sao cho bất kỳ kết hợp giá trị trường nào sẽ có ngữ nghĩa xác định. Nếu điều này không được thực hiện, thường sẽ không có cách nào để cấu trúc ngăn chặn việc xây dựng các cá thể không đúng định dạng bằng mã không đúng, và đối với các trường hợp như vậy gây ra hành vi không đúng trong mã được tạo luồng đúng cách. Ví dụ, nếu một vị trí lưu trữ kiểu hợp lý có các giá trị tử số và mẫu số được nhận diện là đồng nhất, và vị trí được sao chép trong một luồng trong khi giá trị của nó đã được thay đổi trong một luồng khác, luồng sao chép có thể nhận được một cá thể nơi tử số và mẫu số không phải là nguyên tố, nhưng lá cờ nói rằng chúng là. Mã khác mà nhận được trường hợp đó có thể thất bại trong những cách kỳ lạ và kỳ lạ như là kết quả của bất biến bị hỏng; sự thất bại như vậy có thể xảy ra ở đâu đó rất xa từ mã không an toàn tạo ra thể hiện bị hỏng.

Tình huống này có thể được khắc phục bằng cách sử dụng đối tượng lớp không thay đổi để giữ số hữu tỷ và có loại giá trị số hợp lý bao bọc tham chiếu riêng tư cho đối tượng đó. Loại trình bao bọc sẽ sử dụng một cá thể mặc định khi tham chiếu riêng của nó là null hoặc trường hợp được bọc khi nó không phải là. Cách tiếp cận này có thể cung cấp một số cải tiến hiệu quả tiềm năng nếu tham chiếu riêng tư là một kiểu trừu tượng và có một số loại có nguồn gốc đáp ứng các tiêu chí khác nhau. Ví dụ: một trường có thể có một trường bắt buộc RationalSmallInteger chỉ có một trường là Int32RationalLongInteger trường duy nhất là Int64 (thuộc tính Denominator của cả hai loại này sẽ luôn trả về 1). Người ta có thể có các loại mà mẫu số không khác nhưng đã được xác nhận là đồng thời với tử số hoặc các loại mà nó không có; loại thứ hai loại có thể giữ một tham chiếu ban đầu-null cho một trường hợp mà tử số và mẫu số được đảm bảo là coprime. Những hành động đó có thể nâng cao hiệu quả trong các trường hợp như:

RationalNumber r1 = new RationalNumber(4,6); 
RationalNumber r2 = r1; 
RationalNumber r3 = r1.ReducedForm(); 
RationalNumber r4 = r2.ReducedForm(); 

Bản tuyên bố đầu tiên sẽ thiết lập các lĩnh vực tư nhân của r1 để đề cập đến một trường hợp RationalNumber.Int32by32Nonreduced. Thứ hai sẽ thiết lập trường riêng của r2 để trỏ đến cùng một cá thể đó. Câu lệnh thứ ba sẽ tạo ra một cá thể Int32by32Reduced mới và lưu trữ một tham chiếu đến trường hợp đó trong cá thể Int32by32Nonreduced trước đây và cũng trong trường riêng tư của r3. Thứ tư sẽ lấy tài liệu tham khảo nói trên từ Int32by32Reduced trước đây và lưu trữ nó vào trường riêng tư của R4. Lưu ý rằng chỉ cần một thao tác giảm là cần thiết. Ngược lại, nếu RationalNumber là một cấu trúc giữ giá trị của nó trong nội bộ, tuyên bố thứ tư sẽ không có cách nào tái sử dụng kết quả của việc giảm được thực hiện bởi thứ ba.

1

Bạn không thể có một hàm tạo tham số gán giá trị mặc định. Lý do kỹ thuật là struct của bạn là một phân lớp của System.ValueTypeSystem.ValueType()protected, do đó không thể bị ghi đè.

Gần nhất bạn có thể nhận được có lẽ là David Hefferman của giải pháp:

/// <summary> 
/// The denominator is stored in this altered form, because all struct fields initialize to 0 - and we want newly created RationalNumbers to be 0/1 more often than 0/0. 
/// </summary> 
private int _denominatorMinusOne; 
public int Denominator 
{ 
    get { return _denominatorMinusOne + 1; } 
    set { _denominatorMinusOne = value -1; } 
} 

Sau đó, bạn chỉ có thể tham khảo Denominator trong mã của bạn bình thường, và các định dạng lưu trữ đặc biệt sẽ được minh bạch - bạn sẽ chỉ có thể biết được nhìn vào khai báo trường, hoặc bằng cách kiểm tra hành vi của hàm tạo mặc định.

Bạn có thể làm những việc như gọi hàm tạo với tham số hoặc tạo một lớp RationalNumberFactory để tạo số không cho bạn - nhưng không có thứ nào trong số này lặp lại tất cả các thành phần của ma trận thay vì chỉ đường chéo, bởi vì bạn không thể chỉ định hàm khởi tạo mà bộ khởi tạo mảng sẽ sử dụng.

Thực tế, quy ước new RationalNumber[100][100] không đơn giản là viết tắt viết mã, nhưng nó cũng chạy nhanh hơn gọi hàm tạo 10.000 lần. Đây là một phần của lý do tại sao System.ValueType() được thực hiện protected ngay từ đầu. Xem: Why can't I define a default constructor for a struct in .NET?

Lặp qua mọi phần tử của ma trận cung cấp lợi thế rõ ràng, nhưng sử dụng "lạ" trừ một giải pháp không chỉ làm giảm số lượng mã bạn phải chạy mà còn cải thiện hiệu suất. Vì vậy, bạn có thể coi đây là một lập luận mạnh mẽ về lợi ích của nó.

Các vấn đề liên quan