Laden...

Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch

Erstellt von abra_joe vor 14 Jahren Letzter Beitrag vor 3 Jahren 771.797 Views
G
47 Beiträge seit 2011
vor 11 Jahren

Oups,

da hatte ich mich an die Bearbeitung gemacht, bevor ich alles gelesen hatte. Ich werd mich später am Tag damit nochmal befassen...

Gruß Gwinn

M
55 Beiträge seit 2011
vor 11 Jahren

Hi,

dann würde ich mal einen Vorschlag posten.
Der Code dürfte für die meisten nicht sehr attraktiv wirken.
Kritik nehme ich gerne per PM entgegen 😃

Ich habe den Code zwar etwas aufgehübscht, aber das einige Stellen sich wiederholen ist mir bewusst, wollte ich aber nicht noch ausbessern.


		public static TimeSpan SubtractEx(DateTime start, DateTime end, Dictionary<DayOfWeek, IList<TimeSpan>> timelist)
		{
			TimeSpan _result = new TimeSpan();
			int _totalDaysBetween = end.Subtract(start).Days;

			if ( _totalDaysBetween < 0 )
				throw new ArgumentException("Fehler! \n Ungueltige Zeiangabe");

			IList<TimeSpan> _firstList = new List<TimeSpan>();
			IList<TimeSpan> _secondList = new List<TimeSpan>();
			IList<TimeSpan> _tempList = new List<TimeSpan>();

			DateTime _firstDate = new DateTime();
			DateTime _secondDate = new DateTime();
			DateTime _tempDate = new DateTime();

			switch ( _totalDaysBetween )
			{
				case 0:
				//Startdatum == Enddatum
				timelist.TryGetValue(start.DayOfWeek, out _firstList);
				_result = _result.Add(GetTimeSpanBetween(start, end, _firstList));
				break;
				//-------------------------------------------------------------------------------------------
				case 1:
				// Startdatum + 1Tag = Enddatum
				_firstDate = new DateTime(start.Year,
													start.Month,
													start.Day,
													23, 59, 59);

				_secondDate = new DateTime(end.Year,
													end.Month,
													end.Day,
													00, 00, 00);

				timelist.TryGetValue(start.DayOfWeek, out _firstList);
				timelist.TryGetValue(end.DayOfWeek, out _secondList);
				_result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
				_result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));
				break;
				//-------------------------------------------------------------------------------------------
				case 2:
				// Startdatum + 2Tage = Enddatum --> kein "voller" Tag dazwischen
				_firstDate = new DateTime(start.Year,
													start.Month,
													start.Day,
													23, 59, 59);

				_secondDate = new DateTime(end.Year,
													end.Month,
													end.Day,
													00, 00, 00);

				_tempDate = start.AddDays(1);
				_tempDate = new DateTime(_tempDate.Year,
												_tempDate.Month,
												_tempDate.Day,
												23, 59, 59);

				DateTime _tempDateEnd = new DateTime(_tempDate.Year,
																_tempDate.Month,
																_tempDate.Day,
																23, 59, 59);

				timelist.TryGetValue(start.DayOfWeek, out _firstList);
				timelist.TryGetValue(end.DayOfWeek, out _secondList);
				timelist.TryGetValue(_tempDate.DayOfWeek, out _tempList);

				_result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
				_result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));
				_result = _result.Add(GetTimeSpanBetween(_tempDate, _tempDateEnd, _tempList));
				break;
				//-------------------------------------------------------------------------------------------
				default:
				// Startdatum + 3Tage + X = Enddatum
				_firstDate = new DateTime(start.Year,
													start.Month,
													start.Day,
													23, 59, 59);

				_secondDate = new DateTime(end.Year,
													end.Month,
													end.Day,
													00, 00, 00);

				timelist.TryGetValue(start.DayOfWeek, out _firstList);
				timelist.TryGetValue(end.DayOfWeek, out _secondList);

				_result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
				_result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));

				//Alle Tage zwischen Startdatum+1 und Enddatum-1 ermitteln
				DateTime _firstRelevantDay = start.AddDays(1);
				DateTime _lastRelevantDay = end.Subtract(new TimeSpan(24, 0, 0));

				_firstRelevantDay = new DateTime(_firstRelevantDay.Year,
															_firstRelevantDay.Month,
															_firstRelevantDay.Day,
															00, 00, 00);

				_lastRelevantDay = new DateTime(_lastRelevantDay.Year,
															_lastRelevantDay.Month,
															_lastRelevantDay.Day,
															23, 59, 59);

				int _fullDays = _lastRelevantDay.Subtract(_firstRelevantDay).Days;
				_fullDays++;

				// ganze + angebrochene wochen ermittlen
				int _weeks = _fullDays / 7;
				int _daysOverWeeks = _fullDays % 7;

				_tempDate = _firstRelevantDay;
				Dictionary<DayOfWeek, int> _dayOrder = new Dictionary<DayOfWeek, int>();
				for ( int i = 0 ; i < 7 ; i++ )
				{
					if ( i <= _fullDays )
						_dayOrder.Add(_tempDate.AddDays(i).DayOfWeek, i);
				}

				if ( _fullDays <= 7 )
				//Keine volle Woche zwishen start(+1) und ende(-1)
				{
					for ( int i = 1 ; i <= _fullDays ; i++ )
					{

						timelist.TryGetValue(_firstRelevantDay.AddDays(i - 1).DayOfWeek, out _tempList);
						DateTime _currentStart = _firstRelevantDay;
						DateTime _currentEnd = new DateTime(_currentStart.Year,
																		_currentStart.Month,
																		_currentStart.Day,
																		23, 59, 59);

						_result = _result.Add(GetTimeSpanBetween(_currentStart, _currentEnd, _tempList));
					}
				}
				//mindestens 1tag ist dopplet vorhanden (excl start und ende)
				else
				{
					for ( int i = 0 ; i < 7 ; i++ )
					{
						int _factor = _weeks;
						if ( i < _daysOverWeeks )
							_factor++;

						timelist.TryGetValue(_firstRelevantDay.AddDays(i).DayOfWeek, out _tempList);

						DateTime _start = _firstRelevantDay;
						DateTime _end = new DateTime(_start.Year,
																_start.Month,
																_start.Day,
																23, 59, 59);

						TimeSpan _tempSpan = GetTimeSpanBetween(_start, _end, _tempList);
						TimeSpan _multiplied = new TimeSpan(_tempSpan.Days * _factor,
																		_tempSpan.Hours * _factor,
																		_tempSpan.Minutes * _factor,
																		_tempSpan.Seconds * _factor);

						_result = _result.Add(_multiplied);
					}
				}

				break;
			}
			return _result;
		}

		private static TimeSpan GetTimeSpanBetween(DateTime start, DateTime end, IList<TimeSpan> timelist)
		{
			TimeSpan _result = new TimeSpan();

			//timelist ist NULL  wenn kein eintrag fuer den Wochentag im Dict --> leeren TimeSpan zurück geben
			if ( timelist != null )
			{
				for ( int i = 0 ; i < timelist.Count ; i += 2 )
				{
					//(Startzeit < TimeListZeit[n]) + (Endzeit < TimeListZeit[n+1]) --> TimeListZeit[n] + Endzeit nehmen
					if ( ( start.TimeOfDay <= timelist[i] ) && ( end.TimeOfDay <= timelist[i + 1] ) )
					{
						_result = _result.Add(end.TimeOfDay.Subtract(timelist[i]));
					}
					//(Startzeit < TimeListZeit[n]) + (Endzeit > TimeListZeit[n+1]) --> TimeListZeit[n] + TimeListZeit[n+1] nehmen
					else if ( ( start.TimeOfDay <= timelist[i] ) && ( end.TimeOfDay >= timelist[i + 1] ) )
					{
						_result = _result.Add(timelist[i + 1].Subtract(timelist[i]));
					}
					//(Startzeit > TimeListZeit[n]) + (Endzeit < TimeListZeit[n+1]) --> Startzeit       + Endzeit nehmen
					else if ( ( start.TimeOfDay >= timelist[i] ) && ( end.TimeOfDay <= timelist[i + 1] ) )
					{
						_result = _result.Add(end.TimeOfDay.Subtract(start.TimeOfDay));
					}
					//(Startzeit > TimeListZeit[n]) + (Endzeit > TimeListZeit[n+1]) --> Startzeit       + TimeListZeit[n+1] nehmen
					else if ( ( start.TimeOfDay >= timelist[i] ) && ( end.TimeOfDay >= timelist[i + 1] ) )
					{
						_result = _result.Add(timelist[i + 1].Subtract(start.TimeOfDay));
					}
					else
					{
						throw new ArgumentException("Fehler! \n Ungueltige Zeiangabe");
					}
				}
			}
			return _result;
		}

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo mcdt,

so ganz passt es noch nicht. Ich habe mal folgende Testfälle laufen lassen:

var times = new Dictionary <DayOfWeek, IList <TimeSpan>> ();
times [DayOfWeek.Monday] = new List <TimeSpan> ();
times [DayOfWeek.Monday].Add (new TimeSpan (8, 0, 0));
times [DayOfWeek.Monday].Add (new TimeSpan (16, 0, 0));
times [DayOfWeek.Tuesday] = times [DayOfWeek.Monday];
times [DayOfWeek.Wednesday] = times [DayOfWeek.Monday];
times [DayOfWeek.Thursday] = times [DayOfWeek.Monday];
times [DayOfWeek.Friday] = times [DayOfWeek.Monday];

DateTime start = new DateTime (2012, 10, 2, 9, 0 , 0);
DateTime end = new DateTime (2012, 10, 1, 14, 0 , 0);

for (int i = 0; i <= 10; ++i) {
   try { Console.WriteLine (SubtractEx (start, end.AddDays (i), times).TotalHours); }
   catch { Console.WriteLine ("Exception"); }
}

Console.WriteLine ("---");

start = new DateTime (2012, 10, 2, 14, 0 , 0);
end = new DateTime (2012, 10, 1, 9, 0 , 0);

for (int i = 0; i <= 10; ++i) {
   try { Console.WriteLine (SubtractEx (start, end.AddDays (i), times).TotalHours); }
   catch { Console.WriteLine ("Exception"); }
}

Wenn ich mich nicht vertan habe, müsste das folgende Ausgabe produzieren, jedenfalls tut meine Lösung das:


0
5
13
21
29
31
31
37
45
53
61
---
0
0
3
11
19
26
26
27
35
43
51

Statt der der Ausgabe 0 dürfte auch eine Exception fliegen, weil in den Fällen end vor start liegt. Mit deiner Methode erhalte ich folgende Ausgabe:


5
5
13
5,00027777777778
29
31
31
37
45
53
61
---
Exception
-5
-5
3
-4,99972222222222
26
26
27
35
43
51

Dass die Abfrage if ( _totalDaysBetween < 0 ) nicht reicht, um sicherzustellen, dass start ≤ end ist, lässt sich leicht beheben. Aber es muss auch noch an anderen Stellen irgendwelche Abweichungen geben. Vielleicht nur irgendeine Kleinigkeit. Für Zeiträume von mehr als 2 oder 3 Tagen scheint es schonmal zu stimmen.

herbivore

M
55 Beiträge seit 2011
vor 11 Jahren

Hallo,

