Datenstruktur für den Zugriff auf Maßeinheiten

17

TL; DR - Ich versuche, eine optimale Datenstruktur zu entwerfen, um Einheiten innerhalb einer Maßeinheit zu definieren.


A Unit of measureist im Wesentlichen eine value(oder eine Menge), die mit a assoziiert ist unit. SI-Einheiten haben sieben Basen oder Dimensionen. Nämlich: Länge, Masse, Zeit, elektrischer Strom, Temperatur, Substanzmenge (Mol) und Lichtstärke.

Dies wäre recht einfach, aber es gibt eine Reihe von abgeleiteten Einheiten sowie Raten, die wir häufig verwenden. Eine kombinierte Beispieleinheit wäre der Newton: kg * m / s^2und eine Beispielrate wäre tons / hr.

Wir haben eine Anwendung, die sich stark auf implizite Einheiten stützt. Wir werden die Einheiten in den Variablen- oder Spaltennamen einbetten. Dies schafft jedoch Probleme, wenn wir eine Maßeinheit mit verschiedenen Einheiten angeben müssen. Ja, wir können die Werte bei der Eingabe und Anzeige konvertieren, aber dies generiert eine Menge Overhead-Code, den wir in einer eigenen Klasse einkapseln möchten.

Es gibt eine Reihe von Lösungen für Codeplex und andere kollaborative Umgebungen. Die Lizenzierung für die Projekte ist akzeptabel, aber das Projekt selbst ist in der Regel zu leicht oder zu schwer. Wir jagen unser eigenes Einhorn "genau richtig".

Idealerweise könnte ich eine neue Maßeinheit folgendermaßen definieren:

UOM myUom1 = neue UOM (10, Volt);
UOM myUom2 = neue UOM (43,2, Newton);

Natürlich verwenden wir eine Mischung aus imperialen und SI-Einheiten, die auf die Bedürfnisse unserer Kunden abgestimmt ist.

Wir müssen diese Einheitenstruktur auch mit einer zukünftigen Datenbanktabelle synchronisieren, damit wir auch in unseren Daten den gleichen Grad an Konsistenz gewährleisten können.


Wie lassen sich die Einheiten, abgeleiteten Einheiten und Kurse am besten definieren, die zum Erstellen unserer Maßeinheitenklasse verwendet werden müssen? Ich könnte eine oder mehrere Aufzählungen verwenden, aber das könnte für andere Entwickler frustrierend sein. Eine einzelne Aufzählung würde mit mehr als 200 Einträgen sehr umfangreich sein, während mehrere Aufzählungen auf der Grundlage von SI-Einheiten gegenüber imperialen Einheiten und einer zusätzlichen Aufschlüsselung auf der Grundlage der Kategorisierung der Einheit selbst verwirrend sein könnten.

Aufzählungsbeispiele, die einige meiner Bedenken aufzeigen:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

Unser Satz von Einheiten ist ziemlich gut definiert und es ist ein begrenzter Raum. Wir müssen in der Lage sein, neue abgeleitete Einheiten oder Raten zu erweitern und hinzuzufügen, wenn die Kundennachfrage danach besteht. Das Projekt ist in C #, obwohl ich denke, dass die umfassenderen Designaspekte auf mehrere Sprachen anwendbar sind.


Eine der Bibliotheken, die ich mir angesehen habe, ermöglicht die formlose Eingabe von Einheiten per String. Ihre UOM-Klasse analysierte dann die Zeichenfolge und schlitzte die Dinge entsprechend ein. Die Herausforderung bei diesem Ansatz besteht darin, dass der Entwickler gezwungen ist, über die richtigen Zeichenfolgenformate nachzudenken und sich diese zu merken. Und ich riskiere einen Laufzeitfehler / eine Laufzeitausnahme, wenn wir keine zusätzlichen Prüfungen im Code hinzufügen, um die im Konstruktor übergebenen Zeichenfolgen zu überprüfen.

