Cấu trúc dữ liệu để truy cập các đơn vị đo lường


17

TL; DR - Tôi đang cố gắng thiết kế cấu trúc dữ liệu tối ưu để xác định các đơn vị trong một đơn vị đo.


A Unit of measurethực chất là một value(hoặc số lượng) liên quan đến a unit. Đơn vị SI có bảy cơ sở hoặc kích thước. Cụ thể: chiều dài, khối lượng, thời gian, dòng điện, nhiệt độ, lượng chất (nốt ruồi) và cường độ sáng.

Điều này sẽ đủ đơn giản, nhưng có một số đơn vị dẫn xuất cũng như tỷ lệ mà chúng tôi thường sử dụng. Một đơn vị kết hợp ví dụ sẽ là Newton: kg * m / s^2và một tỷ lệ ví dụ sẽ là tons / hr.

Chúng tôi có một ứng dụng dựa nhiều vào các đơn vị ngụ ý. Chúng tôi sẽ nhúng các đơn vị trong tên biến hoặc cột. Nhưng điều này tạo ra vấn đề khi chúng ta cần chỉ định một đơn vị đo lường với các đơn vị khác nhau. Có, chúng tôi có thể chuyển đổi các giá trị ở đầu vào và hiển thị nhưng điều này tạo ra rất nhiều mã trên cao mà chúng tôi muốn gói gọn trong lớp của chính nó.

Có một số giải pháp trên codeplex và các môi trường hợp tác khác. Việc cấp phép cho các dự án là dễ chịu nhưng bản thân dự án thường kết thúc quá nhẹ hoặc quá nặng. Chúng tôi đang theo đuổi kỳ lân của riêng mình "vừa phải."

Lý tưởng nhất, tôi có thể định nghĩa một đơn vị đo lường mới bằng cách sử dụng một cái gì đó như thế này:

UOM myUom1 = UOM mới (10, vôn);
UOM myUom2 = UOM mới (43.2, Newton);

Tất nhiên, chúng tôi sử dụng kết hợp các đơn vị Imperial và SI dựa trên nhu cầu của khách hàng.

Chúng tôi cũng cần giữ cấu trúc các đơn vị này được đồng bộ hóa với bảng cơ sở dữ liệu trong tương lai để chúng tôi cũng có thể cung cấp cùng một mức độ nhất quán trong dữ liệu của mình.


Cách tốt nhất để xác định đơn vị, đơn vị dẫn xuất và tỷ lệ mà chúng ta cần sử dụng để tạo đơn vị lớp đo lường là gì? Tôi có thể thấy việc sử dụng một hoặc nhiều enum, nhưng điều đó có thể gây khó chịu cho các nhà phát triển khác. Một enum sẽ rất lớn với hơn 200 mục trong khi nhiều enum có thể gây nhầm lẫn dựa trên các đơn vị SI vs Imperial và phân tích bổ sung dựa trên việc phân loại chính đơn vị đó.

Ví dụ Enum cho thấy một số mối quan tâm của tôi:

myUnits.Volt
myUnits.Newton
myUnits.meter

SIUnit.meter
ImpUnit. feet
DrvdUnit.Newton
DrvdUnitSI.Newton DrvdUnitImp.FtLbs

Tập hợp các đơn vị sử dụng của chúng tôi được xác định khá rõ và đó là một không gian hữu hạn. Chúng tôi cần khả năng mở rộng và thêm các đơn vị hoặc tỷ lệ dẫn xuất mới khi chúng tôi có nhu cầu của khách hàng đối với họ. Dự án là trong C # mặc dù tôi nghĩ rằng các khía cạnh thiết kế rộng hơn có thể áp dụng cho nhiều ngôn ngữ.