anbei eine Version mit welcher die geposteten Testfälle erfolgreich durchlaufen werden.
Ansatt einer 0 wird allerdings eine Exception geworfen.
Bei einem speziellen Fall dürfte der Code evtl noch Probleme haben, aber diesen kann ich aktuell aufgrund von Zeitmangel nicht vor heute Abend beheben.


       public static TimeSpan SubtractEx(DateTime start, DateTime end, Dictionary<DayOfWeek, IList<TimeSpan>> timelist)
        {
            TimeSpan _result = new TimeSpan();
     
            // *** Aenderung ***
            TimeSpan _totalDaysBetween = end.Date.Subtract(start.Date);

            if ( end.Subtract(start) < new TimeSpan())
                throw new ArgumentException("Fehler! \n Ungueltige Zeiangabe");

            IList<TimeSpan> _firstList = new List<TimeSpan>();
            IList<TimeSpan> _secondList = new List<TimeSpan>();
            IList<TimeSpan> _tempList = new List<TimeSpan>();

            DateTime _firstDate = new DateTime();
            DateTime _secondDate = new DateTime();
            DateTime _tempDate = new DateTime();


            // *** Aenderung ***
            switch (_totalDaysBetween.Days)
            {
                case 0:
                    //Startdatum == Enddatum
                    timelist.TryGetValue(start.DayOfWeek, out _firstList);
                    _result = _result.Add(GetTimeSpanBetween(start, end, _firstList));
                    break;
                //-------------------------------------------------------------------------------------------
                case 1:
                    // Startdatum + 1Tag = Enddatum
                    _firstDate = new DateTime(start.Year,
                                              start.Month,
                                              start.Day,
                                              23, 59, 59);

                    _secondDate = new DateTime(end.Year,
                                               end.Month,
                                               end.Day,
                                               00, 00, 00);

                    timelist.TryGetValue(start.DayOfWeek, out _firstList);
                    timelist.TryGetValue(end.DayOfWeek, out _secondList);
                    _result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
                    _result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));
                    break;
                //-------------------------------------------------------------------------------------------
                case 2:
                    // Startdatum + 2Tage = Enddatum --> kein "voller" Tag dazwischen
                    _firstDate = new DateTime(start.Year,
                                              start.Month,
                                              start.Day,
                                              23, 59, 59);

                    _secondDate = new DateTime(end.Year,
                                               end.Month,
                                               end.Day,
                                               00, 00, 00);

                    _tempDate = start.AddDays(1);
                    _tempDate = new DateTime(_tempDate.Year,
                                             _tempDate.Month,
                                             _tempDate.Day,
                                             00, 00, 00);

                    DateTime _tempDateEnd = new DateTime(_tempDate.Year,
                                                         _tempDate.Month,
                                                         _tempDate.Day,
                                                         23, 59, 59);

                    timelist.TryGetValue(start.DayOfWeek, out _firstList);
                    timelist.TryGetValue(end.DayOfWeek, out _secondList);
                    timelist.TryGetValue(_tempDate.DayOfWeek, out _tempList);

                    _result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
                    _result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));
                    _result = _result.Add(GetTimeSpanBetween(_tempDate, _tempDateEnd, _tempList));
                    break;
                //-------------------------------------------------------------------------------------------
                default:
                    // Startdatum + 3Tage + X = Enddatum
                    _firstDate = new DateTime(start.Year,
                                              start.Month,
                                              start.Day,
                                              23, 59, 59);

                    _secondDate = new DateTime(end.Year,
                                               end.Month,
                                               end.Day,
                                               00, 00, 00);

                    timelist.TryGetValue(start.DayOfWeek, out _firstList);
                    timelist.TryGetValue(end.DayOfWeek, out _secondList);

                    _result = _result.Add(GetTimeSpanBetween(start, _firstDate, _firstList));
                    _result = _result.Add(GetTimeSpanBetween(_secondDate, end, _secondList));

                    //Alle Tage zwischen Startdatum+1 und Enddatum-1 ermitteln
                    DateTime _firstRelevantDay = start.AddDays(1);
                    DateTime _lastRelevantDay = end.Subtract(new TimeSpan(24, 0, 0));

                    _firstRelevantDay = new DateTime(_firstRelevantDay.Year,
                                                     _firstRelevantDay.Month,
                                                     _firstRelevantDay.Day,
                                                     00, 00, 00);

                    _lastRelevantDay = new DateTime(_lastRelevantDay.Year,
                                                    _lastRelevantDay.Month,
                                                    _lastRelevantDay.Day,
                                                    23, 59, 59);

                    int _fullDays = _lastRelevantDay.Subtract(_firstRelevantDay).Days;
                    _fullDays++;

                    // ganze + angebrochene wochen ermittlen
                    int _weeks = _fullDays / 7;
                    int _daysOverWeeks = _fullDays % 7;

                    _tempDate = _firstRelevantDay;
                    Dictionary<DayOfWeek, int> _dayOrder = new Dictionary<DayOfWeek, int>();
                    for (int i = 0; i < 7; i++)
                    {
                        if (i <= _fullDays)
                            _dayOrder.Add(_tempDate.AddDays(i).DayOfWeek, i);
                    }

                    if (_fullDays <= 7)
                    //Keine volle Woche zwishen start(+1) und ende(-1)
                    {
                        for (int i = 1; i <= _fullDays; i++)
                        {

                            timelist.TryGetValue(_firstRelevantDay.AddDays(i - 1).DayOfWeek, out _tempList);
                            DateTime _currentStart = _firstRelevantDay;
                            DateTime _currentEnd = new DateTime(_currentStart.Year,
                                                                _currentStart.Month,
                                                                _currentStart.Day,
                                                                23, 59, 59);

                            _result = _result.Add(GetTimeSpanBetween(_currentStart, _currentEnd, _tempList));
                        }
                    }
                    //mindestens 1tag ist dopplet vorhanden (excl start und ende)
                    else
                    {
                        for (int i = 0; i < 7; i++)
                        {
                            int _factor = _weeks;
                            if (i < _daysOverWeeks)
                                _factor++;

                            timelist.TryGetValue(_firstRelevantDay.AddDays(i).DayOfWeek, out _tempList);

                            DateTime _start = _firstRelevantDay;
                            DateTime _end = new DateTime(_start.Year,
                                                         _start.Month,
                                                         _start.Day,
                                                         23, 59, 59);

                            TimeSpan _tempSpan = GetTimeSpanBetween(_start, _end, _tempList);
                            TimeSpan _multiplied = new TimeSpan(_tempSpan.Days * _factor,
                                                                _tempSpan.Hours * _factor,
                                                                _tempSpan.Minutes * _factor,
                                                                _tempSpan.Seconds * _factor);

                            _result = _result.Add(_multiplied);
                        }
                    }

                    break;
            }
            return _result;
        }

        private static TimeSpan GetTimeSpanBetween(DateTime start, DateTime end, IList<TimeSpan> timelist)
        {
            TimeSpan _result = new TimeSpan();

            //timelist ist NULL  wenn kein eintrag fuer den Wochentag im Dict --> leeren TimeSpan zurück geben
            if (timelist != null)
            {
                for (int i = 0; i < timelist.Count; i += 2)
                {
                    //(Startzeit < TimeListZeit[n]) + (Endzeit < TimeListZeit[n+1]) --> TimeListZeit[n] + Endzeit nehmen
                    if ((start.TimeOfDay <= timelist[i]) && (end.TimeOfDay <= timelist[i + 1]))
                    {
                        _result = _result.Add(end.TimeOfDay.Subtract(timelist[i]));
                    }
                    //(Startzeit < TimeListZeit[n]) + (Endzeit > TimeListZeit[n+1]) --> TimeListZeit[n] + TimeListZeit[n+1] nehmen
                    else if ((start.TimeOfDay <= timelist[i]) && (end.TimeOfDay >= timelist[i + 1]))
                    {
                        _result = _result.Add(timelist[i + 1].Subtract(timelist[i]));
                    }
                    //(Startzeit > TimeListZeit[n]) + (Endzeit < TimeListZeit[n+1]) --> Startzeit       + Endzeit nehmen
                    else if ((start.TimeOfDay >= timelist[i]) && (end.TimeOfDay <= timelist[i + 1]))
                    {
                        _result = _result.Add(end.TimeOfDay.Subtract(start.TimeOfDay));
                    }
                    //(Startzeit > TimeListZeit[n]) + (Endzeit > TimeListZeit[n+1]) --> Startzeit       + TimeListZeit[n+1] nehmen
                    else if ((start.TimeOfDay >= timelist[i]) && (end.TimeOfDay >= timelist[i + 1]))
                    {
                        _result = _result.Add(timelist[i + 1].Subtract(start.TimeOfDay));
                    }
                    else
                    {
                        throw new ArgumentException("Fehler! \n Ungueltige Zeiangabe");
                    }
                }
            }
            return _result;
        }

Meine Ausgabe mit dem geposteten Testfall lautet:


Exception
5
13
21
29
31
31
37
45
53
61
---
Exception
Exception
3
11
19
26
26
27
35
43
51


Edit:
Habe den Code editiert.
Da nur 2 Zeilen geändert wurden habe ich keinen neuen Post erstellt.
Aus:


int _totalDaysBetween = end.DayOfYear - start.DayOfYear;

Wird:


// *** Aenderung ***
TimeSpan _totalDaysBetween = end.Date.Subtract(start.Date);

und aus:


switch (_totalDaysBetween)

wird:


// *** Aenderung ***
switch (_totalDaysBetween.Days)

G
47 Beiträge seit 2011
vor 11 Jahren

Hallo,

ich hab mal an der Performance gearbeitet...


        public static DateTime EndOfDay(this DateTime day)
        {
            return (day.AddDays(1)).Date;
        }

        public static TimeSpan SubtractEx( DateTime start, DateTime end, Dictionary<DayOfWeek, IList<TimeSpan>> timelist)
        {
            if (end < start)
                throw new ArgumentException("Das Enddatum muss nach dem Startdatum liegen.");

            IList<TimeSpan> todaysList;

            Dictionary<DayOfWeek, TimeSpan> timespanPerDay = new Dictionary<DayOfWeek, TimeSpan>();
            TimeSpan helperTimeSpan;
            TimeSpan timespanPerWeek=new TimeSpan();
            foreach (DayOfWeek dayofweek in Enum.GetValues(typeof(DayOfWeek)))
            {
                try
                {
                    todaysList = timelist[dayofweek];
                }
                catch (KeyNotFoundException)
                {
                    todaysList = null;
                }
                helperTimeSpan = new TimeSpan();
                if (todaysList != null)
                {
                    for (int i = 0; i < todaysList.Count; i += 2)
                    {
                        helperTimeSpan += todaysList[i + 1] - todaysList[i];
                    }
                    timespanPerDay.Add(dayofweek, helperTimeSpan);
                    timespanPerWeek += helperTimeSpan;
                }
            }

            int fulldays=(end.Date - start.EndOfDay()).Days;
            int fullweeks = (int)Math.Floor(fulldays / 7.0);
            TimeSpan result = new TimeSpan(fullweeks*timespanPerWeek.Ticks);

            DateTime calcend = end.Subtract(new TimeSpan(fullweeks*7,0,0,0));

            DateTime time = start;
            try
            {
                todaysList = timelist[time.DayOfWeek];
            }
            catch (KeyNotFoundException)
            {
                todaysList = null;
            }
            if (todaysList != null)
            {
                for (int i = 0; i < todaysList.Count; i += 2)
                {
                    if (time.TimeOfDay<=todaysList[i])
                        result+=(todaysList[i+1]-todaysList[i]);
                    else
                        if (time.TimeOfDay<todaysList[i+1])
                            result+=(todaysList[i+1]-time.TimeOfDay);
                }
            }
            time = time.AddDays(1);
            while (time.Date < calcend.Date)
            {
                result += timespanPerDay[time.DayOfWeek];
                time = time.AddDays(1);
            }
            time=calcend;

            try
            {
                todaysList = timelist[time.DayOfWeek];
            }
            catch (KeyNotFoundException)
            {
                todaysList = null;
            }
            if (todaysList != null)
            {
                for (int i = 0; i < todaysList.Count; i += 2)
                {
                    if (todaysList[i+1]<calcend.TimeOfDay)
                        result+=(todaysList[i+1]-todaysList[i]);
                    else
                        if (todaysList[i]<calcend.TimeOfDay)
                            result +=(calcend.TimeOfDay - todaysList[i]);
                }
            }
            return result;
        }
    }

Ich hoffe das genügt jetzt deiner Aufgabenstellung...

Gruß Gwinn

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo mcdt,

Bei einem speziellen Fall dürfte der Code evtl noch Probleme haben