Eine andere Bibliothek hat im Wesentlichen zu viele Klassen erstellt, mit denen der Entwickler arbeiten müsste. Zusammen mit einer äquivalenten UOM vorausgesetzt , es ist ein DerivedUnitund RateUnitund so weiter. Im Wesentlichen war der Code für die von uns gelösten Probleme zu komplex. Diese Bibliothek würde im Wesentlichen jede: beliebige Kombination zulassen (was in der Einheitenwelt legitim ist), aber wir sind glücklich, unser Problem zu erfassen (unseren Code zu vereinfachen), indem wir nicht jede mögliche Kombination zulassen.

Andere Bibliotheken waren lächerlich einfach und hatten zum Beispiel nicht einmal eine Überladung der Bediener in Betracht gezogen.

Außerdem mache ich mir weniger Sorgen über Versuche, falsche Umrechnungen vorzunehmen (zum Beispiel: Volt in Meter). Entwickler sind die einzigen, auf die auf dieser Ebene zu diesem Zeitpunkt zugegriffen werden kann, und wir müssen uns nicht unbedingt vor solchen Fehlern schützen.


quelle
Können Sie erklären, inwiefern die Bibliotheken, die Sie gefunden haben, nicht Ihren Anforderungen entsprechen?
SVICK
1
Siehe auch stackoverflow.com/q/348853/240613
Arseni Mourzenko
1
@MainMa - danke für diesen Link. Wir müssen keine Dimensionsanalyse durchführen, da unser Problembereich so klein ist, dass wir nur die zulässigen Konvertierungen deklarieren würden. Es wird eine Schande, aber das ist eine einmalige Kosten.
1
Können Sie erklären, welche Art von Konvertierungen Sie benötigen? Handelt es sich nur um eine Skalierungsumrechnung (z. B. von Meter in Zentimeter) oder auch um eine querdimensionale Umrechnung (z. B. von Masse in Kraft)?
Bart van Ingen Schenau
1
Haben Sie darüber nachgedacht, einen Teil des Codes nach F # zu verschieben? Diese Sprache hat Maßeinheiten build int.
Pete

Antworten:

11

Die Boost-Bibliotheken für C ++ enthalten einen Artikel zur Dimensionsanalyse , in dem eine Beispielimplementierung von Handling-Maßeinheiten vorgestellt wird.

Zusammenfassend: Maßeinheiten werden als Vektoren dargestellt, wobei jedes Element des Vektors eine grundlegende Dimension darstellt:

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

Abgeleitete Einheiten sind Kombinationen davon. Zum Beispiel würde Kraft (Masse * Distanz / Zeit ^ 2) dargestellt als

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

Imperial versus SI-Einheiten können durch Hinzufügen eines Umrechnungsfaktors behandelt werden.

Diese Implementierung basiert auf C ++ - spezifischen Techniken (mithilfe der Template-Metaprogrammierung können verschiedene Maßeinheiten leicht in verschiedene Typen zur Kompilierungszeit umgewandelt werden). Die Konzepte sollten jedoch auf andere Programmiersprachen übertragen werden.

Josh Kelley
quelle
Alle abgeleiteten Einheiten entsprechen also C ++ - Konstanten? Ich nehme an, sie sind in einen Namespace eingebunden, um die Umwelt nicht zu verschmutzen.
1
@ GlenH7 - Dies wird in die Metaprogrammierung der Vorlage übernommen. Sie werden tatsächlich als separate Typen (z. B. mpl::vector_c<int,1,0,0,0,0,0,0>) anstelle von Konstanten dargestellt. Der Artikel stellt zunächst den consts-Ansatz zur Erklärung vor (und das habe ich wahrscheinlich nicht gut erklärt). Die Verwendung von consts würde alternativ funktionieren (Sie würden beim Kompilieren einige Sicherheitseinstellungen verlieren). Die Verwendung eines Namespaces zur Vermeidung von Namensverschmutzung ist sicherlich eine Option.
Josh Kelley
8

