- Details
- Category: Code
- By Stuart Mathews
- Hits: 2846
I had a problem recently where a scheduled task was being scheduled to start at the same time as daylight savings time adjustments are made, i.e when the clocks move forward one hour later in the US and UK. This happens at different times for different time zones. For example, in Eastern, Pacific, and Atlantic standard time zones, this happens at 2 am on 10 March 2024. In the UK this happens on the 31 March at 1 am. What this means is that 02:00-2:59 am and 01:00 -01:59 respectively do not exist because at 2 am, the time changes to 3 am for the US timezones, and 1 am changes to 2 am in the UK.
The implication is that as those times don't exist in the timezone, the scheduled task would never run and if you try to convert those times (in those affected timezones) to say UTC like this:
TimeZoneInfo.ConvertTimeToUtc(scheduledtime, timezone);
You get an error:
The supplied DateTime represents an invalid time.
So I went about figuring out how to automatically adjust the time to 3 am in the US when 2 am comes around and adjusting to 2 am when 1 am is in the UK...
The upshot of this is that I created two solutions, one initially which theoretically was sound but was complex, and the second one which was after the realization that the first was too complex and that a simpler solution was possible.
First solution:
if (timezone.IsInvalidTime(scheduledTime) && timezone.SupportsDaylightSavingTime)
{
var adjustedTime = ToDaylightSavingTime(timezone, scheduledTime);
return adjustedTime;
}
Now in ToDaylightsavingTime (shown below) it tried to figure out, for any timezone, what the necessary adjustments needed to be made (and when) by inspecting the AdjustmentRules for the supplied timezone like this:
public static DateTime ToDaylightSavingTime(TimeZoneInfo timeZone, DateTime targetScheduledDateTime)
{
var currentOffset = timeZone.GetUtcOffset(targetScheduledDateTime);
var futureOffset = timeZone.GetUtcOffset(targetScheduledDateTime) +
GetAdjustment(timeZone.GetAdjustmentRules(), targetScheduledDateTime).DaylightDelta;
var newOffset = currentOffset - futureOffset;
var newDateTimeWithOffset = new DateTimeOffset(targetScheduledDateTime, newOffset);
// Apply adjustment
return DateTime.SpecifyKind(newDateTimeWithOffset.UtcDateTime, DateTimeKind.Unspecified);
}
Btw, here is how you'd determine which of the available adjustments apply to the scheduledTime::
private TimeZoneInfo.AdjustmentRule GetAdjustment(IEnumerable<TimeZoneInfo.AdjustmentRule> adjustments,
DateTime targetDateTime)
{
// Iterate adjustment rules for time zone
foreach (var adjustment in adjustments)
{
var start = CalculateTransitionDateTime(adjustment.DaylightTransitionStart, targetDateTime.Year);
var end = CalculateTransitionDateTime(adjustment.DaylightTransitionEnd, targetDateTime.Year);
var withinTransitionDate = targetDateTime >= start && targetDateTime <= end;
if (adjustment.DateStart.Year <= targetDateTime.Year &&
adjustment.DateEnd.Year >= targetDateTime.Year && withinTransitionDate)
return adjustment;
}
return null;
}
Anyway, with all this code being written and tested, I woke up the next morning and realized I didn't need any of it!
A far simpler solution was just to forget about determining which adjustments needed to apply for any timezone, and just always adjust by 1 hour forward regardless one of these invalid times was detected using timezone.IsInvalidTime():
if (timezone.IsInvalidTime(scheduledTime) && timezone.SupportsDaylightSavingTime)
{
// Move forward past invalid time by an hour
return scheduledTime.AddHours(1);
}
And some research shows that most timezones that observe Daylight Saving Time adjust by 1 hour: https://en.wikipedia.org/wiki/Daylight_saving_time_by_country
Anyway for posterity, here is my complex solution if anyone needs to figure out how to work with AdjustmentRules in the timezone.
One of the difficulties in my complex solution was determining if the scheduled time falls within one of the AdjustmentRules which indicates that adjustment must be made. It's difficult because sometimes the adjustment might be needed on a non-fixed date like the 2nd Sunday of March, so you need to figure out what the date is for the 2nd Sunday of March, and if the scheduled time falls on that 2nd Sunday or not (See GetFloatingTransitionDate()) :
public sealed class DaylightSavingTimeHelper
{
public static DateTime CalculateTransitionDateTime(TimeZoneInfo.TransitionTime transition, int year)
{
DateTime transitionDatetime;
if (transition.IsFixedDateRule)
{
// Fixed date such as 2:00 A.M. on November 3 eg:
// startTransition.TimeOfDay
// startTransition.Month
// startTransition.Day,
transitionDatetime = new DateTime(year,
transition.Month, transition.Day,
transition.TimeOfDay.Hour,
transition.TimeOfDay.Minute,
transition.TimeOfDay.Second);
}
else
{
// Non-fixed date such as 2:00 A.M. on first Sunday November
transitionDatetime = GetFloatingTransitionDateTime(transition, year);
}
return transitionDatetime;
}
private static DateTime GetFloatingTransitionDateTime(TimeZoneInfo.TransitionTime transition, int year)
{
// Code borrowed from Microsoft's examples here:
// see https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.transitiontime.week?view=net-8.0
// https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.transitiontime.isfixeddaterule?view=net-8.0
// For non-fixed date rules, get local calendar
var cal = CultureInfo.CurrentCulture.Calendar;
// Get first day of week for transition
// For example, the 3rd week starts no earlier than the 15th of the month
var startOfWeek = transition.Week * 7 - 6;
// What day of the week does the month start on?
var firstDayOfWeek = (int) cal.GetDayOfWeek(new DateTime(year, transition.Month, 1));
// Determine how much start date has to be adjusted
int transitionDay;
var changeDayOfWeek = (int) transition.DayOfWeek;
if (firstDayOfWeek <= changeDayOfWeek)
transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
else
transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);
// Adjust for months with no fifth week
if (transitionDay > cal.GetDaysInMonth(year, transition.Month))
transitionDay -= 7;
return new DateTime(year, transition.Month, transitionDay,
transition.TimeOfDay.Hour,
transition.TimeOfDay.Minute,
transition.TimeOfDay.Second);
}
private TimeZoneInfo.AdjustmentRule GetAdjustment(IEnumerable<TimeZoneInfo.AdjustmentRule> adjustments,
DateTime targetDateTime)
{
// Iterate adjustment rules for time zone
foreach (var adjustment in adjustments)
{
var start = CalculateTransitionDateTime(adjustment.DaylightTransitionStart, targetDateTime.Year);
var end = CalculateTransitionDateTime(adjustment.DaylightTransitionEnd, targetDateTime.Year);
var withinTransitionDate = targetDateTime >= start && targetDateTime <= end;
if (adjustment.DateStart.Year <= targetDateTime.Year &&
adjustment.DateEnd.Year >= targetDateTime.Year && withinTransitionDate)
return adjustment;
}
return null;
}
public static DateTime ToDaylightSavingTime(TimeZoneInfo timeZone, DateTime targetScheduledDateTime)
{
var currentOffset = timeZone.GetUtcOffset(targetScheduledDateTime);
var futureOffset = timeZone.GetUtcOffset(targetScheduledDateTime) +
GetAdjustment(connectorTimeZone.GetAdjustmentRules(), targetScheduledDateTime).DaylightDelta;
var newOffset = currentOffset - futureOffset;
var newDateTimeWithOffset = new DateTimeOffset(targetScheduledDateTime, newOffset);
// Apply adjustment
return DateTime.SpecifyKind(newDateTimeWithOffset.UtcDateTime, DateTimeKind.Unspecified);
}
}
So it was so much simpler than I thought it needed to be. I think this is one of the valuable things about trying to actually understand what you're trying to do so that you can realize what is simpler.
- Details
- Category: Code
- By Stuart Mathews
- Hits: 2544
Since Cyber Security Concepts and Education, I've been working through various projects and recently I found myself working with the Microsoft Windows API.
Anyone who's ever worked with C/C++ knows that a traditional way of getting data from any function is to inspect its return value. If you want to get multiple values back, a useful but clunky way is to pass out values back through 'out' parameters you initially supplied with the function call.
You might want to do this if you'd like to return, say an integer from the function, perhaps to indicate if the function was a success or not, and if not, you can inspect the reason set in one of the function's output parameters. This is usually done as pointers such that the function internals will modify the contents of the pointers and then you can inspect the return result and the pointers after you call the function. Something like this:
HRESULT result = WinApiFunc(int a, int b, char* buffer, int* count);
That might for example be used to have Windows write something into a buffer for you and write into a pointer how many/count bytes were actually written. So its returned multiple values but not all in the result result.
In LanguageExt you have the concept of an Either<X,Y> meaning you can get either and X or Y back from the function. This is useful because now you can use a wrapper function (that still calls this) but now deals with the 'out' parameters and resolves the situation to an Either return type: either it succeeded or if returned an error.
I put together an Either type to achieve this in C++:
Either<int, string> result = windowsApiFunctionWrapper();
Why I find this particularly useful is because I can wrap Windows API calls with all their 'out' parameters mess into less cognitive wrapper functions that hide this mess and then return either an expected result or the problem details if they occurred within the wrapper function.
This means that wrapper functions just return a single return type of Either. This makes my code look less complex, saving me from having to litter my logic with checking the out parameters and return results while structuring my code to be simple (without so many conditional statements).
I've also put together an Option<T> type, a-la-Language-Ext also. These can be found here.
Its one small step to making working with the Windows API a little nicer to look at.
- Details
- Category: Code
- By Stuart Mathews
- Hits: 2644
Since Counting and Permutations, I've been practising more C++. For example, last week (Lecture 6) we were given a task to add, subtract and multiply polynomials.
The main functionality that we are practising is overloading operators. Our solution is to be based on arrays, not lists or vectors etc so that does restrict possible implementation options.
Working with polynomials turned out to be quite complicated. A polynomial is a series of terms, so I decided to create a Term class. The idea here is to encapsulate the coefficient and the exponent into a Term datatype:
class Term
{
public:
/// <summary>
/// Create a term
/// </summary>
Term(int coeff, int exp): exp(exp), coeff(coeff), isCancelled(false) {}
/// <summary>
/// Default constructor
/// </summary>
Term(): coeff(0), exp(0), isCancelled(false) {}
/// <summary>
/// Copy another Term
/// </summary>
Term operator+(Term& other) // not actually using this
{
assert(exp == other.GetExponent());
return Term(coeff + other.GetCoeff(), exp);
}
/// <summary>
/// Get Coefficient in term
/// </summary>
int GetCoeff() const
{
return coeff;
}
/// <summary>
/// Set Coefficient
/// </summary>
void SetCoeff(int inCoeff)
{
coeff = inCoeff;
}
/// <summary>
/// Get term exponent
/// </summary>
int GetExponent() const
{
return exp;
}
/// <summary>
/// Set Exponent
/// </summary>
void SetExponent(int inExp)
{
exp = inExp;
}
/// <summary>
/// Get if term is cancelled
/// </summary>
bool IsCancelled() const
{
return isCancelled;
}
/// <summary>
/// Sets if the term is cancelled
/// </summary>
void SetIsCancelled(bool yesno)
{
isCancelled = yesno;
}
private:
int coeff;
int exp;
bool isCancelled;
};
We were encouraged to define getters an setters which, in my opinion just make the code look messy. Anyway, each term can optionally be cancelled. I incorporated this when I decided to implement a Simplify() function on the polynomial class. Speaking of the Polynomial class, I'll drop it in here and then try to discuss the various parts in isolation.
class Polynomial
{
public:
/// <summary>
/// Construct a empty polynomial with space for inNumTerm polynomials (can't grow)
/// </summary>
/// <param name="inNumTerms">Number of terms this polynomil has</param>
Polynomial(int inNumTerms, bool useSequentialExponents = false) : numTerms(inNumTerms)
{
terms = new Term[inNumTerms];
if(useSequentialExponents)
{
for(auto t = 0; t < inNumTerms;t++)
{
terms[t].SetExponent(t);
}
}
}
/// <summary>
/// Construct a polynomial as a series of input terms (copied)
/// </summary>
Polynomial(Term inTerms[], int inNumTerms)
{
terms = CopyTerms(inNumTerms, inTerms);
numTerms = inNumTerms;
}
/// <summary>
/// Construct a Polynomial by copying another
/// </summary>
/// <param name="other"></param>
Polynomial(const Polynomial& other)
{
terms = CopyTerms(other.GetNumTerms(), other.terms);
numTerms = other.GetNumTerms();
}
~Polynomial()
{
delete [] terms;
}
/// <summary>
/// a) Add two polynomials
/// </summary>
Polynomial operator+(const Polynomial other)
{
auto maxNumTerms = GetLargestExponent(*this, other) + 1;
// Ensure the polynomial are in standard format i.e, acending powers of x
Polynomial meStd = ToStdFormat(*this, maxNumTerms);
Polynomial youStd = ToStdFormat(other, maxNumTerms);
// Create empty resulting polynomial in standard format, i.e acending powers of x
Polynomial resultPoly(maxNumTerms, true);
// Add like terms
for(auto t = 0; t < maxNumTerms;t++)
{
resultPoly.terms[t].SetCoeff(meStd.terms[t].GetCoeff() + youStd.terms[t].GetCoeff());
}
return resultPoly;
}
/// <summary>
/// b) Subtract two polynomials
/// </summary>
Polynomial operator-(const Polynomial other)
{
auto largestExponent = GetLargestExponent(*this, other);
auto maxNumTerms = largestExponent + 1;
Polynomial meStd = ToStdFormat(*this, maxNumTerms);
Polynomial youStd = ToStdFormat(other, maxNumTerms);
Polynomial result(maxNumTerms, true);
for(auto t = 0; t < maxNumTerms;t++)
{
result.terms[t].SetCoeff(meStd.terms[t].GetCoeff() - youStd.terms[t].GetCoeff());
}
return result.Simplify();
}
/// <summary>
/// c) Assign one polynomial to another
/// </summary>
Polynomial& operator=(Polynomial& other)
{
terms = other.GetTerms();
numTerms = other.GetNumTerms();
}
/// <summary>
/// d) Multiply two polynomials
/// </summary>
Polynomial operator*(const Polynomial other)
{
Polynomial theirPoly = other;
// Result will have a fixed number of terms, i.e poly1.GetNumTerms() x poly2.GetNumTerms()
auto resultNumTerms = numTerms * theirPoly.GetNumTerms();
// Create empty polynomial
Polynomial resultPoly(resultNumTerms);
int resultTermNum = 0;
// Distribute and multiply our terms over other's terms
for(auto numOurTerm = 0; numOurTerm < numTerms; numOurTerm++)
{
for(auto numTheirTerm = 0; numTheirTerm < theirPoly.GetNumTerms(); numTheirTerm++)
{
auto theirTerm = theirPoly.GetTerms()[numTheirTerm];
// Multiply the coefficient
auto coeffResult = terms[numOurTerm].GetCoeff() * theirTerm.GetCoeff();
// calculate the exponent
int expResult = terms[numOurTerm].GetExponent() + theirTerm.GetExponent();
// Store the result into the result term
auto* ptrResultTerm = &resultPoly.GetTerms()[resultTermNum];
// store
ptrResultTerm->SetCoeff(ptrResultTerm->GetCoeff() + coeffResult); // combine with existing term
ptrResultTerm->SetExponent(expResult);
resultTermNum++; // move onto the next term to calculate in the result polynomial
}
}
// Return result but simplify the polynomial first
return resultPoly.Simplify();
}
/// <summary>
/// e) Overload += compound assignment operator
/// </summary>
Polynomial& operator+=(const Polynomial& other)
{
// Copy result of addition of two polynomials (use overloaded + operator on Polynomial)
auto temp = *this + other;
// assign result into self
numTerms = temp.GetNumTerms();
terms = CopyTerms( numTerms, temp.GetTerms());
return *this;
}
/// <summary>
/// e) Overload -= compound assignment operator
/// </summary>
Polynomial& operator-=(const Polynomial& other)
{
// Copy result of subtraction of two polynomials (use overloaded - operator on Polynomial)
auto temp = *this - other;
// assign result into self
numTerms = temp.GetNumTerms();
terms = CopyTerms(numTerms, temp.GetTerms());
return *this;
}
/// <summary>
/// Get the largest exponent
/// </summary>
int GetLargestExponent(Polynomial p1, Polynomial p2)
{
auto largestExponent = p1.GetDegree();
if(largestExponent < p2.GetDegree())
{
largestExponent = p2.GetDegree();
}
return largestExponent;
}
/// <summary>
/// Get Number of terms
/// </summary>
int GetNumTerms() const
{
return numTerms;
}
/// <summary>
/// Get terms
/// </summary>
Term* GetTerms()
{
return terms;
}
/// <summary>
/// Set terms
/// </summary>
void SetTerms(Term* inTerms)
{
terms = inTerms;
}
/// <summary>
/// Get highest exponent
/// </summary>
int GetDegree() const
{
auto largestExponent = 0;
for(auto t = 0; t < numTerms;t++)
{
if( terms[t].GetExponent() > largestExponent)
{
largestExponent = terms[t].GetExponent();
}
}
return largestExponent;
}
/// <summary>
/// Check if polynomial has a specific term
/// </summary>
bool HasTerm(int coeff, int exp)
{
for(auto t = 0; t < numTerms; t++)
{
if(terms[t].GetCoeff() == coeff && terms[t].GetExponent() == exp)
{
return true;
}
}
return false;
}
/// <summary>
/// Create new polynomial in standard format i.e, nx0 nx1 nx2 nx3 ...
/// </summary>
Polynomial ToStdFormat(Polynomial poly, int degree)
{
Polynomial std = Polynomial(degree, true);
for(auto t = 0; t < poly.numTerms;t++)
{
auto exp = poly.terms[t].GetExponent();
std.terms[exp].SetCoeff(poly.terms[t].GetCoeff());
}
return std;
}
private:
int numTerms;
Term* terms;
/// <summary>
/// Simplify/aggregate like terms in the polynomial
/// </summary>
Polynomial Simplify()
{
Polynomial result(numTerms);
for(auto t = 0; t < numTerms; t++)
{
// Add this term to the result and we'll add to it if we find like terms:
result.terms[t] = terms[t];
// Look for like terms in the other terms
for(auto t2 = 0; t2 < numTerms; t2++)
{
// Dont consider myself or other terms that are empty/cancelled as candiates to use for simplification
if(t == t2 || (terms[t2].IsCancelled() || terms[t].IsCancelled()))
{
continue;
}
// if exponents are the same, can simlify terms as like-terms:
if(terms[t].GetExponent() == terms[t2].GetExponent())
{
// Add like term to result term
auto* resultTerm = &result.GetTerms()[t];
resultTerm->SetCoeff(resultTerm->GetCoeff() + terms[t2].GetCoeff());
resultTerm->SetExponent(terms[t].GetExponent());
// cancel term
terms[t2].SetIsCancelled(true);
}
}
}
return result;
}
/// <summary>
/// Make a copy of inTerms and use that to replace our terms
/// </summary>
Term* CopyTerms(int inNumTerms, Term inTerms[])
{
auto newTerms = new Term[inNumTerms];
// Take a copy of all terms and use as our own
for (auto t = 0; t < inNumTerms; t++)
{
// copy term
newTerms[t] = inTerms[t];
}
return newTerms;
}
};
So let's start with one of the constructors. This one will create a new array of terms; you specify how many and then the 2nd argument allows the new, empty polynomial to be setup in the standard format for a polynomial which is where its terms are of ascending powers, otherwise, the exponent part for each term is set to just 0.
/// <summary>
/// Construct a empty polynomial with space for inNumTerm polynomials (can't grow)
/// </summary>
/// <param name="inNumTerms">Number of terms this polynomil has</param>
Polynomial(int inNumTerms, bool useSequentialExponents = false) : numTerms(inNumTerms)
{
terms = new Term[inNumTerms];
if(useSequentialExponents)
{
for(auto t = 0; t < inNumTerms;t++)
{
terms[t].SetExponent(t);
}
}
}
Next, this constructor will allow you to take the terms of another polynomial and use them as your own. It copies the terms and uses them to construct the new polynomial:
/// <summary>
/// Construct a polynomial as a series of input terms (copied)
/// </summary>
Polynomial(Term inTerms[], int inNumTerms)
{
terms = CopyTerms(inNumTerms, inTerms);
numTerms = inNumTerms;
}
This is similar to the next constructor, which is the copy constructor which just a copy of another polynomial and pretty much does the same as if you just passed in the copy of terms.
/// <summary>
/// Construct a Polynomial by copying another
/// </summary>
/// <param name="other"></param>
Polynomial(const Polynomial& other)
{
terms = CopyTerms(other.GetNumTerms(), other.terms);
numTerms = other.GetNumTerms();
}
To copy terms, I use this. It basically dynamically allocated a new set of terms in memory and makes clones of the incoming terms.
/// <summary>
/// Make a copy of inTerms and use that to replace our terms
/// </summary>
Term* CopyTerms(int inNumTerms, Term inTerms[])
{
auto newTerms = new Term[inNumTerms];
// Take a copy of all terms and use as our own
for (auto t = 0; t < inNumTerms; t++)
{
// copy term
newTerms[t] = inTerms[t];
}
return newTerms;
}
later, on I'm sure to delete the memory:
~Polynomial()
{
delete [] terms;
}
Here is where it starts becoming interesting, adding polynomials:
/// <summary>
/// a) Add to polynomials
/// </summary>
Polynomial operator+(const Polynomial other)
{
auto maxNumTerms = GetLargestExponent(*this, other) + 1;
// Ensure the polynomial are in standard format i.e, acending powers of x
Polynomial meStd = ToStdFormat(*this, maxNumTerms);
Polynomial youStd = ToStdFormat(other, maxNumTerms);
// Create empty resulting polynomial in standard format, i.e acending powers of x
Polynomial resultPoly(maxNumTerms, true);
// Add like terms
for(auto t = 0; t < maxNumTerms;t++)
{
resultPoly.terms[t].SetCoeff(meStd.terms[t].GetCoeff() + youStd.terms[t].GetCoeff());
}
return resultPoly;
}
The approach I took was to get two polynomials being added together into a format where we can just add each term position with the opposite term in the same position in the other polynomial.
That would be easy it's not guaranteed that the polynomials are like that. For instance, one could have a term with a power of 2 while the other polynomial doesn't have a term of degree 2, and starts instead at say, 4. So you need each polynomial to have the same number of terms, even if some are empty. That's what ToStdFormat function does. But before that, we need the resulting polynomial to also be the same number of terms. We determine the largest number of terms we need and then make polynomials of that degree and fill in the terms of the data and then add like terms. That's pretty much it.
I figured that we could do something similar with subtracting two polynomials. So converting the polynomials into the standard format makes it easier.
/// <summary>
/// b) Subtract two polynomials
/// </summary>
Polynomial operator-(const Polynomial other)
{
auto largestExponent = GetLargestExponent(*this, other);
auto maxNumTerms = largestExponent + 1;
Polynomial meStd = ToStdFormat(*this, maxNumTerms);
Polynomial youStd = ToStdFormat(other, maxNumTerms);
Polynomial result(maxNumTerms, true);
for(auto t = 0; t < maxNumTerms;t++)
{
result.terms[t].SetCoeff(meStd.terms[t].GetCoeff() - youStd.terms[t].GetCoeff());
}
return result.Simplify();
}
Here is where things get a lot more complicated. Multiplication. The problem with multiplication is that you've got to distribute terms, and ultimately you need to simplify like terms that are created as a result of the multiplication.
In essence, I'm just doing the procedure we learnt in school... Basically, we know that the number of resulting terms is a product of the number of terms of the two participating polynomials. We create a result polynomial with that many terms and then we set out to populate it.
/// <summary>
/// d) Multiply two polynomials
/// </summary>
Polynomial operator*(const Polynomial other)
{
Polynomial theirPoly = other;
// Result will have a fixed number of terms, i.e poly1.GetNumTerms() x poly2.GetNumTerms()
auto resultNumTerms = numTerms * theirPoly.GetNumTerms();
// Create empty polynomial
Polynomial resultPoly(resultNumTerms);
int resultTermNum = 0;
// Distribute and multiply our terms over other's terms
for(auto numOurTerm = 0; numOurTerm < numTerms; numOurTerm++)
{
for(auto numTheirTerm = 0; numTheirTerm < theirPoly.GetNumTerms(); numTheirTerm++)
{
auto theirTerm = theirPoly.GetTerms()[numTheirTerm];
// Multiply the coefficient
auto coeffResult = terms[numOurTerm].GetCoeff() * theirTerm.GetCoeff();
// calculate the exponent
int expResult = terms[numOurTerm].GetExponent() + theirTerm.GetExponent();
// Store the result into the result term
auto* ptrResultTerm = &resultPoly.GetTerms()[resultTermNum];
// store
ptrResultTerm->SetCoeff(ptrResultTerm->GetCoeff() + coeffResult); // combine with existing term
ptrResultTerm->SetExponent(expResult);
resultTermNum++; // move onto the next term to calculate in the result polynomial
}
}
// Return result but simplify the polynomial first
return resultPoly.Simplify();
}
This result however is like terms being in the resulting polynomial and that annoyed me. So I created a complicated simplify method, how ironic.
What this method does is it goes through each term in the result, and looks ahead for other like terms in it. If it finds it, it adds them together and puts the result back into the first term, and set the other like term to cancelled. Thats why the Term class has that function. Long story short, the resulting polynomial has a whole bunch of terms that are either aggregated by other like terms and those like terms that were used are cancelled and will not be printed when we print a polynomial. They are effectively 0 coefficient term after we used them:
/// <summary>
/// Simplify/aggregate like terms in the polynomial
/// </summary>
Polynomial Simplify()
{
Polynomial result(numTerms);
for(auto t = 0; t < numTerms; t++)
{
// Add this term to the result and we'll add to it if we find like terms:
result.terms[t] = terms[t];
// Look for like terms in the other terms
for(auto t2 = 0; t2 < numTerms; t2++)
{
// Dont consider myself or other terms that are empty/cancelled as candiates to use for simplification
if(t == t2 || (terms[t2].IsCancelled() || terms[t].IsCancelled()))
{
continue;
}
// if exponents are the same, can simlify terms as like-terms:
if(terms[t].GetExponent() == terms[t2].GetExponent())
{
// Add like term to result term
auto* resultTerm = &result.GetTerms()[t];
resultTerm->SetCoeff(resultTerm->GetCoeff() + terms[t2].GetCoeff());
resultTerm->SetExponent(terms[t].GetExponent());
// cancel term
terms[t2].SetIsCancelled(true);
}
}
}
return result;
}
I added the ability to print polynomials and terms too. You can see how I avoid printing cancelled terms or terms with 0 as a coefficient.
/// <summary>
/// Print a Term
/// </summary>
std::ostream& operator<<(std::ostream& stream, Term& term)
{
stream << term.GetCoeff() << "x^" << term.GetExponent();
return stream;
}
/// <summary>
/// Print a Polynomial
/// </summary>
std::ostream& operator<<(std::ostream& stream, Polynomial& poly)
{
for( auto t = 0; t < poly.GetNumTerms();t++)
{
if(poly.GetTerms()[t].IsCancelled() || poly.GetTerms()[t].GetCoeff() == 0)
{
// skip printing empty terms, ie terms with coefficent of 0
continue;
}
stream << poly.GetTerms()[t] << " ";
}
return stream;
}
To try this out let add two polynomials:
\[ (10x^2 + 2x^3) + (x^2 + 3x^3 + 2x^4) \]
This is the code that will input that from the user:
int main(int argc, char* argv[])
{
runTests();
Polynomial poly1 = GetPolynomial("p1");
cout << "p1 = " << poly1 << endl;
Polynomial poly2 = GetPolynomial("p2");
cout << "p2 = " <<poly2 << endl;
cout << endl;
Polynomial sum = poly1 + poly2;
cout << "p1 + p1: " << sum << endl;
Polynomial product = poly1 * poly2;
cout << "p1 * p2:: " << product << endl;
Polynomial difference1 = poly1 - poly2;
cout << "p1 - p2: " << difference1 << endl;
Polynomial difference2 = poly2 - poly1;
cout << "p2 - p1: " << difference2 << endl;
Polynomial compoundAddResult = poly1 += poly2;
cout << "p1 += p2: " << compoundAddResult << endl;
cout << "p1 now: " << poly1 << endl;
Polynomial compoundSubtractResult = poly1 -= poly2;
cout << "p1 -= p2: " << compoundSubtractResult << endl;
cout << "p1 now: " << poly1 << endl;
}
This is how I read in the polynomials in from the user:
Polynomial GetPolynomial(std::string polyName)
{
cout << "Enter the number of terms for " << polyName << ":";
int numTerms;
cin >> numTerms;
Term* terms = new Term[numTerms];
cout << "Enter the coefficients:";
for(int t = 0; t < numTerms;t++)
{
int coeff;
cin >> coeff;
terms[t].SetCoeff(coeff);
}
cout << "Enter the exponents:";
for(auto t = 0; t < numTerms;t++)
{
int exp;
cin >> exp;
terms[t].SetExponent(exp);
}
// Create polynomial
Polynomial result(terms, numTerms);
delete [] terms;
return result;
}
You need to specify the number of terms for each polynomial, then give its the coefficients then its exponents. So for:
\[ (10x^2 + 2x^3) + (x^2 + 3x^3 + 2x^4) \]
Enter the number of terms for p1:2
Enter the coefficients:10 2
Enter the exponents:2 3
p1 = 10x^2 2x^3
Enter the number of terms for p2:3
Enter the coefficients:1 3 2
Enter the exponents:2 3 4
p2 = 1x^2 3x^3 2x^4
p1 + p1: 11x^2 5x^3 2x^4
p1 * p2:: 10x^4 32x^5 26x^6 4x^7
p1 - p2: 9x^2 -1x^3 -2x^4
p2 - p1: -9x^2 1x^3 2x^4
p1 += p2: 11x^2 5x^3 2x^4
p1 now: 11x^2 5x^3 2x^4
p1 -= p2: 10x^2 2x^3
p1 now: 10x^2 2x^3
Which pretty much covers overloading the +, - and compound += and -= operators.
Also, the multiplication nicely handles implying the polynomial so no duplication occurs. However, the problem with it is that, unlike addition and subtraction, the result is not in the standard format.
It's got other problems too. It's too complicated, it can't handle provided polynomials that are not in the standard format, i.e like terms or out-of-order exponents etc. so on the while, its a bit iffy to say the least, but it works in the simple cases which serves its purpose.
Now, coffee.
More Articles …
- A software engineering process for delivering software
- Counting and Permutations
- Encrypting strings at rest
- Implementing a Vignette
- Functional programming paradigms and techniques
- Ruby RSpec let and let! diffirences
- Convolution, Running and Finite State Machines
- LanguageExt tutorial, games and timing
- Lambda Cross Account access with Serverless framework
- Mostly copies of itself
Subcategories
Game Development Article Count: 28
I discovered the realms of game development purely by accident, having picked up a book entitled 'Core Techniques and Algorithms in Game Programming' and discovered a surprising niche of innovation in programming quite unparalleled to my day-to-day needs as a developer. Here optimisation, graphics rendering, and algorithms are used on a totally different level and its very interesting.
Page 1 of 17