so ist das. Bei (sehr) langen Zeiträumen kommen noch falsch Ergebnisse. Daher ist die Aufgabe noch nicht gelöst.

Hallo Gwinn,

der obige Testcode liefert mit deiner Methode folgende Ausgabe:


Exception
Exception
13
21
29
31
Exception
Exception
Exception
53
61
---
Exception
Exception
3
11
19
26
Exception
Exception
Exception
43
51

Also in einigen regulären Fällen fliegen noch Exceptions. Daher ist die Aufgabe noch nicht gelöst.

Zu Schönheit (die wie gesagt nicht gefordert ist) trotzdem noch ein Wort: Da fehlende Keys erlaubt sind, liegt keine Ausnahmesituation vor. Statt try-catch(KeyNotFoundException) solltest du besser überall Dictionary<>.TryGetValue verwenden (und dann auf null abfragen).

Außerdem hast du einer Stelle im Code ein DateTime.Add, ohne den berechneten Wert zu verändern. DateTime.Add ändert den übergebenen DateTime nicht, sondern liefert das Ergebnis der Berechnung nur zurück.

herbivore

G
47 Beiträge seit 2011
vor 11 Jahren

Also,


        public static DateTime EndOfDay(this DateTime day)
        {
            return (day.AddDays(1)).Date;
        }

        public static TimeSpan SubtractEx( DateTime start, DateTime end, Dictionary<DayOfWeek, IList<TimeSpan>> timelist)
        {
            bool negativ = false;
            if (end < start)
            {
                DateTime tmp = start;
                start = end;
                end = tmp;
                negativ = true;
            }

            IList<TimeSpan> todaysList;

            Dictionary<DayOfWeek, TimeSpan> timespanPerDay = new Dictionary<DayOfWeek, TimeSpan>();
            TimeSpan helperTimeSpan;
            TimeSpan timespanPerWeek=new TimeSpan();
            foreach (DayOfWeek dayofweek in Enum.GetValues(typeof(DayOfWeek)))
            {
                timelist.TryGetValue(dayofweek,out todaysList);
                helperTimeSpan = new TimeSpan();
                if (todaysList != null)
                {
                    for (int i = 0; i < todaysList.Count; i += 2)
                    {
                        helperTimeSpan += todaysList[i + 1] - todaysList[i];
                    }
                    timespanPerDay.Add(dayofweek, helperTimeSpan);
                    timespanPerWeek += helperTimeSpan;
                }
            }

            int fulldays=(end.Date - start.EndOfDay()).Days;
            int fullweeks = (int)Math.Floor(fulldays / 7.0);
            TimeSpan result = new TimeSpan(fullweeks*timespanPerWeek.Ticks);

            DateTime calcend = end.Subtract(new TimeSpan(fullweeks*7,0,0,0));

            DateTime time = start;
            timelist.TryGetValue(time.DayOfWeek,out todaysList);
            if (todaysList != null)
            {
                for (int i = 0; i < todaysList.Count; i += 2)
                {
                    if (time.TimeOfDay<=todaysList[i])
                        result+=(todaysList[i+1]-todaysList[i]);
                    else
                        if (time.TimeOfDay<todaysList[i+1])
                            result+=(todaysList[i+1]-time.TimeOfDay);
                }
            }
            time = time.AddDays(1);
            while (time.Date < calcend.Date)
            {
                if (timespanPerDay.TryGetValue(time.DayOfWeek, out helperTimeSpan));
                    result += helperTimeSpan;
                time = time.AddDays(1);
            }
            time=calcend;

            timelist.TryGetValue(time.DayOfWeek, out todaysList);

            if (todaysList != null)
            {
                for (int i = 0; i < todaysList.Count; i += 2)
                {
                    if (todaysList[i+1]<calcend.TimeOfDay)
                        result+=(todaysList[i+1]-todaysList[i]);
                    else
                        if (todaysList[i]<calcend.TimeOfDay)
                            result +=(calcend.TimeOfDay - todaysList[i]);
                }
            }
            if (negativ)
                result = -result;
            return result;
        }

Das liefert jetz mit einer Ausnahme deine Testergebnisse zurück.

DateTime.Substract liefert nämlich nicht 0, sondern einen negativen Wert, falls das Enddatum vor dem Startdatum liegt.

Gruß Gwinn

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Gwinn, hallo mcdt,

da wurde es ja auf die letzten Meter nochmal richtig knapp. Der Post mit der verbesserten Lösung von dir, Gwinn, ist von 12:58, das Edit mit der verbesserten Lösung von dir, mcdt, ist von 13:02. Ich habe mir beide Lösungen angeschaut und ich halte sie beide für richtig. Ihr habt die Aufgabe beide gelöst. Da du, Gwinn, einen Tick schneller warst, bist du mit der nächsten Aufgabe dran.

Unter dem Aspekt der Universialität ist die eine Behandlung für negative Werte, die du, Gwinn, eingebaut hast, etwas besser, da sie auch von der normalen Substract-Methode verwendet wird. Da ich aber nicht genau spezifiziert hatte, wie die Behandlung für start > end aussehen soll, ist auch das Werfen einer Exception, wie du, mcdt, es gemacht hast, korrekt.

Hier der Vollständigkeit halber noch meine Lösung (sicher noch verbesserungsfähig):


   public static TimeSpan SubtractEx (DateTime start, DateTime end, Dictionary <DayOfWeek, IList <TimeSpan>> times)
   {
      if (start >= end) {
         return new TimeSpan ();
      }

      int numDays = (end.Date - start.Date).Days + 1;
      int numCompleteWeeks = 0;
      if (numDays >= 9) {
         numCompleteWeeks = (numDays - 2) / 7;
         numDays -= numCompleteWeeks * 7;
      }

      TimeSpan sum = new TimeSpan ();
      TimeSpan sumWeek = new TimeSpan ();

      if (numCompleteWeeks > 0) {
         foreach (DayOfWeek dayOfWeek in Enum.GetValues (typeof (DayOfWeek))) {
            IList <TimeSpan> timesOfDay;
            times.TryGetValue (dayOfWeek, out timesOfDay);
            if (timesOfDay == null) { continue; }
            for (int i = 0; i < timesOfDay.Count; i += 2) {
               sumWeek += timesOfDay [i + 1] - timesOfDay [i];
            }
         }
         sum = new TimeSpan (sumWeek.Ticks * numCompleteWeeks);
      }

      TimeSpan time0 = new TimeSpan ();
      TimeSpan time24 = new TimeSpan (24, 0, 0);
      for (int i = 0; i < numDays; ++ i) {
         DayOfWeek dayOfWeek = (DayOfWeek)(((int)(start.DayOfWeek + i)) % 7);
         IList <TimeSpan> timesOfDay;
         times.TryGetValue (dayOfWeek, out timesOfDay);
         if (timesOfDay == null) { continue; }
         sum += SubtractEx ((i == 0 ? start.TimeOfDay : time0), (i == numDays - 1 ? end.TimeOfDay : time24), timesOfDay);
      }

      return sum;
   }

   public static TimeSpan SubtractEx (TimeSpan start, TimeSpan end, IList <TimeSpan> timesOfDay)
   {
      if (timesOfDay == null || start >= end) {
         return new TimeSpan ();
      }

      TimeSpan sum = new TimeSpan ();

      for (int i = 0; i < timesOfDay.Count; i += 2) {
         if (start >= timesOfDay [i + 1]) { continue; }
         if (end <= timesOfDay [i])       { break; }
         sum += (end   < timesOfDay [i + 1] ? end   : timesOfDay [i + 1])
              - (start > timesOfDay [i]     ? start : timesOfDay [i]);

      }

      return sum;
   }

Übrigens sind das - ohne die Leerzeilen - exakt 50 Zeilen.

herbivore

G
47 Beiträge seit 2011
vor 11 Jahren

Da ich bis jetz noch keine 'schöne' neue Aufgabe habe, gebe ich die Aufgabenstellung einfach mal frei.

Wer also eine neue Aufgabe stellen möchte, sei herzlich dazu eingeladen, das zu tun.

Gruß Gwinn

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Community,

als ich beim letzten Mal überlegt hatte, welche Aufgabe ich stelle, waren mir zwei Aufgaben eingefallen, die ich beide gleich gut und genauso spannend fand. Die mit den Zeitdifferenzen habe ich bereits gestellt, dann kommt jetzt das Berechnen von Quersummen. Und um es noch interessanter zu machen, das Ganze für beliebige Zahlensysteme.

Schreibe eine Methode [TT]Int32 Quersumme (Int32 wert, Int32 basis)[/TT], die mindestens für die Basen 2, 5, 8, 10 und 16 die Quersumme von [TT]wert[/TT] berechnet und zurückliefert, [B]ohne[/B] dabei den Umweg über Strings zu gehen, also alles rein mit Operatoren und Methoden der Klasse Int32.

Da die Herausforderung eher darin liegt, zu erkennen, wie man die Aufgabe am besten angeht, und wenn man das herausgefunden hat, sich die Methode selbst mit relativ wenig Code realisieren lässt, lege ich diesmal besonderen Wert auf korrekte (Fehler-)Behandlung alle richtigen und falschen Eingaben bezogen auf den [B]kompletten[/B] Wertebereich der verwendeten Typen.
[csharp]
                                          // wert     -> Quersumme
Console.WriteLine (Quersumme (10, 2));    // 1010 bin ->  2 dec
Console.WriteLine (Quersumme (10, 5));    //   20 pen ->  2 dec
Console.WriteLine (Quersumme (10, 8));    //   12 oct ->  3 dec
Console.WriteLine (Quersumme (10, 10));   //   10 dec ->  1 dec
Console.WriteLine (Quersumme (10, 16));   //    A hex -> 10 dec[/csharp]
[COLOR]Der Code wird einfacher, wenn man beliebige (zulässige) Werte für die Basis akzeptiert und keine Basis besonders behandelt. Wenn man die (nicht zwingende) Einschränkung auf die in der Aufgabe genannten Basiswerte umsetzen würde, würde es also nicht einfacher, sondern schwerer. :-)[/COLOR]

herbivore

G
47 Beiträge seit 2011
vor 11 Jahren

Hi herbivore,

ich hoffe ich habe keine Fehlerquellen übersehen.
Ungültige Argumente führen in meiner Version zu einer entsprechenden Exception.
Die Funktion nimmt als Argumente nichtnegative Zahlen und Basen von 2 bis 62.
Statt einer Exception könnte man auch den Ausgabestring entsprechend ändern. In Hinblick auf eine Anwendung gehe ich jedoch davon aus, dass man zuerst an dem Ergebniss der Berechnung interessiert ist. Deshalb habe ich als Rückgabewert die Quersumme gewählt und schreibe den Ausgabestring in den Parameter str.


        public static int Quersumme(Int32 zahl, Int32 basis, out String str)
        {
            Int32 quersumme = 0;
            Int32 ziffer;
            StringBuilder strbuilder = new StringBuilder();
            Int32 restzahl = zahl;
            if ((basis > 62) || (basis < 2))
                throw new ArgumentOutOfRangeException("Die Basis muss zwischen 2 und 62 liegen.");
            if (zahl < 0)
                throw new ArgumentOutOfRangeException("Die Quersumme ist nur für natürliche Zahlen definiert.");
            while (restzahl > 0)
            {
                ziffer = restzahl % basis;
                quersumme += ziffer;
                if (ziffer < 10)
                    strbuilder.Insert(0, ziffer);
                else
                    if (ziffer > 35)
                        strbuilder.Insert(0, (char)(ziffer + 61));
                    else
                        strbuilder.Insert(0, (char)(ziffer + 55));
                restzahl = (restzahl - ziffer) / basis;
            }
            str = zahl.ToString() + "= " + strbuilder.ToString() + " base " + basis.ToString() + " -> Quersumme " + quersumme.ToString();
            return quersumme;
        }

Gruß Gwinn

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Gwinn,