Một trong những thư viện tôi đã xem xét cho phép nhập đơn vị dạng tự do thông qua chuỗi. Lớp UOM của họ sau đó phân tích chuỗi và xiên các thứ tương ứng. Thách thức với cách tiếp cận này là nó buộc nhà phát triển phải suy nghĩ và ghi nhớ các định dạng chuỗi chính xác là gì. Và tôi có nguy cơ xảy ra lỗi / ngoại lệ thời gian chạy nếu chúng ta không thêm các kiểm tra bổ sung trong mã để xác thực các chuỗi được truyền trong hàm tạo.

Một thư viện khác về cơ bản đã tạo ra quá nhiều lớp mà nhà phát triển sẽ phải làm việc với. Cùng với một Đơn vị đo lường tương đương với nó cung cấp một DerivedUnitRateUnitvân vân. Về cơ bản, mã quá phức tạp đối với các vấn đề chúng ta đang giải quyết. Thư viện đó về cơ bản sẽ cho phép mọi: mọi kết hợp (hợp pháp trong thế giới đơn vị) nhưng chúng tôi rất vui khi phạm vi vấn đề của chúng tôi (đơn giản hóa mã của chúng tôi) bằng cách không cho phép mọi kết hợp có thể.

Các thư viện khác rất đơn giản và thậm chí còn không xem xét quá tải toán tử.

Ngoài ra, tôi không lo lắng về các nỗ lực chuyển đổi không chính xác (ví dụ: vôn sang mét). Các nhà phát triển là những người duy nhất sẽ truy cập ở cấp độ này vào thời điểm này và chúng tôi không nhất thiết phải bảo vệ chống lại các loại sai lầm đó.


Bạn có thể giải thích theo những cách mà các thư viện bạn tìm thấy không phù hợp với nhu cầu của bạn không?
Svick


1
@MainMa - cảm ơn vì liên kết đó. Chúng tôi không cần phân tích thứ nguyên vì không gian vấn đề của chúng tôi đủ nhỏ để chúng tôi chỉ cần khai báo các chuyển đổi được phép. Nó sẽ là một khẩu hiệu để tạo ra nhưng đó là chi phí một lần.

1
Bạn có thể giải thích loại chuyển đổi bạn cần? Có phải nó chỉ chuyển đổi tỷ lệ (ví dụ: mét sang centimet) hoặc chuyển đổi đa chiều (ví dụ: khối lượng thành lực)?
Bart van Ingen Schenau

1
Bạn đã xem xét việc chuyển một phần mã sang F # chưa? Ngôn ngữ đó có đơn vị đo int xây dựng.
Pete

Câu trả lời:


11

Các thư viện Boost cho C ++ bao gồm một bài viết về phân tích thứ nguyên trình bày một triển khai mẫu của các đơn vị đo lường xử lý.

Tóm lại: Đơn vị đo lường được biểu diễn dưới dạng vectơ, với mỗi phần tử của vectơ đại diện cho một chiều cơ bản:

typedef int dimension[7]; // m  l  t  ...
dimension const mass      = {1, 0, 0, 0, 0, 0, 0};
dimension const length    = {0, 1, 0, 0, 0, 0, 0};
dimension const time      = {0, 0, 1, 0, 0, 0, 0};

Đơn vị phái sinh là sự kết hợp của những. Ví dụ: lực (khối lượng * khoảng cách / thời gian ^ 2) sẽ được biểu diễn dưới dạng

dimension const force  = {1, 1, -2, 0, 0, 0, 0};

Các đơn vị Imperial so với SI có thể được xử lý bằng cách thêm hệ số chuyển đổi.

Việc triển khai này dựa trên các kỹ thuật cụ thể của C ++ (sử dụng siêu lập trình mẫu để dễ dàng biến các đơn vị đo lường khác nhau thành các loại thời gian biên dịch khác nhau), nhưng các khái niệm nên chuyển sang các ngôn ngữ lập trình khác.


Vậy tất cả các đơn vị dẫn xuất có tương đương với hằng số C ++ không? Tôi đoán họ bị cuốn vào một không gian tên để tránh những thứ gây ô nhiễm?