Ich habe gerade Units.NET auf Github und NuGet veröffentlicht .

Sie erhalten alle gängigen Einheiten und Umrechnungen. Es ist leicht, einheitengetestet und unterstützt PCL.

Zu Ihrer Frage:

  • Dies ist am leichteren Ende der Implementierungen. Der Schwerpunkt liegt auf der Unterstützung der eindeutigen Darstellung, Umrechnung und Konstruktion von Maßeinheiten.
  • Es gibt keinen Gleichungslöser, der neue Einheiten nicht automatisch aus Berechnungen ableitet.
  • Eine große Aufzählung für die Definition von Einheiten.
  • UnitConverter-Klasse zum dynamischen Konvertieren zwischen Einheiten.
  • Unveränderliche Datenstrukturen für die explizite Konvertierung zwischen Einheiten.
  • Überladene Operatoren für einfache Arithmetik.
  • Beim Erweitern auf neue Einheiten und Konvertierungen müssen Sie eine neue Aufzählung für die dynamische Konvertierung und eine Maßeinheit wie Length hinzufügen, um explizite Konvertierungseigenschaften und Operatorüberladungen zu definieren.

Ich habe den heiligen Gral der Lösungen in diesem Bereich noch nicht gesehen. Wie Sie sagen, kann es leicht zu komplex oder zu ausführlich werden, um damit zu arbeiten. Manchmal ist es am besten, die Dinge einfach zu halten, und für meine Bedürfnisse hat sich dieser Ansatz als ausreichend erwiesen.

Explizite Konvertierung

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

Dynamische Konvertierung

// 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);
Angularsen
quelle
Ihr Beispiel scheint im Wesentlichen einenTruple<T1, T2, T3>(x, y, z)
Chef_Code 14.02.16
Nicht sicher, was Sie meinen, es ist nur ein einziger Wert für jede Einheit gespeichert. Für Length gilt ein Meterfeld vom Typ double und für Mass sind es Kilogramm. Bei der Umrechnung in andere Einheiten wird dieser Wert durch eine Umrechnungsfunktion ausgeführt. Diese Beispiele sind jetzt etwas veraltet, aber das gleiche Konzept gilt.
Angularsen
Ich glaube, ich habe mich geirrt und bin zu Schlussfolgerungen gesprungen ... ich meinte Tuple. Ich kann Ihre UnitConverterKlasse nicht sehen , aber IMO sieht es so aus, als ob sie eine ähnliche Funktionalität wie die TupleKlasse hat.
Chef_Code
Immer noch nicht sicher, was den Tupel- Vergleich angeht , aber auf der Github-Seite finden Sie aktualisierte Beispiele zur Verwendung.
Angularsen
3

Wenn Sie stattdessen mit C # zu F # wechseln können, verfügt F # über ein Maßeinheitensystem (das mithilfe von Metadaten zu Werten implementiert wird), das Ihren Vorstellungen entspricht:

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

Vor allem:

// 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 *)
paul
quelle
Guter Vorschlag, und wir haben darüber nachgedacht. Ich glaube nicht, dass wir mit F # steuern können, wie die Einheiten auf der Ausgabe angezeigt werden.
2
@ GlenH7 Ich glaube, Sie haben Recht: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