die Ausgabe der Zahl in dem jeweiligen Ziffernsystem war gar nicht gefordert. 😃 Es reicht die Berechnung der Quersumme, solange sie die nebenbei berechneten String-Repräsentation beruht. Nur auf die eigentliche Quersummenberechnung beziehe ich mich im folgenden.

Die Beschränkung, dass die Basis zwischen 2 und 62 liegen muss, ist zulässig. Die Beschränkung auf positive Werte auch. Die Definition in der deutschen Wikipedia bezieht sich ausdrücklich nur auf natürliche (und nicht etwa ganze) Zahlen.

Aus meiner Sicht gibt es drei Möglichkeiten, wie man die Quersumme negativer Zahlen definieren kann:

  1. gar nicht, sprich Exception
  2. als Quersumme des Absolutwerts der Zahl, also Quersumme (-x, y) == Quersumme (x, y)
  3. oder durch Negieren der Quersumme des Absolutwerts der Zahl, also Quersumme (-x, y) == -Quersumme (x, y)

Ich hätte alle drei Lösungen als richtig interpretiert, wobei mir definitionsmäßig die zweite Variante am logischsten erscheint, denn die Quersumme ist nach meinem Verständnis die Summe der Ziffern und Ziffern haben kein Vorzeichen. Nur die Zahl als ganzes hat ein Vorzeichen und zwar auch dann, wenn sie nur aus einer Ziffer besteht. Ob die dritte Definition bei Berechnungen besser geeignet ist, habe ich mir nicht überlegt. Und bei der ersten Definition macht man sich die Aufgabe in meinen Augen zu leicht 😃 weil man so das Problem umgeht, dass Math.Abs (Int32.MinValue) nicht definiert ist. Darauf zielte mein Hinweis, dass der komplette Wertbereich abgedeckt werden soll, was inbesondere deren Grenzen einschließt. Aber wie gesagt, ich akzeptiere alle drei Definitionen als richtig.

Und daher ist deine Lösung korrekt. Jetzt bist du aber wirklich mit einer neuen Aufgabe dran, denn es gilt das am Anfang des Threads Gesagte: "Postet nur eine Lösung, wenn ihr schon eine neue Aufgabe in petto habt."

Hier noch meine Lösung:


   public static Int32 Quersumme (Int32 wert, Int32 basis)
   {
      if (basis < 2) { throw new ArgumentException ("basis muss größer als eins sein"); }
      Int64 restwert = wert > 0 ? wert : -(Int64)wert;
      Int32 quersumme = 0;
      while (restwert > 0) {
         quersumme += (Int32)(restwert % basis);
         restwert /= basis;
      }
      return quersumme;
   }

herbivore

G
47 Beiträge seit 2011
vor 11 Jahren

Hallo Community,

da wir zuvor schon bei einer Springeraufgabe waren, hier meine Aufgabe:

Gesucht ist eine Funktion, die einen Lösungsweg für das Springerproblem für ein 8x8 Feld liefert.
Als Parameter soll die Funktion die Koordinaten des Startfeldes empfangen.

Viel Spaß

Gwinn

Edit: Um die Laufzeit nicht zu sehr in die Höhe zu treiben soll es genügen, wenn als Startpunkt eine Ecke vorgegeben wird.

D
31 Beiträge seit 2010
vor 11 Jahren

Ich hoffe mal, dass das so akzeptiert wird:

        public static int[,] Springer(int x, int y)
        {
            if(x >= 8 || x < 0 || y >= 8 || y < 0)
                throw new ArgumentException();
            int[,] field = new int[8,8];
            int count = 1;
            if (Springer(field, x, y, count))
                return field;
            return null;
        }

        private static bool Springer(int[,] field, int x, int y, int count)
        {
            field[x, y] = count++;
            if (count > field.Length)
                return true;
            foreach (int[] fields in AvailableFields(field, x, y))
            {
                if (Springer(field, fields[0], fields[1], count))
                    return true;
            }
            field[x, y] = 0;
            return false;
        }

        private static List<int[]> AvailableFields(int[,] field, int x, int y)
        {
            List<int[]> fields = new List<int[]>();
            int[] directions = new int[] { -2, -1, 1, 2 };
            foreach (int direction in directions)
            {
                if (x + direction >= 0 && x + direction < 8)
                {
                    int direction2 = direction % 2 == 0 ? 1 : 2;
                    if (y + direction2 < 8 && field[x + direction, y + direction2] == 0)
                        fields.Add(new int[] { x + direction, y + direction2 });
                    if (y - direction2 >= 0 && field[x + direction, y - direction2] == 0)
                        fields.Add(new int[] { x + direction, y - direction2 });
                }
            }
            return fields;
        }
B
2 Beiträge seit 2012
vor 11 Jahren

Quick&Dirty


using System;
using System.Collections.Generic;
using System.Drawing;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            solve(new Point(4, 7), 1);

            foreach (var pnt in solution)
                Console.WriteLine(pnt.ToString());
            Console.ReadKey(true);
        }

        static bool[,] board = new bool[8, 8];
        static List<Point> solution = new List<Point>();

        static Point[] vectors = new Point[] { new Point(1, 2),new Point(2, 1),new Point(2, -1),new Point(1, -2),
                                               new Point(-1, -2),new Point(-2, -1),new Point(-2, 1),new Point(-1, 2) };

        static bool valid(Point p)
        {
            return 0 <= p.X && p.X <= 7 && 0 <= p.Y && p.Y <= 7 && !board[p.X, p.Y];
        }

        static Point add(Point a, Point b)
        {
            return new Point(a.X + b.X, a.Y + b.Y);
        }

        static bool solve(Point pos, int depth)
        {
            if (depth == 1)
                solution.Add(pos);

            board[pos.X, pos.Y] = true;

            if (depth == 64)
                return true;

            for (int i = 0; i < vectors.Length; ++i)
            {
                Point next = add(pos, vectors[i]);

                if (valid(next))
                {
                    if (solve(next, depth + 1))
                    {
                        solution.Insert(1, next);
                        return true;
                    }

                    board[next.X, next.Y] = false;
                }
            }

            return false;
        }
    }
}


Ausgabe:


{X=4,Y=7}
{X=6,Y=6}
{X=7,Y=4}
{X=6,Y=2}
{X=7,Y=0}
{X=5,Y=1}
{X=6,Y=3}
{X=7,Y=5}
{X=5,Y=4}
{X=7,Y=3}
{X=6,Y=1}
{X=4,Y=0}
{X=5,Y=2}
{X=6,Y=4}
{X=7,Y=6}
{X=5,Y=5}
{X=6,Y=7}
{X=4,Y=6}
{X=6,Y=5}
{X=7,Y=7}
{X=5,Y=6}
{X=4,Y=4}
{X=3,Y=2}
{X=5,Y=3}
{X=7,Y=2}
{X=6,Y=0}
{X=4,Y=1}
{X=2,Y=0}
{X=0,Y=1}
{X=1,Y=3}
{X=2,Y=5}
{X=3,Y=7}
{X=4,Y=5}
{X=5,Y=7}
{X=3,Y=6}
{X=1,Y=7}
{X=0,Y=5}
{X=2,Y=6}
{X=0,Y=7}
{X=1,Y=5}
{X=2,Y=7}
{X=0,Y=6}
{X=1,Y=4}
{X=3,Y=3}
{X=2,Y=1}
{X=0,Y=0}
{X=1,Y=2}
{X=2,Y=4}
{X=0,Y=3}
{X=1,Y=1}
{X=3,Y=0}
{X=4,Y=2}
{X=3,Y=4}
{X=2,Y=2}
{X=4,Y=3}
{X=3,Y=5}
{X=1,Y=6}
{X=0,Y=4}
{X=2,Y=3}
{X=0,Y=2}
{X=1,Y=0}
{X=3,Y=1}
{X=5,Y=0}
{X=7,Y=1}

G
47 Beiträge seit 2011
vor 11 Jahren

Hi Community,

ich bin leider erst mit etwas Verzögerung dazu gekommen, mir die Lösungen anzusehen.
Funktionieren tun beide. Die Lösung von D4rkScr43m läuft dabei wesentlich performanter ab (auch als meine Lösung).
Da seine Lösung zudem etwas schneller kam, erhält er den Zuschlag für die nächste Aufgabe.

Gruß Gwinn

D
31 Beiträge seit 2010
vor 11 Jahren

Dann mal was ganz simples:

Schreibt eine Methode
void ExecuteHQ9Plus(String code, ref int acc);

Die den code nach den Regeln der "Programmiersprache" HQ9+ (HQ9+) abarbeitet und die Ausgabe auf der Konsole ausgibt. acc ersetzt dabei den Akkumulator.

Für den Fall dass im Code ein Nicht-Sprachelement vorkommt, hätte ich gerne anstatt der Ausgabe des Programms eine Fehlermeldung mit der Stelle des Fehlers.

Der Aufruf könnte z.B. so aussehen:

int i = 0;
ExecuteHQ9Plus("+H+QQH9++", ref i);
Console.WriteLine(i);
ExecuteHQ9Plus("HQ++++Q+H", ref i);
Console.WriteLine(i);
ExecuteHQ9Plus("++HQx++++", ref i);
Console.WriteLine(i);

und hätte dann z.B diese ausgabe:

Hello World!
+H+QQH9++
+H+QQH9++
Hello World!
[99 Bottles of beer... and so on...] // hier ist der gesammte Songtext gemeint.
4
Hello World!
HQ++++Q+H
HQ++++Q+H
Hello World!
9
Fehler im Programm an Stelle: 4 [...]x[...]
9
T
708 Beiträge seit 2008
vor 11 Jahren

Hallo D4rkScr43m,

"Nichts leichter als das" habe ich mir gedacht und bin dann prompt auf folgendes Ergebnis gekommen:

        private static void ExecuteHQ9Plus(string code, ref int acc)
        {
            int i = 1;
            foreach (var c in code.ToUpper().ToCharArray())
            {
                switch (c)
                {
                    case 'H':
                        Console.WriteLine("Hello World!");
                        break;
                    case 'Q':
                        Console.WriteLine(code);
                        break;
                    case '9':
                        Console.WriteLine("[99 Bottles of Beer on the Wall]");
                        break;
                    case '+':
                        acc++;
                        break;
                    default:
                        Console.WriteLine("Fehler im Programm an Stelle: {0} [...]{1}[...]", i, c);
                        break;
                }
                i++;
            }
        }

Diese "kompiliert" allerdings nicht und gibt bis zum Fehler alle bis dato gültigen Befehle aus.
Um das zu umgehen, aber auch die Stelle des verkehrten Zeichens heraus zu filtern habe ich dann einfach die Ausgabe zwischengespeichert. (Den Akkumulator ebenfalls)
Ist sicherlich nicht die schönste Lösung wegen des ganzen Zwischengespeichere, aber sie funktioniert:

        private static void ExecuteHQ9Plus(string code, ref int acc)
        {
            bool error = false;
            int accOriginal = acc;
            int i = 1;
            string output = string.Empty;
            foreach (var c in code.ToUpper().ToCharArray())
            {
                switch (c)
                {
                    case 'H':
                        output += "Hello World!\n";
                        break;
                    case 'Q':
                        output += code;
                        break;
                    case '9':
                        output += "[99 Bottles of Beer on the Wall]\n";
                        break;
                    case '+':
                        acc++;
                        break;
                    default:
                        output = String.Format("Fehler im Programm an Stelle: {0} [...]{1}[...]", i, c);
                        acc = accOriginal;
                        error = true;
                        break;
                }
                if (error)
                    break;
                i++;
            }
            Console.WriteLine(output);
        }
D
31 Beiträge seit 2010
vor 11 Jahren

Hallo trib,

da ist aber noch ein kleiner Fehler drin:
der Aufruf deines Programms mit

ExecuteHQ9Plus("qqqq", null);

für zu der Ausgabe von qqqqqqqqqqqqqqqq wobei eigentlich
qqqq
qqqq
qqqq
qqqq
heraus kommen müsste. Außerdem fehlt die Implementierung von 99 Bottles of beer.
Das ist aber wohl nur Fleißarbeit (oder C&P).