1
@ GlenH7 - Điều này được đưa vào công cụ siêu lập trình mẫu. Chúng thực sự được biểu diễn dưới dạng các loại riêng biệt (ví dụ mpl::vector_c<int,1,0,0,0,0,0,0>:) thay vì hằng số; bài báo trình bày cách tiếp cận consts trước bằng cách giải thích (và có lẽ tôi đã không giải thích điều đó tốt). Sử dụng consts sẽ hoạt động như một giải pháp thay thế (bạn sẽ mất một số loại an toàn thời gian biên dịch). Sử dụng một không gian tên để tránh ô nhiễm tên chắc chắn là một lựa chọn.
Josh Kelley

8

Tôi vừa phát hành đơn vị.NET trên Github và trên NuGet .

Nó cung cấp cho bạn tất cả các đơn vị phổ biến và chuyển đổi. Nó có trọng lượng nhẹ, đơn vị được thử nghiệm và hỗ trợ PCL.

Hướng tới câu hỏi của bạn:

  • Đây là kết thúc nhẹ hơn của việc thực hiện. Trọng tâm là hỗ trợ đại diện rõ ràng, chuyển đổi và xây dựng các đơn vị đo lường.
  • Không có bộ giải phương trình, nó không tự động lấy các đơn vị mới từ các phép tính.
  • Một enum lớn để xác định đơn vị.
  • Lớp UnitConverter để chuyển đổi linh hoạt giữa các đơn vị.
  • Cấu trúc dữ liệu bất biến để chuyển đổi rõ ràng giữa các đơn vị.
  • Toán tử quá tải cho số học đơn giản.
  • Mở rộng sang các đơn vị và chuyển đổi mới là vấn đề thêm một enum mới cho chuyển đổi động và thêm một đơn vị lớp đo lường, như Độ dài, để xác định các thuộc tính chuyển đổi rõ ràng và quá tải toán tử.

Tôi vẫn chưa thấy chén thánh của các giải pháp trong lĩnh vực này. Khi bạn nêu, nó có thể dễ dàng trở nên quá phức tạp hoặc quá dài dòng để làm việc. Đôi khi, tốt nhất là giữ mọi thứ đơn giản và theo nhu cầu của tôi, phương pháp này đã được chứng minh là đủ.

Chuyển đổi rõ ràng

Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701

Pressure p = Pressure.FromPascal(1);
double kpa = p.KiloPascals; // 1000
double bar = p.Bars; // 1 × 10-5
double atm = p.Atmosphere; // 9.86923267 × 10-6
double psi = p.Psi; // 1.45037738 × 10-4

Chuyển đổi động

// Explicitly
double m = UnitConverter.Convert(1, Unit.Kilometer, Unit.Meter); // 1000
double mi = UnitConverter.Convert(1, Unit.Kilometer, Unit.Mile); // 0.621371
double yds = UnitConverter.Convert(1, Unit.Meter, Unit.Yard); // 1.09361

// Or implicitly.
UnitValue val = GetUnknownValueAndUnit();

// Returns false if conversion was not possible.
double cm;
val.TryConvert(LengthUnit.Centimeter, out cm);

Ví dụ của bạn về cơ bản dường như sử dụng mộtTruple<T1, T2, T3>(x, y, z)
Chef_Code

Không chắc ý của bạn là gì, chỉ có một giá trị được lưu trữ cho mỗi đơn vị. Đối với Chiều dài, nó chứa một trường loại gấp đôi và đối với Khối lượng, nó là kilôgam. Khi chuyển đổi sang các đơn vị khác, nó chạy giá trị đó thông qua chức năng chuyển đổi. Các mẫu này hiện đã hơi lỗi thời, nhưng cùng một khái niệm được áp dụng.
angensesen