Basierend auf der Tatsache, dass alle erforderlichen Konvertierungen Skalierungskonvertierungen sind (außer wenn Sie Temperaturkonvertierungen unterstützen müssen. Berechnungen, bei denen die Konvertierung einen Versatz beinhaltet, sind wesentlich komplexer), würde ich mein "Maßeinheit" -System folgendermaßen entwerfen:

  • Eine Klasse unitmit einem Skalierungsfaktor, einer Zeichenfolge für die Textdarstellung der Einheit und einer Referenz, die dieunit skaliert wird. Die Textdarstellung dient zu Anzeigezwecken und der Bezugnahme auf die Basiseinheit, um zu wissen, in welcher Einheit sich das Ergebnis befindet, wenn mit Werten mit verschiedenen Einheiten gerechnet wird.

    Für jede unterstützte Einheit wird eine statische Instanz der unitKlasse bereitgestellt.

  • Eine Klasse, UOMdie einen Wert und einen Verweis auf den Wert enthält unit. Die UOMKlasse bietet überladene Operatoren zum Addieren / Subtrahieren eines anderen UOMund zum Multiplizieren / Dividieren mit einem dimensionslosen Wert.

    Wenn Addition / Subtraktion für zwei UOMmit demselben Wert ausgeführt wird unit, wird dies direkt ausgeführt. Andernfalls werden beide Werte in ihre jeweiligen Basiseinheiten konvertiert und addiert / subtrahiert. Das Ergebnis wird als in der Basis angegeben unit.

Nutzung wäre wie

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

Da Vorgänge mit nicht kompatiblen Einheiten kein Problem darstellen, habe ich nicht versucht, das Design in dieser Hinsicht typsicher zu machen. Sie können eine Laufzeitprüfung hinzufügen, indem Sie sicherstellen, dass sich zwei Einheiten auf dieselbe Basiseinheit beziehen.

Bart van Ingen Schenau
quelle
Seit du Temperatur erwähnt hast: Was ist das 95F - 85F? Was ist 20C - 15C? In beiden Beispielen hätten beide UOMs dasselbe unit. Würden die Subtraktionen direkt ausgeführt?
@MattFenwick: Die Ergebnisse wären jeweils 10 Fund 5 C. Die Berechnungen werden nach Möglichkeit direkt durchgeführt, um nicht benötigte Konvertierungen zu vermeiden. Es wäre ziemlich trivial, Einheitenumrechnungsmethoden UOMzur Celsius-Fahrenheit-Umrechnung hinzuzufügen , aber für die Celsius-Fahrenheit-Umrechnung unitmüsste die Klasse um die Möglichkeit eines Versatzes zusätzlich zu einem Skalierungsfaktor erweitert werden.
Bart van Ingen Schenau
Aber 95F - 85F! = 10F.
1
@ MattFenwick: Bitte erleuchten Sie mich. Wie kalt wird es, wenn Sie eine Temperatur von 95Fum senken 85F? Meines Wissens ist Fahrenheit immer noch eine lineare Skala.
Bart van Ingen Schenau
2
Lassen Sie uns das Celsius-Beispiel machen, weil es einfacher ist, in Kelvin zu konvertieren: Wenn wir sagen 20C - 15C = 5C, dann sagen wir 293.15K - 288.15K = 278.15K, was eindeutig falsch ist.
2

Überlegen Sie, was Ihr Code tut und was er zulässt. Wenn ich eine einfache Aufzählung mit allen möglichen Einheiten habe, kann ich so etwas wie Volt in Meter umrechnen. Das ist natürlich für einen Menschen nicht gültig, aber die Software wird es gerne versuchen.

Ich habe einmal etwas Ähnliches getan, und meine Implementierung hatte abstrakte Basisklassen (Länge, Gewicht usw.), die alle implementiert wurden IUnitOfMeasure. Jede abstrakte Basisklasse definierte einen Standardtyp (Klasse Lengthhatte eine Standardimplementierung von class Meter), den sie für alle Konvertierungsarbeiten verwenden würde. Daher IUnitOfMeasurewurden zwei verschiedene Methoden implementiert, ToDefault(decimal)und FromDefault(decimal).

Die tatsächliche Zahl, die ich einschließen wollte, war ein generischer Typ, der IUnitOfMeasureals generisches Argument akzeptiert wurde . Wenn Sie so etwas sagen, erhalten Measurement<Meter>(2.0)Sie eine automatische Schriftsicherheit. Wenn Sie die richtigen impliziten Konvertierungen und mathematischen Methoden für diese Klassen implementieren, können Sie Folgendes tun Measurement<Meter>(2.0) * Measurement<Inch>(12)und ein Ergebnis mit dem Standardtyp ( Meter) zurückgeben. Ich habe niemals abgeleitete Einheiten wie Newton erarbeitet. Ich habe sie einfach als Kilogramm * Meter / Sekunde / Sekunde belassen.