Also lass ich das mal so gelten, weitermachen!

T
708 Beiträge seit 2008
vor 11 Jahren

da ist aber noch ein kleiner Fehler drin:

Richtig, dort habe ich vergessen dem output += code noch ein "\n" oder besser noch System.Environment.NewLine hinzuzufügen.

Außerdem fehlt die Implementierung von 99 Bottles of beer.

Das ist im Code oben mit drin und funktioniert meines Erachtens auch.

Eine neue Aufgabe folgt in Kürze!

[EDIT]
Okay, dann habe ich es wirklich falsch verstanden. Der case für 9 muss dann natürlich so lauten:


for (int beer = 99; beer > 0; beer--)
{
    output += String.Format("{0} bottles of beer on the wall, {0} bottles of beer.{2}" +
                "Take one down and pass it around, {1} bottles of beer on the wall.{2}",
                beer, beer == 0 ? (beer - 1).ToString():"no more", System.Environment.NewLine);
}
output += String.Format("No more bottles of beer on the wall, no more bottles of beer.{0}" +
               "Go to the store and buy some more, 99 bottles of beer on the wall.", System.Environment.NewLine);

[EDIT 2]
Okay, ich habe das etwas zu schnell "hingepfuscht" ohne ausgiebig genug zu testen. Nun sollte aber alles wie gewünscht funktionieren und auch der Singular bei der letzten Flasche.
Die Codes aus deinem Link habe ich natürlich nicht genommen, weil ich das selbst implementieren wollte 😕


case '9':
   for (int beer = 99; beer > 0; beer--)
   {
   output += String.Format("{0} bottle{3} of beer on the wall, {0} bottle{3} of beer.{2}" +
       "Take one down and pass it around, {1} bottle{4} of beer on the wall.{2}",
       beer, beer > 1 ? (beer - 1).ToString():"no more", System.Environment.NewLine,
       (beer > 1) || (beer ==0) ? "s" : "", beer == 2 ? "" : "s");
   }
   output += String.Format("No more bottles of beer on the wall, no more bottles of beer.{0}" +
      "Go to the store and buy some more, 99 bottles of beer on the wall.{0}", System.Environment.NewLine);
    break;

D
31 Beiträge seit 2010
vor 11 Jahren

Außerdem fehlt die Implementierung von 99 Bottles of beer.
Das ist im Code oben mit drin und funktioniert meines Erachtens auch.

Ach ja? Ich hätte diese Ausgabe erwartet http://99-bottles-of-beer.net/lyrics.html
bei dir kommt ja nur die Zeile "[99 Bottles of Beer on the Wall]"

Aber wenn du willst, darfst du das ganze selbst implementieren ohne die Beispiele von http://99-bottles-of-beer.net/ zu verwenden 😃

76 Beiträge seit 2008
vor 11 Jahren

1 bottles? 😃

D
31 Beiträge seit 2010
vor 11 Jahren

1 bottles? 😃

Stell dir vor, du trinkst 98 Flaschen Bier ... wie viele Flaschen siehst du, wenn nur noch eine an der Wand steht? 😄

[EDIT]
Okay, dann habe ich es wirklich falsch verstanden. Der case für 9 muss dann natürlich so lauten:

  
for (int beer = 99; beer > 0; beer--)  
{  
    output += String.Format("{0} bottles of beer on the wall, {0} bottles of beer.{2}" +  
                "Take one down and pass it around, {1} bottles of beer on the wall.{2}",  
                beer, beer == 0 ? (beer - 1).ToString():"no more", System.Environment.NewLine);  
}  
output += String.Format("No more bottles of beer on the wall, no more bottles of beer.{0}" +  
               "Go to the store and buy some more, 99 bottles of beer on the wall.", System.Environment.NewLine);  

Womit wir wieder beim vergessenen System.Environment.NewLine sind, bzw. bei einem fehlenden {0}.

Außerdem wird in einer Schleife mit der Bedingung beer > 0 beer == 0 niemals zu true.

Und ich dachte schon, ich würde damit jemandem einen Gefallen tun...

T
708 Beiträge seit 2008
vor 11 Jahren

Hallo zusammen,

vorweg: Ich habe nochmal meine Lösung angepasst und hoffe nun alles erfüllt zu haben.
Dazu habe ich meinen letzten Beitrag editiert um die neue Aufgabe hier nicht unübersichtlich zu gestalten.

Aufgabe:
Die nächste Aufgabe ist ein kleines Bowlingspiel. Beziehungsweise die Auswertung dessen.

Regeln:
Es werden die allgemein gültigen Spielregeln angewendet Bowling - Spielregeln.
Demnach besteht jedes Spiel aus 10 Frames. Jeder Frame besteht aus bis zu zwei Würfen. Im Falle eines Strikes nur einem.
Der 10. (letzte) Frame macht die Ausnahme, werden dort ein Spare oder zwei Strikes geworfen, so darf der Spieler ein drittes Mal werfen.

Die maximale Punkteanzahl beträgt 300. Diese setzt sich aus der Summe aller Würfe zusammen.
Dabei werden nach einem Spare die Punkte aus dem Folgewurf addiert. Im Falle eines Strikes sogar die der beiden folgenden Würfe. (Bei einem "Miss" / "Pudel" natürlich 0-Punkte)

Durchführung:
Es sind zwei Funktionen gegeben:

public void Roll(int pins)

welche für jeden Wurf ausgeführt wird und

public int Score()

die die aktuelle Punktezahl ausgibt.

Einschränkungen:
Für eine gültige Lösung muss kein Wurfzähler implementiert werden. Sprich: Wie viele Würfe der Spieler noch hat.

Test:

//20 Würfe mit einem Punkt
for (int i = 0; i < 20; i++)
   Roll(1);
Assert.AreEqual(20, Score());
//Spare (10 + 3) +3
Roll(5);
Roll(5);
Roll(3);
Assert.AreEqual(16, Score());

Viel Spaß!
PS: Persönlich war ich überrascht, dass die Aufgabe doch etwas aufwändiger ist, als man zuerst denkt 😃

D
31 Beiträge seit 2010
vor 11 Jahren

Mal sehen ob das so OK ist. Einen Wurfzähler in dem Sinne habe ich ja nicht verwendet:

private bool? m_lastRollStrike = null; // null = normaler Wurf, false = spare, true = strike
private bool m_lastButOneRollStrike = false;
private bool m_firstRoll = true;
private int[] m_score = new int[] { -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1,
                                    -1, -1, -1, -1, -1,
                                    -1};

public void Roll(int pins)
{
    int roll = actualRoll();
    if (roll == -1 || (roll == 21 && m_lastRollStrike != null && !(bool)m_lastRollStrike))
        throw new NotSupportedException("Keine Würfe mehr! Zuerst die Punktzahl abrufen!");
    if (pins > 10)
        throw new ArgumentException("Es können nicht mehr als 10 Pins umgeworfen werden pro Wurf!");

    if (m_firstRoll)
    {
        if (m_lastButOneRollStrike)
            m_score[roll - 3] += pins;

        m_lastButOneRollStrike = false;
        if (m_lastRollStrike != null)
        {
            m_score[roll - 1] += pins;
            m_lastButOneRollStrike = (bool)m_lastRollStrike;
        }

        if (pins == 10)
        {
            if (roll + 2 < m_score.Length)
            {
                m_score[roll] = 0;
                m_score[roll + 1] = 10;
            }
            else
            {
                m_score[roll] = 10;
            }
            m_lastRollStrike = true;
        }
        else
        {
            m_score[roll] = pins;
            m_firstRoll = false;
            m_lastRollStrike = null;
        }
    }
    else
    {
        if (pins + m_score[roll - 1] > 10)
            throw new ArgumentException("Es wurden mehr Pins umgeworfen als noch standen!");

        m_score[roll] = pins;

        if (m_lastButOneRollStrike)
            m_score[roll - 2] += pins;

        if (m_score[roll - 1] + pins == 10)
            m_lastRollStrike = false;
        else
            m_lastRollStrike = null;

        m_lastButOneRollStrike = false;
        m_firstRoll = true;
    }
}

public int Score()
{
    int s = 0;
    for (int i = 0; i < m_score.Length; i++)
    {
        s += m_score[i] == -1 ? 0 : m_score[i];
        m_score[i] = -1;
    }
    m_firstRoll = true;
    return s;
}

private int actualRoll()
{
    for (int i = 0; i < m_score.Length; i++)
        if (m_score[i] == -1)
            return i;
    return -1;
}
T
708 Beiträge seit 2008
vor 11 Jahren

Hallo D4rkScr43m,

Folgender Test für einen Perfekten Lauf liefert einen Fehler:

for (int i = 0; i < 12; i++)
    Roll(10);
    //Für Spartaaaa
    Assert.AreEqual(300, Score());
Keine Würfe mehr! Zuerst die Punktzahl abrufen!
D
31 Beiträge seit 2010
vor 11 Jahren

hallo trib,

ich hab ausversehen das Endgame bei der Punkteberechnung doppelt mit eingerechnet.

Hier die korrigierte Version (Außerdem hab ich noch weitere Fehleingaben abgefangen):

private bool? m_lastRollStrike = null; // null = normaler Wurf, false = spare, true = strike
private bool m_lastButOneRollStrike = false;
private bool m_firstRoll = true;
private int[] m_score = new int[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };

public void Roll(int pins)
{
    int roll = actualRoll();
    if (roll == -1 || (roll == 20 && m_lastRollStrike == null) || (roll == 21 && (m_lastRollStrike == null || !(bool)m_lastRollStrike)))
        throw new NotSupportedException("Keine Würfe mehr! Zuerst die Punktzahl abrufen!");
    if (pins > 10)
        throw new ArgumentException("Es können nicht mehr als 10 Pins umgeworfen werden pro Wurf!");
    if(pins < 0)
        throw new ArgumentException("Die Anzahl der Pins die umgeworfen wurden muss größer gleich 0 sein!");
    if (m_firstRoll)
    {
        if (m_lastButOneRollStrike)
            m_score[roll - 3] += pins;

        m_lastButOneRollStrike = false;
        if (m_lastRollStrike != null)
        {
            m_score[roll - 1] += pins;
            m_lastButOneRollStrike = (bool)m_lastRollStrike;
        }

        if (pins == 10)
        {
            if (roll + 2 < m_score.Length)
            {
                m_score[roll] = 0;
                m_score[roll + 1] = 10;
            }
            else
            {
                m_score[roll] = 10;
            }
            m_lastRollStrike = true;
        }
        else
        {
            m_score[roll] = pins;
            m_firstRoll = false;
            m_lastRollStrike = null;
        }
    }
    else
    {
        if (pins + m_score[roll - 1] > 10)
            throw new ArgumentException("Es wurden mehr Pins umgeworfen als noch standen!");

        m_score[roll] = pins;

        if (m_lastButOneRollStrike)
            m_score[roll - 2] += pins;

        if (m_score[roll - 1] + pins == 10)
            m_lastRollStrike = false;
        else
            m_lastRollStrike = null;

        m_lastButOneRollStrike = false;
        m_firstRoll = true;
    }
}

public int Score()
{
    int s = 0;
    for (int i = 0; i < m_score.Length; i++)
    {
        if (i < m_score.Length - 2)
            s += m_score[i] == -1 ? 0 : m_score[i];
        m_score[i] = -1;
    }
    m_firstRoll = true;
    return s;
}

private int actualRoll()
{
    for (int i = 0; i < m_score.Length; i++)
        if (m_score[i] == -1)
            return i;
    return -1;
}
T
708 Beiträge seit 2008
vor 11 Jahren

ich hab ausversehen das Endgame bei der Punkteberechnung doppelt mit eingerechnet.

Hier die korrigierte Version (Außerdem hab ich noch weitere Fehleingaben abgefangen):