Tôi đoán là tôi đã sai chính tả cũng như nhảy vào kết luận ... ý tôi là vậy Tuple. Tôi không thể thấy UnitConverterlớp của bạn , nhưng IMO có vẻ như nó có thể chia sẻ một chức năng tương tự như Tuplelớp.
Chef_Code

Vẫn không chắc chắn về so sánh Tuple, nhưng hãy xem trang github để biết các ví dụ cập nhật về cách sử dụng.
angensesen

3

Nếu bạn có thể kéo chuyển sang F # thay vì sử dụng C #, F # có một đơn vị đo hệ thống (được triển khai bằng siêu dữ liệu trên các giá trị) có vẻ như nó sẽ phù hợp với những gì bạn đang cố gắng thực hiện:

http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure

Đáng chú ý:

// Additionally, we can define types measures which are derived from existing measures as well:

[<Measure>] type m                  (* meter *)
[<Measure>] type s                  (* second *)
[<Measure>] type kg                 (* kilogram *)
[<Measure>] type N = (kg * m)/(s^2) (* Newtons *)
[<Measure>] type Pa = N/(m^2)       (* Pascals *)

Đề nghị tốt, và chúng tôi đã xem xét nó. Tôi không tin F # cho chúng ta khả năng kiểm soát cách các đơn vị cuối cùng được hiển thị trên đầu ra.

2
@ GlenH7 Tôi tin rằng bạn đúng:Important: Units of measure look like a data type, but they aren't. .NET's type system does not support the behaviors that units of measure have, such as being able to square, divide, or raise datatypes to powers. This functionality is provided by the F# static type checker at compile time, **but units are erased from compiled code**. Consequently, it is not possible to determine value's unit at runtime.
paul

3

Dựa trên thực tế là tất cả các chuyển đổi bắt buộc đều là tỷ lệ chuyển đổi (trừ khi bạn phải hỗ trợ chuyển đổi nhiệt độ. Các tính toán trong đó chuyển đổi liên quan đến phần bù phức tạp hơn đáng kể), tôi sẽ thiết kế hệ thống 'đơn vị đo lường' của mình như sau:

  • Một lớp unitcó chứa một hệ số tỷ lệ, một chuỗi cho biểu diễn văn bản của đơn vị và một tham chiếu mà unittỷ lệ tới. Biểu diễn văn bản có sẵn cho mục đích hiển thị và tham chiếu đến đơn vị cơ sở để biết kết quả là đơn vị nào khi thực hiện toán trên các giá trị với các đơn vị khác nhau.

    Đối với mỗi đơn vị được hỗ trợ, một thể hiện tĩnh của unitlớp được cung cấp.

  • Một lớp UOMchứa một giá trị và một tham chiếu đến giá trị unit. Các UOMlớp học cung cấp quá tải khai thác cho việc thêm / trừ khác UOMvà cho nhân / chia với một giá trị không thứ nguyên.

    Nếu phép cộng / phép trừ được thực hiện trên hai UOMcái giống nhau unit, thì nó được thực hiện trực tiếp. Mặt khác, cả hai giá trị được chuyển đổi thành các đơn vị cơ sở tương ứng của chúng và được cộng / trừ. Kết quả được báo cáo là ở trong căn cứ unit.

Cách sử dụng sẽ như thế nào

unit volts = new unit(1, "V"); // base-unit is self
unit Newtons = new unit(1, "N"); // base-unit is self
unit kiloNewtons = new unit(1000, "kN", Newtons);
//...
UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, kiloNewtons);

Vì các hoạt động trên các đơn vị không tương thích không được coi là một vấn đề, tôi đã không cố gắng làm cho kiểu thiết kế an toàn về mặt đó. Có thể thêm kiểm tra thời gian chạy bằng cách xác minh rằng hai đơn vị tham chiếu đến cùng một đơn vị cơ sở.


Vì bạn đã đề cập đến nhiệt độ: là 95F - 85Fgì? Là 20C - 15Cgì Trong cả hai ví dụ, cả hai UOMsẽ có cùng unit. Phép trừ sẽ được thực hiện trực tiếp?