mgw854
quelle
Ich mag den Ansatz, den Sie mit der Verwendung von generischen Typen vorschlagen.
1

Ich glaube, die Antwort liegt in der Stapelüberlauf- Antwort von MarioVW auf:

Praktisches Beispiel Wo kann Tuple in .Net 4-0 verwendet werden?

Mit Tupeln können Sie leicht ein zweidimensionales Wörterbuch (oder n-dimensional für diese Angelegenheit) implementieren. Beispielsweise könnten Sie ein solches Wörterbuch verwenden, um eine Währungsumrechnungszuordnung zu implementieren:

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

Ich hatte ein ähnliches Bedürfnis nach meiner Bewerbung. Tupleist auch unveränderlich, was auch für Objekte wie Maße und Gewichte zutrifft.

Chef_Code
quelle
0

Mein Prototypcode: http://ideone.com/x7hz7i

Meine Designpunkte:

  1. Auswahl der ME (Maßeinheit) als Eigenschaft holen / setzen
    Länge len = neue Länge ();
    Längenmeter = 2,0;
    Console.WriteLine (len.Feet);
    
  2. Benannter Konstruktor für die Auswahl der ME
    Length len = Length.FromMeters (2.0);
    
  3. ToString-Unterstützung für ME
    Console.WriteLine (len.ToString ("ft"));
    Console.WriteLine (len.ToString ("F15"));
    Console.WriteLine (len.ToString ("ftF15"));
    
  4. Roundtrip-Konvertierung (vernachlässigbarer Rundungsverlust durch doppelte Genauigkeit)
    Length lenRT = Length.FromMeters (Length.FromFeet (Length.FromMeters (len.Meters) .Feet) .Meters);
    
  5. Überladung des Operators (aber fehlende Maßtypprüfung)
    // Ziemlich chaotisch, fehlerhaft, unsicher und möglicherweise nicht ohne F # oder C ++ MPL möglich.
    // Die Dimensionsanalyse ist keine optionale Funktion für UoM -
    // ob du es direkt verwendest oder nicht. Es ist erforderlich .
    
rwong
quelle
0

Es gibt einen guten Artikel in einem Magazin, leider in deutscher Sprache: http://www.dotnetpro.de/articles/onlinearticle1398.aspx

Die Grundidee ist, eine Einheitenklasse wie Length mit einem BaseMeasurement zu haben. Die Klasse enthält den Konvertierungsfaktor, Operatorüberladungen, ToString-Überladungen, Parser von Zeichenfolgen und eine Implementierung als Indexer. Wir haben sogar die Architectual-Ansicht implementiert, diese ist jedoch nicht als Bibliothek verfügbar.

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

...

So sehen Sie die Verwendung mit dem Druckoperator oder nur:

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];

Aber wie du sagtest, ich habe das Einhorn auch nicht gefunden :)

KCT
quelle
-1

Dies ist das Grundprinzip des Unix- unitsBefehls, der alles unter Verwendung eines datendateigesteuerten Ansatzes zur Angabe der Beziehungen ausführt.

Ross Patterson
quelle
Vielen Dank für die Erwähnung units. Der Hauptgrund, warum Einheiten für meine umfassendere Lösung nicht funktionieren, sind die Zeichenfolgen in freier Form. Zugegeben, es werden im Gegenzug Fehlermeldungen angezeigt. Dieser Ansatz zielt jedoch auf Entwickler ab, die diesen Code in unsere Anwendung integrieren. Freiform-Strings bieten zu viele Möglichkeiten für Fehler.
1
Sie sollten sich die Datendatei von ansehen units. Die Art und Weise, wie Beziehungen zwischen Mengen definiert werden, ist sehr sauber und kann für Ihr Problem nützlich sein.
Ross Patterson