Meine UnitTests wurden alle bestanden und ich habe auch sonst nichts zu beanstanden!
In meiner Lösung habe ich die Berechnung ausschließlich in der Score() vorgenommen und in der Roll(int pins) nur die Werte gespeichert. Weiterhin hatte ich mit verschachtelten Klassen für den Frame und die dahinterliegenden Würfe gebaut. Das hat sich aber schnell als komplizierter herausgestellt als nötig. Die Version mit einem Array ist natürlich viel einfacher (auch wenn sie sich nicht sooooo korrekt anfühlt [Finde ich persönlich])

Dann bist Du dran, D4rkScr43m!

D
31 Beiträge seit 2010
vor 11 Jahren

Mir fällt beim besten Willen keine Aufgabe ein, also darf jeder der möchte eine stellen!

D
81 Beiträge seit 2009
vor 11 Jahren

Da mir gerade etwas eingefallen ist, warum denn nicht, wenn sonst niemand will?

Schreibe folgende Methoden:



        /// <summary>
        /// This is used to indicate in which direction the image is "looking"
        /// </summary>
        public enum ImageDirection
        {
            Top,
            Right,
            Bottom,
            Left
        }

        /// <summary>
        /// Calculates the angle between 2 Vectors
        /// </summary>
        /// <param name="p1">Vector 1</param>
        /// <param name="p2">Vector 2</param>
        /// <returns>Angle between the vectors</returns>
        public static float GetAngle(Point vectorA, Point vectorB);

        /// <summary>
        /// Calculates the angle
        /// </summary>
        /// <param name="p1">Point, where the image should be placed</param>
        /// <param name="p2">Point, where the image should "look" to</param>
        /// <param name="direction">The direction, where the image is "looking" actually</param>
        /// <returns>The Angle, that would be needed to rotate the image to "look" at p2</returns>
        public static float GetAngle(Point p1, Point p2, ImageDirection direction);

        public static Bitmap RotateImage (Bitmap image, float angle);


Aufgabe:
Die Methode "GetAngle" soll den Winkel in ° (deg) zurückgeben, der von beiden Vektoren eingeschlossen ist:

siehe Anhang

Bei der Überladung mit ImageDirection muss man zuerst die Vektoren berechnen

D
81 Beiträge seit 2009
vor 11 Jahren

Damit jeder noch weiß, was es mit dem "sehen" auf sich hat:

siehe Anhang

Das Flugzeug "sieht" nach oben, dass bedeutet, ich würde GetAngle mit ImageDirection.Top aufrufen!

Man darf sich darauf verlassen, dass die Vektoren richtig übergeben werden. Trotzdem sollen Exceptions geworfen werden wenn z. B. Angle außerhalb von 0-360 ° liegt oder das übergebene Bild null ist.

Ich hoffe die Aufgabenstellung ist klar genug formuliert. (🤔 Ansonsten wird Herbivore sich schon melden 😁

1.378 Beiträge seit 2006
vor 11 Jahren

Was mir fehlt ist der Ursprung der zwei Vektoren - oder wird automatisch 0,0 als Ursprung angenommen?

Lg, XXX

D
31 Beiträge seit 2010
vor 11 Jahren

Mal angenommen 0,0 sei der Ursprung für die durch die Punkte beschriebenen Vektoren:

public enum ImageDirection
{
    Top,
    Right,
    Bottom,
    Left
}

private static int SkalarProduct(Point vectorA, Point vectorB)
{
    return vectorA.X * vectorB.X + vectorA.Y * vectorB.Y;
}

private static double Laenge(Point vector)
{
    return Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}

public static float GetAngle(Point vectorA, Point vectorB)
{
    double d = SkalarProduct(vectorA, vectorB) / (Laenge(vectorA) * Laenge(vectorB));
    return (float)(Math.Acos(d) * 360 / (2 * Math.PI));
}

public static float GetAngle(Point p1, Point p2, ImageDirection direction)
{
    if (p1.Equals(p2))
        throw new ArgumentException("Der Punkt an dem das Bild steht und der, zu dem das Bild gucken soll sind gleich!");
    Point lookingAt = new Point();
    switch (direction)
    {
        case ImageDirection.Top:
            lookingAt = new Point(0, 1);
            break;
        case ImageDirection.Left:
            lookingAt = new Point(-1, 0);
            break;
        case ImageDirection.Right:
            lookingAt = new Point(1, 0);
            break;
        case ImageDirection.Bottom:
            lookingAt = new Point(0, -1);
            break;
    }
    Point newLookingAt = new Point(p2.X - p1.X, p2.Y - p1.Y);
    return GetAngle(lookingAt, newLookingAt);
}

public static Bitmap RotateImage(Bitmap image, float angle)
{
    if (float.IsNaN(angle))
        throw new ArgumentException("Der angegebene Winkel ist NaN!");
    if (angle == 0)
        return new Bitmap(image);
    Bitmap returnBitmap = new Bitmap(image.Width, image.Height);
    Graphics graphics = Graphics.FromImage(returnBitmap);
    graphics.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
    graphics.RotateTransform(angle);
    graphics.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
    graphics.DrawImage(image, new Point(0, 0));
    return returnBitmap;  
}

Ein kurzer Test in einem Form mit einer PictureBox zur Darstellung, einer TextBox zur eingabe des Guck-Zu-Punktes und einem Button zum Rotieren hat scheinbar funktioniert. Das einzige Problem: Das Bild wird auf diese Weise immer unschärfer, da sich bei Drehungen nicht um ein vielfaches von 90° mehrere Pixel den "Platz" teilen müssen.

Edit: habe mal die Kommentare aus der Aufgabenstellung entfernt um etwas Platz zu sparen.

D
81 Beiträge seit 2009
vor 11 Jahren

Im Anhang sieht man wie ich das gemeint hab (hoffentlich) 😃

Vektor 1 ergibt sich aus der ImageDirection. Ich hab einfach angenommen das der Vektor dann dadurch berechnet wird das p1 je nach Direction um 1 erhöht wird und somit der Vektor zustande kommt.

D
31 Beiträge seit 2010
vor 11 Jahren

Wie sieht es denn aus? Ist meine Lösung "richtig" genug? 😃

D
81 Beiträge seit 2009
vor 11 Jahren

Bei folgendem Fall dreht sich das Bild nicht in die richtige Richtung

(der rote Punkt ist der "new Looking at"-Punkt)

D
31 Beiträge seit 2010
vor 11 Jahren

Ja, das war wohl ein Problem, dass ich es nur mit einer Picturebox in der linken oberen Ecke probiert habe. Hier nochmal der korrigiert Code (Außerdem hatte ich nicht mehr daran gedacht, dass auf dem Bildschirm der Koordinatenursprung oben und nicht unten liegt 😃 )

public enum ImageDirection
{
    Top,
    Right,
    Bottom,
    Left
}

private static int SkalarProduct(Point vectorA, Point vectorB)
{
    return vectorA.X * vectorB.X + vectorA.Y * vectorB.Y;
}

private static double Laenge(Point vector)
{
    return Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}

public static float GetAngle(Point vectorA, Point vectorB)
{
    double d = SkalarProduct(vectorA, vectorB) / (Laenge(vectorA) * Laenge(vectorB));
    return (float)(Math.Acos(d) * 360 / (2 * Math.PI));
}

public static float GetAngle(Point p1, Point p2, ImageDirection direction)
{
    if (p1.Equals(p2))
        throw new ArgumentException("Der Punkt an dem das Bild steht und der, zu dem das Bild gucken soll sind gleich!");
    Point lookingAt = new Point();
    switch (direction)
    {
        case ImageDirection.Top:
            lookingAt = new Point(0, -1);
            break;
        case ImageDirection.Left:
            lookingAt = new Point(-1, 0);
            break;
        case ImageDirection.Right:
            lookingAt = new Point(1, 0);
            break;
        case ImageDirection.Bottom:
            lookingAt = new Point(0, 1);
            break;
    }
    Point newLookingAt = new Point(p2.X - p1.X, p2.Y - p1.Y);
    float angle = GetAngle(lookingAt, newLookingAt);
    if (p2.X < p1.X)
        return 360 - angle;
    else
        return angle;
}

public static Bitmap RotateImage(Bitmap image, float angle)
{
    if (float.IsNaN(angle))
        throw new ArgumentException("Der angegebene Winkel ist NaN!");
    if (angle == 0)
        return new Bitmap(image);
    Bitmap returnBitmap = new Bitmap(image.Width, image.Height);
    Graphics graphics = Graphics.FromImage(returnBitmap);
    graphics.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
    graphics.RotateTransform(angle);
    graphics.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
    graphics.DrawImage(image, new Point(0, 0));
    return returnBitmap;
}
D
81 Beiträge seit 2009
vor 11 Jahren

Alles klar, ich finde nichts mehr, du bist dran!

D
31 Beiträge seit 2010
vor 11 Jahren

Ok, dann hätte ich gerne eine möglichst performante Methode

static void GetDifferences(TextReader reader1, TextReader reader2, TextWriter writer)

die aus den Readern reader1 und reader2 Werte wie aus einer .ini-Datei auslesen und alle Unterschiede in den Writer schreiben.
als Beispiel:
Wenn ich diese 2 Ini-Dateien vergleichen wollte:

[Kategorie1]
wert1=abc
wert2=xyz
[Kategorie2]
wert3=bla
wert4=blub
[Kategorie3]
wert5=123
wert6=666

[Kategorie1]
wert1=xyz
wert2=xyz
[Kategorie4]
wert5=123
wert6=666
[Kategorie2]
wert3=bla
wert4=***

sollte das ergebnis so aussehen:

[Kategorie1]
wert1=xyz
[Kategorie4]
wert5=123
wert6=666
[Kategorie2]
wert4=***

Einfach gesagt sollte das Ergebnis dann reader2 ohne reader1 für unsortierte Kategorien und Werte sein.

Ich hoffe das ganze war verständlich. Es sollte für beliebig lange Datenströme funktionieren und wie gesagt, möglichst performant sein.

Edit: hatte in 2 Ini-Dateien nen Fehler im Kategorienamen, sollte aber das Ergebnis nicht beeinflussen

795 Beiträge seit 2006
vor 11 Jahren
using System;
using System.IO;
using System.Collections.Generic;

public class MyClass
{
    public static void Main()
    {
        string path1 = @"C:\temp\1.ini",
               path2 = @"C:\temp\2.ini",
               path3 = @"C:\temp\3.ini";

        using (Stream file1 = File.OpenRead(path1),
                      file2 = File.OpenRead(path2),
                      file3 = File.OpenWrite(path3))
        using (TextReader reader1 = new StreamReader(file1),
                          reader2 = new StreamReader(file2))
        using (TextWriter writer = new StreamWriter(file3))
        {
            GetDifferences(reader1, reader2, writer);

            writer.Flush();
            writer.Close();
            reader1.Close();
            reader2.Close();
        }
    }

    static void GetDifferences(TextReader reader1, TextReader reader2, TextWriter writer)
    {
        Dictionary<string, Dictionary<string, string>> file1 = new Dictionary<string, Dictionary<string, string>>(),
                                                       file2 = new Dictionary<string, Dictionary<string, string>>(),
                                                       diff = null;

        ReadIni(reader1, file1);
        ReadIni(reader2, file2);

        diff = DiffIni(file1, file2);

        WriteIni(writer, diff);
    }

    static void ReadIni(TextReader r, Dictionary<string, Dictionary<string, string>> d)
    {
        string line = null,
               category = null;

        while ((line = r.ReadLine()) != null)
        {
            if (line.StartsWith("[") && line.EndsWith("]"))
            {
                category = line.Substring(1, line.Length - 2);

                d.Add(category, new Dictionary<string, string>());
            }
            else if (line.Contains("="))
            {
                string key = line.Substring(0, line.IndexOf("=")),
                       value = line.Substring(line.IndexOf("=") + 1);

                d[category].Add(key, value);
            }
        }
    }

    static Dictionary<string, Dictionary<string, string>> DiffIni(Dictionary<string, Dictionary<string, string>> f1, Dictionary<string, Dictionary<string, string>> f2)
    {
        Dictionary<string, Dictionary<string, string>> result = new Dictionary<string, Dictionary<string, string>>();

        foreach (KeyValuePair<string, Dictionary<string, string>> category in f2)
        {
            if (f1.ContainsKey(category.Key))
            {
                foreach (KeyValuePair<string, string> key in category.Value)
                {
                    if (!f1[category.Key].ContainsKey(key.Key) || f1[category.Key][key.Key] != key.Value)
                    {
                        if (!result.ContainsKey(category.Key))
                        {
                            result.Add(category.Key, new Dictionary<string, string>());
                        }

                        result[category.Key].Add(key.Key, key.Value);
                    }
                }
            }
            else
            {
                result.Add(category.Key, category.Value);
            }
        }

        return result;
    }

    static void WriteIni(TextWriter w, Dictionary<string, Dictionary<string, string>> d)
    {
        foreach (KeyValuePair<string, Dictionary<string, string>> category in d)
        {
            w.WriteLine("[{0}]", category.Key);

            foreach (KeyValuePair<string, string> key in category.Value)
            {
                w.WriteLine("{0}={1}", key.Key, key.Value);
            }
        }
    }
}
`There are 10 types of people in the world: Those, who think they understand the binary system Those who don't even have heard about it And those who understand "Every base is base 10"`
D
31 Beiträge seit 2010
vor 11 Jahren

Funktioniert wie erwartet und ist allerdings nicht ganz so schnell wie meins. Bei entsprechend großen Dateien komme ich mit meiner Lösung auf ca. 9 Sekunden, wobei deine Lösung noch ca. 14 Sekunden braucht.

Der einzige Unterschied liegt wohl darin, dass ich Key und Value nicht auseinander ziehe sondern als einen langen String vergleiche.

Aber ich lass das mal durchgehen, mein erster Versuch lag bei den beiden Dateien bei über etwa 30 Sekunden.

309 Beiträge seit 2008
vor 11 Jahren

Um mal den Thread aus der Versenkung zu holen:

Ich möchte Code um die Größe von einer sequentiellen Struktur in Bytes abzurufen:

Beispiel:


[StructLayout(LayoutKind.Sequential)]
struct Foo
{
    public int baz;
    public ushort bar;
    public long foobar;
}

Ohne dabei Marshal.SizeOf() oder sizeof() zu benutzen.
Die Angabe muss aber mit Marshal.SizeOf() identisch sein (Stichwort: Byte Alignment).

Ist locker mit weniger als 10 Zeilen zu schaffen. 😉

using System;class H{static string z(char[]c){string r="";for(int x=0;x<(677%666);x++)r+=c[
x];return r;}static void Main(){int[]c={798,218,229,592,232,274,813,585,229,842,275};char[]
b=new char[11];for(int p=0;p<((59%12));p++)b[p]=(char)(c[p]%121);Console.WriteLine(z(b));}}

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Community,

die letzte Aufgabe scheint nicht so angekommen zu sein. Nachdem (deutlich) mehr als eine Woche ohne Lösung verhangen ist, greife ich den Thread wieder auf. Vielleicht habe ich mit dieser Aufgabe mehr Glück.

Die Aufgabe ist, die Methode

public static String FormatSI (Decimal d, int significantDigits, String unit)

zu implementieren, um Zahlen zu formatieren, und zwar so, dass folgende Bedingungen erfüllt sind:

  • Für Vielfache von Tausend sollen die passenden Vorsätze für Maßeinheiten verwendet werden (k, M, G, ..., m, µ, n, ...), so dass die eigentliche Zahl (Mantisse) immer ein bis drei Stellen vor dem Komma hat (ohne dass die Ziffer Null alleine vor dem Komma steht, es sei denn, die Zahl ist Null). Die Zahl 123456 würde also z.B. als 123k dargestellt werden. Die Zahl 0,1 würde als 100m (m für Milli, nicht Meter) dargestellt werden.

  • Ist die Zahl 1027 oder größer oder ist die Zahl 10-27 oder kleiner, soll statt der SI-Präfixe die Darstellung des Exponenten mit einem vorangestellten E erfolgen, also z.B. 123E27. Der verwendete Exponent muss dabei immer durch drei teilbar sein (27, 30, 33, ...).

  • Der Parameter significantDigits bestimmt, wie viele Ziffern dargestellt werden sollen bzw. auf wieviele Ziffern gerundet werden soll. Die Zahl 123456 würde also in Abhängigkeit von significantDigits wie folgt dargestellt werden:
    1 = 100k
    2 = 120k
    3 = 123k
    4 = 123,5k (Achtung: hier greift die Rundung)
    5 = 123,46k (Achtung: hier greift die Rundung)
    6 = 123,456k
    7 = 123,4560k

  • Der Parameter unit (z.B. "V") wird einfach an das bisherige Ergebnis angehängt, z.B. 123kV.

Es gibt noch drei weitere Rahmenbedingungen:

  • Es müssen nur positive Zahlen berücksichtigt werden. Die Bedingungen im obigen Text sind entsprechend formuliert. Wer negative Zahlen berücksichtigen will, sollte zu Anfang das Vorzeichen ermitteln und dann mit dem Absolutwert der Zahl weiterrechnen.

  • Die Methode soll keine if-Ketten enthalten. Sowas wie wenn d >= 1000, dann k, sonst wenn d >= 100000, dann M wäre also zu vermeiden. Insgesamt sollte eine elegante, kompakte Lösung angestrebt werden.

  • Testet die Methode gründlich, bevor ihr sie als Lösung postet. Beachtet insbesondere Grenz- und Sonderfälle. Die Methode soll für jede(*) mögliche Decimal-Zahl wie beschrieben funktionieren.

Ich wünsche euch viel Spaß!

herbivore

(*) Ausnahme sind - wie gesagt - negative Zahlen. Wenn ihr negative Zahlen wie beschreiben per Absolutwert behandeln wollt, sind negative Zahlen, die sich mit Math.Abs nicht in eine positive Zahl umwandeln lassen, ausgenommen.

A
15 Beiträge seit 2012
vor 11 Jahren

Hier ist mein Lösungsvorschlag.
Beim Runden auf die signifikanten Stellen, wollte ich keine eigene Methode schreiben, sondern die Mittel von .Net nutzen. Prinzipiell kann man das beim Umwandeln einer Zahl in eine Zeichenkette mit Hilfe des Formatzeichens G bzw. g lösen. Dabei wird bei der Ausgabe aber immer die kompakteste Schreibweise ausgegeben, so dass unter Umständen nicht die Komma-Schreibweise, sondern wissenschaftliche Notation ausgegeben wird, bsp. aus 123 wird bei 2 Signifkanten Stellen 1E+02. Daher parse ich in diesen Fällen die Zahl erneut als decimal und wandle sie wieder in einen String um. Ist etwas getrickst, aber funktioniert. Wenn jemand eine Idee hat wie man das schöner lösen kann, bzw. allgemeine Verbesserungsvorschläge hat, dann immer her damit. 😉

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    string TextOfNumber = "";
    string siPrefix = "";

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    d = scaleNumberAndGetSIPrefix(d, out siPrefix);
    TextOfNumber = getSIString(d, significantDigits);

    return sign + TextOfNumber + siPrefix + unit;
}