@MattFenwick: Kết quả sẽ tương ứng 10 F5 C. Các tính toán được thực hiện trực tiếp nếu có thể, để tránh các chuyển đổi không cần thiết. Việc thêm các phương thức chuyển đổi đơn vị vào sẽ khá đơn giản UOM, nhưng đối với chuyển đổi Celsius-Fahrenheit, unitlớp sẽ phải được mở rộng với khả năng bù trừ ngoài hệ số tỷ lệ.
Bart van Ingen Schenau

Nhưng 95F - 85F! = 10F.

1
@MattFenwick: Xin hãy khai sáng cho tôi. Trời lạnh đến mức nào nếu bạn hạ nhiệt độ 95Fxuống 85F? Theo hiểu biết của tôi, Fahrenheit vẫn là một thang đo tuyến tính.
Bart van Ingen Schenau

2
Hãy làm ví dụ về Celsius vì việc chuyển đổi sang Kelvin dễ dàng hơn: nếu chúng ta nói 20C - 15C = 5C, thì chúng ta đang nói 293.15K - 288.15K = 278.15K, điều đó rõ ràng là sai.

2

Hãy suy nghĩ về những gì mã của bạn đang làm, và những gì nó sẽ cho phép. Có một bảng liệt kê đơn giản với tất cả các đơn vị có thể có trong đó cho phép tôi làm một cái gì đó như chuyển đổi Volts sang mét. Điều đó rõ ràng không hợp lệ đối với con người, nhưng phần mềm sẽ sẵn sàng thử.

Tôi đã làm một cái gì đó mơ hồ tương tự như thế này một lần và việc triển khai của tôi có các lớp cơ sở trừu tượng (chiều dài, trọng lượng, v.v.) mà tất cả đều thực hiện IUnitOfMeasure. Mỗi lớp cơ sở trừu tượng đã định nghĩa một loại mặc định (lớp Lengthcó triển khai mặc định của lớp Meter) mà nó sẽ sử dụng cho tất cả các công việc chuyển đổi. Do đó IUnitOfMeasurethực hiện hai phương pháp khác nhau, ToDefault(decimal)FromDefault(decimal).

Số thực tế tôi muốn bọc là một loại chung chấp nhận IUnitOfMeasurelàm đối số chung của nó. Nói một cái gì đó như Measurement<Meter>(2.0)cung cấp cho bạn loại an toàn tự động. Việc thực hiện các phương thức toán học và chuyển đổi ngầm định thích hợp trên các lớp này cho phép bạn thực hiện những việc như Measurement<Meter>(2.0) * Measurement<Inch>(12)và trả về một kết quả theo kiểu mặc định ( Meter). Tôi không bao giờ làm việc ra các đơn vị dẫn xuất như Newton; Tôi chỉ đơn giản là để chúng dưới dạng Kilôgam * mét / giây / giây.


Tôi thích cách tiếp cận mà bạn đề xuất với việc sử dụng các loại chung chung.

1

Tôi tin rằng câu trả lời nằm trong phản hồi Stack Overflow của MarioVW đối với:

Ví dụ thực tế Trường hợp Tuple có thể được sử dụng trong .Net 4-0?

Với bộ dữ liệu, bạn có thể dễ dàng thực hiện từ điển hai chiều (hoặc n chiều cho vấn đề đó). Ví dụ: bạn có thể sử dụng một từ điển như vậy để thực hiện ánh xạ trao đổi tiền tệ:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);
decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58

Tôi có một nhu cầu tương tự cho ứng dụng của tôi. Tuplecũng không thay đổi, điều này cũng đúng với các đối tượng như Trọng lượng và Đo lường Như câu nói "một pint một pound thế giới xung quanh."


0

Mã nguyên mẫu của tôi: http://ideone.com/x7hz7i