private static decimal scaleNumberAndGetSIPrefix(decimal d, out string prefix)
{
    decimal multiplicationFactor = (d < 1) ? 1000m : 0.001m;
    int indexStep = (d < 1) ? -1 : +1;
    int thousandsIndex = 0;
            
    if(d != decimal.Zero)
    {
        while(d < 1 || d >= 1000)
        {
            d *= multiplicationFactor;
            thousandsIndex += indexStep;
        }
    }

    prefix = getSIPrefix(thousandsIndex);
    return d;
}


private static string getSIPrefix(int thousandsIndex)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(thousandsIndex <= -9 || thousandsIndex >= 9)
        return "E" + thousandsIndex * 3;

    int offset = prefixes.Length / 2;
    return prefixes[thousandsIndex + offset];
}


private static string getSIString(decimal d, int significantDigits)
{
    // round to significant digits
    string textOfNumber = d.ToString("G" + significantDigits);
    if(textOfNumber.ToLower().Contains('e'))
    {
        // exponential notation to decimal point notation
        textOfNumber = decimal.Parse(textOfNumber, System.Globalization.NumberStyles.Float).ToString();
    }

    bool hasDecimalPoint = textOfNumber.Contains(',') || textOfNumber.Contains('.');
    int requiredLength = significantDigits;
    if(hasDecimalPoint)
        requiredLength++;

    // pad with zeros if necessary
    if(textOfNumber.Length < requiredLength)
    {
        if(!hasDecimalPoint)
        {
            textOfNumber += ",";
            requiredLength++;
        }
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}
49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Ace86,

vielen Dank für deinen Lösungsvorschlag. Die Aufteilung in Methoden gefällt mir gut. Das würde ich vermutlich nicht oder nicht viel anders machen.

Nur die Methode getSIString ist, wie du selbst geschrieben hast, nicht so schön. Es wäre wohl schöner, noch auf Ebene der Zahlen auszurechnen, wie korrekt formatiert werden müsste und dann passend zu formatieren, statt erst zu formatieren und dann auf String-Ebene rumzumanipulieren. Auch gibt es Probleme, wenn auf einem Rechner ein anderes Trennzeichen als Punkt oder Komma eingestellt ist.

Schönheit ist der eine Aspekt. Wichtiger ist jedoch die Korrektheit. Leider funktioniert deine Methode FormatSI noch nicht für alle Zahlen korrekt. Hier ein Gegenbeispiel:

FormatSI (950, 1, "") müsste "1k" liefern, liefert aber "1000". Das liegt unter anderem an der getSIString-Methode. Wenn du diese überarbeitest, könntest du versuchen, sie wie vorgeschlagen zu implementieren.

herbivore

A
15 Beiträge seit 2012
vor 11 Jahren

Die letzten Tage hatte ich wenig Zeit, aber heute bin ich wieder dazu gekommen die Methode zu überarbeiten:

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int thousandsExponent = 0;

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    d = scaleNumberAndGetSIPrefix(d, out thousandsExponent);
    d = round(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = round(d, significantDigits);
        thousandsExponent++;
    }

    return sign + getSIText(d, significantDigits) + getSIPrefix(thousandsExponent) + unit;
}


private static decimal scaleNumberAndGetSIPrefix(decimal d, out int thousandsExponent)
{
    decimal multiplicationFactor = (d < 1) ? 1000m : 0.001m;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;
            
    if(d != decimal.Zero)
    {
        while(d < 1 || d >= 1000)
        {
            d *= multiplicationFactor;
            expontent += indexStep;
        }
    }

    thousandsExponent = expontent;
    return d;
}


private static decimal scaleNumber(decimal d)
{
    int num;
    return scaleNumberAndGetSIPrefix(d, out num);
}


private static decimal round(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    double scaleExponent = Math.Floor(Math.Log10((double)d) - significantDigits + 1);
    decimal scaleFactor = (decimal)Math.Pow(10, scaleExponent);

    // scale to significant digits
    decimal a = d / scaleFactor;

    decimal b = Math.Round(a);

    // scale back
    return b * scaleFactor;
}


private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();
    string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
    bool hasDecimalSeparator = textOfNumber.Contains(decimalSeparator);

    int requiredLength = significantDigits;
    if(hasDecimalSeparator)
        requiredLength++;

    if(textOfNumber.Length < requiredLength)
    {
        if(!hasDecimalSeparator)
        {
            textOfNumber += ",";
            requiredLength++;
        }
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int thousandsIndex)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(thousandsIndex <= -9 || thousandsIndex >= 9)
        return "E" + thousandsIndex * 3;

    int offset = prefixes.Length / 2;
    return prefixes[thousandsIndex + offset];
}
49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Ace86,

das von mir genannte Rundungsproblem hast du behoben, aber dafür leider ein neues eingebaut. Das liegt daran, dass du einer Stelle fälschlich bzw. unbedachterweise mit double arbeitest.