Điểm thiết kế của tôi:

  1. Lựa chọn UoM (Đơn vị đo lường) làm tài sản nhận / bộ
    Chiều dài len = chiều dài mới ();
    len.Meter = 2.0;
    Console.WriteLine (len.Feet);
    
  2. Nhà xây dựng được đặt tên cho sự lựa chọn của UoM
    Chiều dài len = Chiều dài.FromMeter (2.0);
    
  3. Hỗ trợ ToString cho UoM
    Console.WriteLine (len.ToString ("ft"));
    Console.WriteLine (len.ToString ("F15"));
    Console.WriteLine (len.ToString ("ftF15"));
    
  4. Chuyển đổi khứ hồi (tổn thất làm tròn không đáng kể được cho phép bởi độ chính xác kép)
    Chiều dài lenRT = Chiều dài.FromMeter (Chiều dài.FromFeet (Chiều dài.FromMeter (len.Meter) .eet) .Meter);
    
  5. Quá tải toán tử (nhưng thiếu kiểm tra loại chiều)
    // Khá lộn xộn, lỗi, không an toàn và có thể không thực hiện được nếu không sử dụng F # hoặc C ++ MPL.
    // Nó tiếp tục nói rằng Dimensional Phân tíchkhông phải là một tính năng tùy chọn cho UoM -
    // cho dù bạn sử dụng nó trực tiếp hay không. Nó là bắt buộc .
    

0

Có một bài viết hay trong một magazin unourtunatly đó là tiếng Đức: http://www.dotnetpro.de/articles/onlinearticle1398.aspx

Ý tưởng cơ bản là có một lớp Đơn vị như Độ dài với BaseMeas mua sắm. Lớp này chứa hệ số chuyển đổi, quá tải toán tử, quá tải ToString, trình phân tích cú pháp chuỗi và triển khai như một trình chỉ mục. Thậm chí chúng tôi đã triển khai ngay cả khung nhìn Architectual nhưng nó không phát hành dưới dạng thư viện.

public class Length : MeasurementBase
    {
        protected static double[] LengthFactors = { 1, 100, 1000, 0.001, 100 / 2.54 };
        protected static string[] LengthSymbols = { "m", "cm", "mm", "km", "in" };
...
      public virtual double this[Units unit]
        {
            get { return BaseValue * LengthFactors[(int)unit]; }
            set { BaseValue = value / LengthFactors[(int)unit]; }
        }
...

        public static ForceDividedByLength operator *(Length length, Pressure pressure1)
        {
            return new ForceDividedByLength(pressure1[Pressure.Units.kNm2] * length[Units.m], ForceDividedByLength.Units.kNm);
        }

...

Vì vậy, bạn thấy việc sử dụng với toán tử áp suất hoặc chỉ:

var l = new Length(5, Length.Units.m)    
Area a = l * new Length("5 m");
a.ToString() // -> 25 m^2
double l2 = l[Length.Units.ft];

Nhưng như bạn đã nói, tôi cũng không tìm thấy kỳ lân :)


-1

Đây là raison d'être của Unix unitslệnh, mà sẽ làm tất cả sử dụng một cách tiếp cận dữ liệu tập tin theo định hướng để xác định các mối quan hệ.


Cảm ơn bạn đã đề cập units. Các đơn vị lý do chính sẽ không hoạt động cho giải pháp rộng hơn của tôi là các chuỗi biểu mẫu miễn phí. Cấp, nó cung cấp thông báo lỗi, nhưng cách tiếp cận này đang nhắm mục tiêu các nhà phát triển sẽ tích hợp mã này với ứng dụng của chúng tôi. Chuỗi hình thức miễn phí trình bày quá nhiều cơ hội cho lỗi.

1
Bạn nên xem unitstập tin dữ liệu của. Cách nó xác định mối quan hệ giữa các đại lượng rất rõ ràng và có thể hữu ích cho vấn đề của bạn.
Ross Patterson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.