Double hat nur eine Genauigkeit (Mantisse) von 15-16 Stellen, wogegen Decimal eine Genauigkeit (Mantisse) von 28-29 Stellen hat. Wenn man nun einen Decimal mit einer Mantisse von mehr als 16 Stellen verwendet, kann es zu falschen Ergebnissen führen.

Der folgende Code

decimal m = 9.9999999999999999m;
Console.WriteLine ("{0:G28}", m);
Console.WriteLine ("{0:G28}", (double)m);
Console.WriteLine (FormatSI (m, 18, ""));
Console.WriteLine (FormatSI (m, 17, ""));

produziert folgende Ausgabe


9,9999999999999999
10
9,99999999999999990
10,000000000000000

Die ersten beiden Zeilen zeigen, dass bei der Umwandlung einer ausreichend genauen decimal-Zahl nahe aber kleiner 10 in einen double, diese zwangsläufig auf 10 aufgerundet wird. In der Folge wird in deiner round-Methode ein falscher scaleExponent berechnet. Deshalb wird anschließend die decimal-Zahl eine Stelle zu weit links gerundet. Dadurch wird in der vierten Zeile 10,000000000000000 ausgegeben, obwohl 9,9999999999999999 ausgegeben werden müsste, denn die Ziffer hinter der letzten 9 - ist wie die dritte Zeile zeigt - eine 0, also dürfte in der vierten Zeile noch nicht aufgerundet werden.

Nenn mich pingelig 😃 aber ich hatte direkt in der Aufgabe ausdrücklich gefordert, dass "die Methode für jede mögliche Decimal-Zahl wie beschrieben funktionieren soll".

herbivore

PS: Sobald ich einen Fehler in einem Lösungsvorschlag gefunden habe, höre ich auf, intensiv nach weiteren Fehlern zu suchen. Es können also noch andere Fehler enthalten sein. Insbesondere wird Math.Round möglicherweise nicht so runden, wie es ToString tut, siehe z.B. Math.Round rundet anders als erwartet. Allerdings habe ich in der Aufgabe keine explizite Aussage dazu getroffen, wie genau gerundet werden soll. Also ist es eine Frage der Interpretation, ob man den Aufgabentext so interpretiert, dass zwangsläufig die in Deutschland übliche kaufmännische Rundung gemeint ist und man eine anderes Rundungsverfahren demzufolge als Fehler ansieht. Die Beispiele jedenfalls sind kaufmännisch gerundet.

A
15 Beiträge seit 2012
vor 11 Jahren

Nenn mich pingelig 😃 aber ich hatte direkt in der Aufgabe ausdrücklich gefordert, dass "die Methode für jede mögliche Decimal-Zahl wie beschrieben funktionieren soll".

Ist schon richtig, wenn die Methode nicht den Anforderungen entspricht, dann muss sie korrigiert werden. Ich frage mich aber warum nicht alle Methoden der Math Bibliothek auch für decimal implementiert sind.

Insbesondere wird Math.Round möglicherweise nicht so runden, wie es ToString tut, siehe z.B. Math.Round rundet anders als erwartet.

Gut zu wissen, ich bin bisher davon ausgegangen, dass Math.Round standardmäßig kaufmännisch rundet

Und jetzt zum eigentlichen Anliegen. Aller guten Dinge sind (hoffentlich) drei, hier ist meine überarbeitete Version:

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int powerOfThousand;
    string textOfNumber;
    string siPrefix;

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    powerOfThousand = getPowerOfThousand(d);
    d = scaleNumber(d);
    d = roundSignificant(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = roundSignificant(d, significantDigits);
        powerOfThousand++;
    }

    textOfNumber = getSIText(d, significantDigits);
    siPrefix = getSIPrefix(powerOfThousand);

    return sign + textOfNumber + siPrefix + unit;
}



private static int getPowerOfThousand(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 1000, out power);
    return power;
}


private static int getPowerOfTen(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 10, out power);
    return power;
}


private static decimal scaleNumber(decimal d)
{
    int power;
    return scaleNumberAndGetPower(d, 1000, out power);
}


private static decimal scaleNumberAndGetPower(decimal d, int upperBound, out int power)
{
    decimal factor = (d < 1) ? upperBound : 1m / upperBound;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;

    if(d != decimal.Zero)
    {
        while(d < 1 || d >= upperBound)
        {
            d *= factor;
            expontent += indexStep;
        }
    }

    power = expontent;
    return d;
}


private static decimal roundSignificant(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    decimal scaleFactor = (decimal)Math.Pow(10, getPowerOfTen(d) - significantDigits + 1);

    // scale to significant digits
    decimal a = d / scaleFactor;

    decimal b = Math.Round(a, MidpointRounding.AwayFromZero);

    // scale back
    return b * scaleFactor;
}



private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();

    if(d == decimal.Zero && significantDigits > 1)
    {
        string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
        int requiredLength = significantDigits + 1;

        textOfNumber += ",";
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int powerOfThousand)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(powerOfThousand <= -9 || powerOfThousand >= 9)
        return "E" + powerOfThousand * 3;

    int offset = prefixes.Length / 2;
    return prefixes[powerOfThousand + offset];
}
49.485 Beiträge seit 2005
vor 11 Jahren

Hallo Ace86,

ich gebe es zu, ich suche natürlich extra nach den fiesen Grenzfällen. Ein Decimal hat 28-29 signifikante Stellen. Wenn der Aufrufer von FormatSI die maximal mögliche Genauigkeit haben will, egal welche Zahl er übergibt, wird er 29 als Parameter für significantDigits übergeben. Dann kommt es aber auf die Größe der übergebenen decimal-Zahl an, ob deine Methode funktioniert.


{
   decimal m = 7.1234567890123456789012345678m;
   Console.WriteLine ("{0:G29}", m);
   Console.WriteLine (FormatSI (m, 28, ""));
   Console.WriteLine (FormatSI (m, 29, ""));
}

{
   decimal m = 8.1234567890123456789012345678m; // *
   Console.WriteLine ("{0:G29}", m);
   Console.WriteLine (FormatSI (m, 28, ""));
   Console.WriteLine (FormatSI (m, 29, "")); //**
}

Bei (**) kommt folgende Ausnahme:

Fehlermeldung:
Unbehandelte Ausnahme: System.OverflowException: Der Wert für eine Decimal war zu groß oder zu klein.
bei System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
bei System.Decimal.op_Division(Decimal d1, Decimal d2)
bei Versuch3.roundSignificant(Decimal d, Int32 significantDigits)
bei Versuch3.FormatSI(Decimal d, Int32 significantDigits, String unit)

(*) Zugegeben, die letzte 8 wird sowieso abgeschnitten (bzw. genaugenommen fürs Aufrunden der Ziffer davor verwendet). Eine decimal-Zahl, die mit 8 beginnt, kann maximal 28 signifikante Stellen haben. Aber zum einen greift hier die Argumentation von oben, dass ein Aufrufer für maximale Genauigkeit pauschal 29 significantDigits angeben möchte. Und außerdem hattest und hast du den Fall, dass significantDigits größer ist als die tatsächliche Anzahl der signifikanten Stellen der Zahl, ja explizit durch das PadRight berücksichtigt. Also sollte FormatSI auch bei significantDigits == 29 (eigentlich sogar bei significantDigits ≥ 29) immer korrekt funktionieren (wobei ich für significantDigits > 29 damit einverstanden wäre, wenn du den Fall genauso als Argumentfehler ansiehst wie significantDigits < 1).

Ich hoffe du hast den Spaß noch nicht verloren, sondern siehst es im Gegenteil als spannend und interessant an, wie viele Fehler in einer auf den ersten Blick korrekten Implementierung eines an sich überschaubaren Algorithmus stecken können, wenn man sehr genau hinschaut.

Wobei es für mich langsam eng wird. So viele Grenzfälle sehe ich nicht mehr. Du hast also gute Chancen für einen baldigen erfolgreichen Abschluss.

Ich bin sogar eben schon auf einen Grenzfall hineingefallen, bei dem ich dachte, dass deine Methode nicht korrekt funktioniert, sie es aber doch tut. Denn bei

decimal m = 1.2345678901234567890123456789e-27m;
Console.WriteLine (Versuch3.FormatSI (m, 3, ""));

wird "1,20E-27" ausgegeben, also wo ist die 3 hin? Aber die ist gar nicht in dem decimal gelandet, denn bei e-27 kann ein decimal nur noch max. 2 signifikante Ziffern darstellen, wie auch die Ausgabe "1,2E-27" der folgenden Zeile zeigt:

Console.WriteLine ("{0:G3}", m);

herbivore

A
15 Beiträge seit 2012
vor 11 Jahren

Ich hoffe du hast den Spaß noch nicht verloren, sondern siehst es im Gegenteil als spannend und interessant an, wie viele Fehler in einer auf den ersten Blick korrekten Implementierung eines an sich überschaubaren Algorithmus stecken können, wenn man sehr genau hinschaut.

An dieser Aufgabe sieht man sehr schön wie gemein die "Einschränkung" alle decimal Zahlen (bzw. die die sich mit Math.Abs als positive umwandeln lassen) ist, insbsondere wenn jemand so genau wie du prüft. Der Spaß ist aber noch nicht weg.

Ich betrachte significantDigits > 29 jetzt auch als Argumentfehler. Schon im letzten Lösungsvorschlag wurde PadRight nur noch verwendet, wenn d = 0 ist. Für alle anderen Fälle hatte die ToString-Methode schon den richtigen Wert ausgegeben. Wenn die Zahl wie in deinem Beispiel statt 29 nur 28 signifikante Stellen hat, hänge ich auch keine Null mehr an, da die letzte Stelle des decimals schon von anfang an gerundet sein kann. Darüber lässt sich sicherlich auch streiten. Es wäre aber auch kein Problem das anfügen der Nullen wieder wie in früheren Vorschlägen durchzuführen.

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int powerOfThousand;
    string textOfNumber;
    string siPrefix;

    if(significantDigits < 1)
        throw new ArgumentException("Der Parameter \"significantDigits\" muss größer als 0 sein.");

    if(significantDigits > 29)
        throw new ArgumentException("Der Parameter \"significantDigits\" darf nicht größer als 29 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    powerOfThousand = getPowerOfThousand(d);
    d = scaleNumber(d);
    d = roundSignificant(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = roundSignificant(d, significantDigits);
        powerOfThousand++;
    }

    textOfNumber = getSIText(d, significantDigits);
    siPrefix = getSIPrefix(powerOfThousand);

    return sign + textOfNumber + siPrefix + unit;
}



private static int getPowerOfThousand(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 1000, out power);
    return power;
}


private static int getPowerOfTen(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 10, out power);
    return power;
}


private static decimal scaleNumber(decimal d)
{
    int power;
    return scaleNumberAndGetPower(d, 1000, out power);
}


private static decimal scaleNumberAndGetPower(decimal d, int upperBound, out int power)
{
    decimal factor = (d < 1) ? upperBound : 1m / upperBound;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;

    if(d != decimal.Zero)
    {
        while(d < 1 || d >= upperBound)
        {
            d *= factor;
            expontent += indexStep;
        }
    }

    power = expontent;
    return d;
}


private static decimal roundSignificant(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    try
    {
        decimal scaleFactor = (decimal)Math.Pow(10, getPowerOfTen(d) - significantDigits + 1);

        decimal a = d / scaleFactor;
        decimal b = Math.Round(a, MidpointRounding.AwayFromZero);
        return b * scaleFactor;
    }
    catch(OverflowException)
    {
        return roundSignificant(d, --significantDigits);
    }
}



private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();

    if(d == decimal.Zero && significantDigits > 1)
    {
        string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
        int requiredLength = significantDigits + 1;

        textOfNumber += ",";
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int powerOfThousand)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(powerOfThousand <= -9 || powerOfThousand >= 9)
        return "E" + powerOfThousand * 3;

    int offset = prefixes.Length / 2;
    return prefixes[powerOfThousand + offset];
}