Most efficient way to loop through two C# Lists - c#

I have a method which I call CalculatePopularityScore. It exists on a Story object. The Story object has a field which is an ICollection of Comment objects.
public virtual ICollection<Comment> Comments { get; set; }
The Comment object has another collection of Reply objects.
My method looks at the story, loops through the comments associated with that story, and if the story's comments has replies, adds up that total. That, along with some other fields, gives me a very (and I stress this) very rudimentary algorithm of a story's popularity.
public double CalculateStoryPopularityScore()
{
if (Comments == null) throw new ArgumentException("Comments can't be null");
if (Comments.Count < 0) throw new ArgumentException("Comments can't be less than zero.");
int ReplyCountSum = 0;
double ReplyScore;
double CommentScore;
double InsightfulVoteScore;
double UsefulVoteScore;
double viewCount;
foreach (var comment in Comments)
{
int replyCount;
if (comment.Replies == null)
{
throw new ArgumentNullException("Replies cannot be null");
}
if (comment.Replies.Count() == 0)
{
replyCount = 0;
} else
{
replyCount = comment.Replies.Count();
}
ReplyCountSum += replyCount;
}
ReplyScore = ReplyCountSum * 4;
CommentScore = Comments.Count() * 4;
InsightfulVoteScore = InsightFulVoteCount * 3;
UsefulVoteScore = UsefulVoteCount * 2;
viewCount = ViewCount;
double PopularityScore = CommentScore + ReplyScore + InsightfulVoteScore + + UsefulVoteScore + viewCount;
return PopularityScore;
}
This seems to work fine. Now, what I'd like to do is take this method and apply it to a number of stories (i.e. a list).
I currently have this method written. It has not yet implemented another loop to look through the replies to the comments collection of a story. I know nested loops are considered bad and slow. What would be the most efficient way to look at the list of stories, then the list of comments in each story, add up those replies, and calculate a story's popularity score?
public void CalculateStoryPopularityScore(List<Story> stories)
{
if (stories == null) throw new ArgumentException("Stories can't be null");
double CommentScore;
double InsightfulVoteScore;
double UsefulVoteScore;
double PopularityScore;
double ViewCount;
foreach (var story in stories)
{
CommentScore = story.Comments.Count() * 4;
InsightfulVoteScore = story.InsightFulVoteCount * 3;
UsefulVoteScore = story.UsefulVoteCount * 2;
ViewCount = story.ViewCount;
PopularityScore = CommentScore + InsightfulVoteScore + UsefulVoteScore + ViewCount;
story.PopularityScore = PopularityScore;
}
}

Use SelectMany
var commentCount = story.Comments.Count();
// count all replies to all comments for a story
var replyCountSum = story.Comments
.SelectMany(c => c.Replies)
.Count();
Apply to a collection of stories:
stories.Select(s => new
{
Story = s,
CommentCount = s.Comments.Count(),
ReplyCount = s.Comments.SelectMany(c => c.Replies).Count(),
});

Unless I'm missing something, all the scores you're calculating with a separate method can instead be written as a public read-only (calculated) property of the Story class. The reply count can be obtained by using SelectMany (which is used to flatten lists of lists into a single list) and then getting the Count property:
public class Story
{
public List<Comment> Comments { get; set; }
public int InsightFulVoteCount { get; set; }
public int UsefulVoteCount { get; set; }
public int ViewCount { get; set; }
public int PopularityScore
{
get
{
return
(Comments?.Count ?? 0) * 4 +
(Comments?.SelectMany(comment => comment.Replies).Count() ?? 0) * 4 +
InsightFulVoteCount * 3 +
UsefulVoteCount * 2 +
ViewCount;
}
}
}
public class Comment
{
public List<string> Replies { get; set; }
}
In case you're not familiar with the null-conditional operator (?.), it returns null if the left operand (the object) is null before accessing the right operand (property or method of the object). If the left side is not null, then the property/method value is returned.
Then the null-coalescing operator (??) evaluates the left operand (which is the result of the property or method access) and, if it's null, it returns the right operand ('0' in our case).
Basically this simplifies the code. You don't have to do:
var score = 0;
if (Comments != null) score = Comments.Count;
You can just do:
var score = Comments?.Count ?? 0;

Related

Returning an object from a method in C#

I want to return an object as the result of a calculation in a method but I get "Cannot implicitly convert type 'ConsoleApp2Iteration.LoanOut' to 'double". The "return new LoanOut" is underlined. What is going wrong? I need the three outputs (InterestPaymentAnnual etc.) as input to other calculations elsewhere.
public class Loan
{
> initialisers here
public double LoanCalc()
{
double nper = LoanYrs * NprPrYr;//payment terms per yr
`double EffectiveRate = Math.Pow(1 + (InterestPct + ContribPct),
(Math.Pow(NprPrYr, -1))) - 1;`
//interest per payment term
double Interest_Contribution = InterestPct + ContribPct;
double length = nper;
double InterestPaymentP1 = 0;
{
Pymnt = Financial.Pmt(EffectiveRate, nper, LoanNPV, 0, 0);
LoanOutstanding = LoanNPV;
for (int i = 1; i <= length; i++)
{
// code block removed for clarity
if (i % NprPrYr != 0)
// testing for full years as derived calculations use input inyears
{
InterestPaymentAnnual += Interest_ContributionUSD;
RePymntAnnual += RePymnt;
}
else
{
InterestPaymentAnnual += Interest_ContributionUSD;
RePymntAnnual += RePymnt;
// new object which containts annual interest payment, annual repayment and
// remaining loan amount
return new LoanOut(InterestPaymentAnnual, RePymntAnnual,
LoanOutstanding);
//initialisation before new payment cycle
InterestPaymentAnnual = 0;
RePymntAnnual = 0;
}
}
}
return InterestPymntP1;
}
}
public class LoanOut
{
public double InterestPaymentAnnual { get; private set; }
public double RePymntAnnual { get; private set; }
public double LoanOutstanding { get; private set; }
double InterestPymntP1 = 0;
double Repayment = 0;
double DebtRemaining = 0;
public LoanOut(double InterestPaymentAnnual, double RePymntAnnual,
double LoanOutstanding)
{
this.InterestPaymentAnnual = InterestPymntP1;
this.RePymntAnnual = Repayment;
this.LoanOutstanding = DebtRemaining;
}
}
Your method is declared as returning a double and you have a statement
return new LoanOut(InterestPaymentAnnual, RePymntAnnual, LoanOutstanding);
Change the return type of your method to LoanOut:
public LoanOut LoanCalc()
As others have stated, you are trying to return both a LoanOut instance, and a double.
return new LoanOut(InterestPaymentAnnual, RePymntAnnual, LoanOutstanding); //Returning LoanOut Instance
and
return InterestPymntP1; //Returning double
if you need (or want) to gain two values from a method call, you should look at out parameters.
EG.
public LoanOut LoanCalc(out double InterestPayment)
{
//Your stuff here
}
You have to assign to InterestPayment for the calling code to use it.
Alternatively you can change your LoanOut class to also contain your 'InterestPayment' informaiton on that object. You would only NOT do that if the InterestPayment info doesn't relate to the LoanOut instance... which looking at it probably isn't the case.
Also, if you do fix this and keep the method structured as displayed in your question, You have unreachable code directly after the return new LoanOut line...
InterestPaymentAnnual = 0;
RePymntAnnual = 0;
will never get run
You have method public double LoanCalc() but you try to return new LoanOut(). So the error is correct, you can't convert LoanOut to double. Change the method signature to return LoanOut.
Also, if the pasted code formatting is what you have in your editor I would strongly suggest to reformat your code to make it more readable.
If i'm reading this right, you want to return a LoanOut but you're getting the error that you are trying to convert from LoanOut to double. Your method, however specifically says that you'll be returning a double:
public double LoanCalc()
I would try replacing double with LoanOut like this:
public LoanOut LoanCalc()
Does that help?

change string in C# [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am new to C# and not so expert in string manipulation. I have a string which represents the position of an object in diagram. The string contains integer values which I want to change for each object.
Example
String Position = "l=50; r=190; t=-430; b=-480";
I want to modify this string to
String Position = "l=50; r=190; t=-505; b=-555";
So if you notice t = -430 is changed to t = -505 and b = -480 to b = -555 which means an increment of -75 in both top and bottom
How can I do this ?
Thanks
If you want to easily populate or modify the values in your input string, you can use string.Format(), like this:
Position = string.Format("l={0}; r={1}; t={2}; b={3}", 50, 190, -430, -480);
You can extract the values of an existing input string using regular expressions, like this:
String Position = "l=50; r=190; t=-430; b=-480";
string pattern = #"^l=(\-{0,1}\d+); r=(\-{0,1}\d+); t=(\-{0,1}\d+); b=(\-{0,1}\d+)$";
var captGroups = Regex.Match(Position, pattern).Groups;
var l = captGroups[1];
var r = captGroups[2];
var t = captGroups[3];
var b = captGroups[4];
You need to parse your expression. Here is an example, in which the string is first splitted for ; and then each part is splitted by =:
var position = "l=50; r=190; t=-430; b=-480";
var parts = position.Split(';'); // split string into 4 parts
var assignments = new Dictionary<string, int>();
foreach (var part in parts)
{
var trimmedPart = part.Trim();
var assignmentParts = trimmedPart.Split('='); // split each part into variable and value part
var value = Int32.Parse(assignmentParts[1]); // convert string to integer value
assignments.Add(assignmentParts[0], value);
}
// change values
assignments["t"] = -505;
assignments["b"] = -555;
// build new string
var newPosition = String.Join("; ", assignments.Select(p => p.Key + "=" + p.Value));
Console.WriteLine(">> " + newPosition);
String Position = "l=50; r=190; t=-430; b=-480";
public void modifyPosition(int l, int r, int t, int b)
{
string[] parts = Position.Split(';');
int oldL = int.Parse(parts[0].Replace("l=","").Trim());
int oldR = int.Parse(parts[1].Replace("r=","").Trim());
int oldT = int.Parse(parts[2].Replace("t=","").Trim());
int oldB = int.Parse(parts[3].Replace("b=","").Trim());
Position = "l="+(oldL+l).ToString()+"; r="+(oldR+r).ToString()+
"; t="+(oldT+t).ToString()+"; b="+(oldB+b).ToString()+";";
}
There are a lot of ways to generalize and optimize that -- I'll leave those up to you...
To provide a further explanation to Jeroen van Langen's comment
You should parse the string into an object, change the values and reformat a string.
You can achieve this with a similar setup to this:
public class Position
{
public int l { get; set; }
public int r { get; set; }
public int t { get; set; }
public int b { get; set; }
public override string ToString()
{
return $"l = {l}, r = {r}, t = {t}, b = {b}";
}
}
(Change the access modifiers to suit your needs)
And work with the object (simply) as below:
public void UpdatePostion()
{
// Create new position
Position pos = new Position
{
l = 50,
r = 190,
t = -430,
b = -480
};
Console.WriteLine($"Pos before change: {pos.ToString()}");
// change top and bottom value to accommodate increment
pos.t += -75;
pos.b += -75;
// Prove that they've been updated
Console.WriteLine($"Pos after change: {pos.ToString()}");
}
You don't usually want to manipulate data in string form unless the data is actually a string. In this case, you've really got a structure which is being represented as a string, so you should turn it into a structure first.
Based on the string you provided, this turns any string of a similar form into a dictionary.
var source = "l=50; r=190; t=-505; b=-555";
var components = source.Split(';').Select(x => x.Trim());
// components is now an enumerable of strings "l=50", "r=190" etc.
var pairs = components.Select(x => x.Split('=').ToArray());
// pairs is now an enumerable of arrays of strings ["l", "50"], ["r", "190"] etc;
var dictionary = pairs.ToDictionary(x => x[0], x => x[1]);
// dictionary now contains r => 190, l => 50 etc.
Of course, you want to play with actual numbers, so you want to convert the values to integers really, so swap that last line out:
var dictionary = pairs.ToDictionary(x => x[0], x => Convert.ToInt32(x[1]));
Now you have a Dictionary which you can manipulate:
dictionary["t"] = dictionary["t"] - 75;
For example. Or you could use this as the basis of populating an object with appropriate properties and manipulate those.
struct Position {
public int L { get; set; }
public int R { get; set; }
public int T { get; set; }
public int B { get; set; }
}
Of course you might want to change it back into a string when you're done manipulating it:
var newString = string.Join("; ", dictionary.Select(x => $"{x.Key}={x.Value}"));
Although if you're planning to manipulate it a lot you should keep the parsed version around for as long as possible, as the parse/render cycle could be a performance issue if you do a lot of it.
Always store your data in appropriate types. Write new classes, write new structs, they don't have to be complicated but the more you can give names and shapes to your data the easier it is to manipulate it and to ensure that you manipulated it correctly.
This is all very simplistic of course - there's no error handling, such as if the value part isn't a valid Int32 (Convert.ToInt32 will throw an exception) or if there are duplicate keys (ToDictionary will throw an exception) etc. But hopefully it gives you some idea where to go.
It's also not the only way you could parse this kind of string, but it's fairly straightforward to follow and shouldn't be too slow, although it's not going to be very memory-efficient on a large input string.
Check this fiddle here.
public class MyObject
{
public int r {get;set;}
public int l {get;set;}
public int t {get;set;}
public int b {get;set;}
public MyObject()
{
}
public void ParseFromString(string val)
{
string[] splitVal = val.Split(';');
int intVal ;
if(!int.TryParse(splitVal[0].Replace("l=","").Trim(), out intVal))
intVal = 0;
this.l = intVal;
if(!int.TryParse(splitVal[1].Replace("r=","").Trim(), out intVal))
intVal = 0;
this.r = intVal;
if(!int.TryParse(splitVal[2].Replace("t=","").Trim(), out intVal))
intVal = 0;
this.t = intVal;
if(!int.TryParse(splitVal[3].Replace("b=","").Trim(), out intVal))
intVal = 0;
this.b = intVal;
}
public string ParseToString()
{
return "l=" + this.l + "; r=" + this.r + "; t=" + this.t + "; b=" + this.b + "";
}
}
So, building on one of the comments suggestions about parsing into an object - you could represent your Position object as follows:
public class Position
{
public int Left { get; set; }
public int Right { get; set; }
public int Top { get; set; }
public int Bottom { get; set; }
public Position(string str)
{
int[] values = str.Split(new[] { "; " }, StringSplitOptions.None)
.Select(s => Convert.ToInt32(s.Substring(s.IndexOf('=') +1))).ToArray();
Left = values[0];
Right = values[1];
Top = values[2];
Bottom = values[3];
}
public override string ToString()
{
return string.Join("; ", Left, Right, Top, Bottom);
}
}
You could then use this object to serialize/deserialize your string to and from an object as follows:
string intputString = "l=50; r=190; t=-430; b=-480";
Position p = new Position(intputString);
p.Top -= 75;
p.Bottom -= 75;
string outputString = p.ToString();`
The only thing of any real complexity is turning your string into the object values. To do this we can delimit the string into each individual value by splitting the string on "; ". Each value can then be turned into an integer by taking the substring of each element after the equals character ("l=50" becomes "50") and converting it into an integer.
I think the best approach would be to use a regex in this case:
var position = "l=50; r=190; t=-430; b=-480";
var match = Regex.Match(position, #"l=(-?\d+); r=(-?\d+); t=(-?\d+); b=(-?\d+)");
if (!match.Success)
throw new Exception("invalid data");
var l = Convert.ToInt32(match.Groups[1].Value);
var r = Convert.ToInt32(match.Groups[2].Value);
var t = Convert.ToInt32(match.Groups[3].Value);
var b = Convert.ToInt32(match.Groups[4].Value);
// modify the values as you like, for example: t -= 75; b -=75;
var newPosition = $"l={l}; r={r}; t={t}; b={b}";
I think a simple method can do the trick:
public static string ModifyPositions(string positionsInput, int displacement)
{
string input = "l=50; r=190; t=-430; b=-480";
var pattern = #"(t|b)=(-?\d+)";
var regex = new Regex(pattern);
var matches = regex.Matches(input);
foreach (Match match in matches)
{
input = input.Replace(match.Groups[2].Value, (int.Parse(match.Groups[2].Value) + displacement).ToString());
}
return input;
}
In your case displacement is -75, positionsInput is "l=50; r=190; t=-430; b=-480"
You can create a Position structure, like this:
public struct Position
{
public int Left { get; private set; }
public int Top { get; private set; }
public int Right { get; private set; }
public int Bottom { get; private set; }
public Position(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public Position(string positionText)
{
string pattern = #"(?=l=(?<left>[\d\-]+))|(?=t=(?<top>[\d\-]+))|(?=r=(?<right>[\d\-]+))|(?=b=(?<bottom>[\d\-]+))";
Match match = Regex.Match(positionText, pattern);
int left = Convert.ToInt32(match.Groups["left"].Value);
int top = Convert.ToInt32(match.Groups["top"].Value);
int right = Convert.ToInt32(match.Groups["right"].Value);
int bottom = Convert.ToInt32(match.Groups["bottom"].Value);
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public Position Move(int leftDelta = 0, int topDelta=0, int rightDelta=0, int bottomDelta = 0)
{
return new Position(Left + leftDelta, Top + topDelta, Right + rightDelta, Bottom + bottomDelta);
}
public override string ToString()
{
return string.Format("l={0}; r={1}; t={2}; b={3}", Left, Right, Top, Bottom);
}
}
Then you can use it like this:
Position oldPosition = new Position("l=50; r=190; t=-430; b=-480");
Position newPosition = oldPosition.Move(topDelta: -75, bottomDelta: -75);
string newPositionText = newPosition.ToString();

Use LINQ To find multiple instances of Min() in Object and change the element(s)

I've the following classes;
public class PricePlan
{
public string Name { get; set; }
public List<Price> Prices { get; set; }
public PricePlan()
{
Prices = new List<Price>();
}
}
public class Price
{
public DateTime Date { get; set; }
public decimal Rate { get; set; }
public bool Free { get; set; }
public Price()
{
Free = false;
}
}
And then the following to populate the object and list;
PricePlan oPricePlan = new PricePlan();
oPricePlan.Name = "Standard Rate Plan";
Price oPrice;
DateTime oDate = DateTime.Today;
for (int x = 1; x < 10; x++)
{
oPrice = new Price();
oPrice.Date = oDate.AddDays(x);
oPrice.Rate = 10 * x;
oPricePlan.Prices.Add(oPrice);
}
oPrice = new Price();
oPrice.Date = oDate.AddDays(11);
oPrice.Rate = 10;
oPricePlan.Prices.Add(oPrice);
The sample data might be:
02/01/2013,10,False
03/01/2013,20,False
04/01/2013,30,False
05/01/2013,40,False
06/01/2013,50,False
07/01/2013,60,False
08/01/2013,70,False
09/01/2013,80,False
10/01/2013,90,False
12/01/2013,10,False
Using
oPricePlan.Prices.Min(r => r.Rate)
I get get the Min value for the Rate or IndexOf[] can return the first instance. However, I'm wanting to return X number of lowest rates. For example how can I set the following;
For 1 Min rate (two rates might have the same Min) in the system, set it to 0 zero and the Free bool to true
For 2 Min rates (that might be the same), set it to 0 zero and the Free bool to true
So basically I'm wanting to find the lowest X number of rates, change the actual lowest rates found, and set the Free bool flag to true.
Should I look at using LINQ, or is their a preferred way ?
int numberOfItems = 1;
var orderedPrices = oPricePlan.Prices.OrderBy(x => x.Rate).ToList();
decimal targetRate = orderedPrices[numberOfItems - 1].Rate;
foreach (var price in orderedPrices.TakeWhile(x => x.Rate <= targetRate))
{
price.Rate = 0;
price.Free = true;
}
Edit: The above is based on selecting a targetRate based on numberOfItems, and then setting all items less than or equal to that to 0 (which might be numberOfItems or a little more items). Originally I had:
For your example input, this code will select one of the items with a rate of 10 (it'll be whichever happened to come first in oPricePlan.Prices since OrderBy is stable). That is, it is the number of items, not the number of distinct rates. I think that's what you're asking for; otherwise a solution like Tim Schmelter's is right.
int numberOfItems = 1;
foreach (var price in oPricePlan.Prices.OrderBy(x => x.Rate).Take(numberOfItems))
{
price.Rate = 0;
price.Free = true;
}
You could use OrderBy + GroupBy, Take and a loop:
var priceGroups = oPricePlan.Prices
.OrderBy(p => p.Rate) // order by rate ascending
.GroupBy(p => p.Rate) // group by rate
.First() // use the lowest price-rate group only
.Take(2); // change 2 to 1 if you only want to modify one price in this min-group
foreach (Price price in priceGroups)
{
price.Rate = 0;
price.Free = true;
}

method to total data in a list of a class

Twice this month, I've had to create a total a bunch of records of a class. (two different classes) and this will happen again. It seems to me that there should be an easy way to do this using reflection for any class without having to code a totaling routine for each class.
Consider:
private class ThisAndThat
{
public int This { get; set; }
public float That { get; set; }
public double TheOther { get; set; }
public string Whatever { get; set; }
}
As my code rumbles along, I create a bunch of these but I also need a totaling routine. Something like the AddToTotal() listed below,m where the numbers are added and the string is ignored.
List<ThisAndThat> _Discovered = new List<ThisAndThat>();
ThisAndThat _Total = new List<ThisAndThat>;
while( !Finished )
{
ThisAndThat CurrentOne = GetAnotherOne();
_Discovered.Add( CurrentOne );
AddToTotal( _Total, CurrentOne );
}
Obviously the numeric three properties in this sample class are easy to code, but I just did one with 60 numeric members. I fumbled around with reflection for a while but could not come up with a routine.
Reflection can absolutely do this. It's not too difficult. Here's an example using the class you provided:
var tat = new ThisAndThat();
tat.This = 1;
tat.That = 2.0F;
tat.TheOther = 3.0;
tat.Whatever = "Whatever";
var type = typeof(ThisAndThat);
var properties = type.GetProperties();
double total = 0.0;
foreach (System.Reflection.PropertyInfo pi in properties)
{
switch (pi.PropertyType.ToString())
{
case "System.Int32": //int
total += (int) pi.GetValue(tat, null);
break;
case "System.Double":
total += (double) pi.GetValue(tat, null);
break;
case "System.Single": //float
total += (float) pi.GetValue(tat, null);
break;
}
}
MessageBox.Show(total.ToString());
Note that my sample only works with Properties. If you have Fields that you need totaled, you'll have to use the GetFields method on the Type.
You should also be aware of handling other numeric types as well such as Int64, etc...
Is this what you're looking for?
ThisAndThat thisThatSum = 0;
foreach(ThisAndThat tat in _Discovered)
{
thisThatSum.This += tat.This;
// do the same for other fields
}
I'm sure a linq, way exists too, but I'd have to like do research and stuff to get that to you
Linq to sql makes this very easy for anything IEnumerable
ThisAndThat item1 = new ThisAndThat();
ThisAndThat item2 = new ThisAndThat();
item1.TheOther = 1.00;
item2.TheOther = 2.00;
_Discovered.Add(item1);
_Discovered.Add(item2);
var amount = from p in _Discovered
select p.TheOther;
Console.WriteLine("Amount total is {0}", amount.Sum());

How best to calculate derived currency rate conversions using C#/LINQ?

class FxRate {
string Base { get; set; }
string Target { get; set; }
double Rate { get; set; }
}
private IList<FxRate> rates = new List<FxRate> {
new FxRate {Base = "EUR", Target = "USD", Rate = 1.3668},
new FxRate {Base = "GBP", Target = "USD", Rate = 1.5039},
new FxRate {Base = "USD", Target = "CHF", Rate = 1.0694},
new FxRate {Base = "CHF", Target = "SEK", Rate = 8.12}
// ...
};
Given a large yet incomplete list of exchange rates where all currencies appear at least once (either as a target or base currency): What algorithm would I use to be able to derive rates for exchanges that aren't directly listed?
I'm looking for a general purpose algorithm of the form:
public double Rate(string baseCode, string targetCode, double currency)
{
return ...
}
In the example above a derived rate would be GBP->CHF or EUR->SEK (which would require using the conversions for EUR->USD, USD->CHF, CHF->SEK)
Whilst I know how to do the conversions by hand I'm looking for a tidy way (perhaps using LINQ) to perform these derived conversions perhaps involving multiple currency hops, what's the nicest way to go about this?
First construct a graph of all your currencies:
private Dictionary<string, List<string>> _graph
public void ConstructGraph()
{
if (_graph == null) {
_graph = new Dictionary<string, List<string>>();
foreach (var rate in rates) {
if (!_graph.ContainsKey(rate.Base))
_graph[rate.Base] = new List<string>();
if (!_graph.ContainsKey(rate.Target))
_graph[rate.Target] = new List<string>();
_graph[rate.Base].Add(rate.Target);
_graph[rate.Target].Add(rate.Base);
}
}
}
Now traverse that graph using recursion:
public double Rate(string baseCode, string targetCode)
{
if (_graph[baseCode].Contains(targetCode)) {
// found the target code
return GetKnownRate(baseCode, targetCode);
}
else {
foreach (var code in _graph[baseCode]) {
// determine if code can be converted to targetCode
double rate = Rate(code, targetCode);
if (rate != 0) // if it can than combine with returned rate
return rate * GetKnownRate(baseCode, code);
}
}
return 0; // baseCode cannot be converted to the targetCode
}
public double GetKnownRate(string baseCode, string targetCode)
{
var rate = rates.SingleOrDefault(fr => fr.Base == baseCode && fr.Target == targetCode);
var rate_i rates.SingleOrDefault(fr => fr.Base == targetCode && fr.Target == baseCode));
if (rate == null)
return 1 / rate_i.Rate
return rate.Rate;
}
Disclaimer: This is untested. Further, I'm sure this isn't the most performant approach to solve the problem (O(n) I think), but I believe it will work. There are a number of things you could add to improve the performance (e.g. saving every new combined rate calculation would eventually turn this into an effective O(1))
Wouldn't it be simpler to just have a list of all conversions to a single currency and then use that for any conversion? So something like (with USD as the base currency):
var conversionsToUSD = new Dictionary<string, decimal>();
public decimal Rate ( string baseCode, string targetCode )
{
if ( targetCode == "USD" )
return conversionsToUSD[baseCode];
if ( baseCode == "USD" )
return 1 / conversionsToUSD[targetCode];
return conversionsToUSD[baseCode] / conversionsToUSD[targetCode]
}
Now, this assumes that algebra is perfectly communicative. I.e., if I convert to EUR->USD->GBP I'll get the same as converting from EUR->GBP. That might not actually be the case in reality in which case, you would need every supported permutation.
Interesting problem!
First off, stay clear from double / floating point arithmetic. The .NET Decimal type should be quite sufficient and provide better precision! Such improved precision may be particularly important given the fact that the calculation of derived Fx rates requires a chain of multiple operations.
Another remark is that it is probably off-limits to introduce a simpler/shorter list of Exchange rates, whereby the Target is always the same [real or fictitious] currency. I'm assuming here that we should use the listed rate when available.
So figuring out derived rates should become a [simplified] network solution, whereby
Given a Base and Target currencies, we identify all the shortest pathes (from Base to Target), given the authoritative (non derived) rates in the list. (We can hope that the shortest path would be 2, in all cases, but this may not be the case given very esoteric currencies).
for each of these shortest paths (I think it would be ludicrous to also consider longer pathes), we perform the simple arithmetic conversion, and...
hopefully confirm that these derived rates are all within a nominal margin of conversion error and therefore take the average of these rates
raise some alert... or just make a lot of money by using making a circular path and raking in the differential ;-)
I have no idea what that "double currency" is for... i'll just ignore it.
Attempt: List<List<FxRate>> res = Rates("EUR", "CHF"); yields {EUR-USD, USD-CHF}.
Looks promising! :)
public class FxRate
{
public string Base { get; set; }
public string Target { get; set; }
public double Rate { get; set; }
}
private List<FxRate> rates = new List<FxRate>
{
new FxRate {Base = "EUR", Target = "USD", Rate = 1.3668},
new FxRate {Base = "GBP", Target = "USD", Rate = 1.5039},
new FxRate {Base = "USD", Target = "CHF", Rate = 1.0694},
new FxRate {Base = "CHF", Target = "SEK", Rate = 8.12}
// ...
};
public List<List<FxRate>> Rates(string baseCode, string targetCode)
{
return Rates(baseCode, targetCode, rates.ToArray());
}
public List<List<FxRate>> Rates(string baseCode, string targetCode, FxRate[] toSee)
{
List<List<FxRate>> results = new List<List<FxRate>>();
List<FxRate> possible = toSee.Where(r => r.Base == baseCode).ToList();
List<FxRate> hits = possible.Where(p => p.Target == targetCode).ToList();
if (hits.Count > 0)
{
possible.RemoveAll(hits.Contains);
results.AddRange(hits.Select(hit => new List<FxRate> { hit }));
}
FxRate[] newToSee = toSee.Where( item => !possible.Contains(item)).ToArray();
foreach (FxRate posRate in possible)
{
List<List<FxRate>> otherConversions = Rates(posRate.Target, targetCode, newToSee);
FxRate rate = posRate;
otherConversions.ForEach(result => result.Insert(0, rate));
results.AddRange(otherConversions);
}
return results;
}
Comments?
PS: you can get the cheaper convertion with double minConvertion = res.Min(r => r.Sum(convertion => convertion.Rate));
The most straight-forward algorithm would probably just be like Dijkstra's shortest path or something on a graph you generate using that list. Being that you don't know beforehand how long the path will be, this isn't really a problem that can be elegantly solved by a LINQ query. (Not that it's not possible, it's just probably not what you should pursue.)
On the other hand, if you know that there is a path from any currency to any other, and that there is only one possible conversion between any two currencies on the list (ie, if USD > EUR and USD > CHF exist, then EUR > CHF doesn't exist or you can ignore it), you can simply generate something like a doubly linked list and traverse. Again though, this isn't something that can be elegantly solved through LINQ.
Generate all of them and cache them. Given initial set this function will generate all existing pairs (inside same list) without graphs or recursion, by simple expanding initial list as it iterates.
public static void CrossRates(List<FxRate> rates)
{
for (int i = 0; i < rates.Count; i++)
{
FxRate rate = rates[i];
for (int j = i + 1; j < rates.Count; j++)
{
FxRate rate2 = rates[j];
FxRate cross = CanCross(rate, rate2);
if (cross != null)
if (rates.FirstOrDefault(r => r.Ccy1.Equals(cross.Ccy1) && r.Ccy2.Equals(cross.Ccy2)) == null)
rates.Add(cross);
}
}
}
This utility function will generate individual cross rate.
public static FxRate CanCross(FxRate r1, FxRate r2)
{
FxRate nr = null;
if (r1.Ccy1.Equals(r2.Ccy1) && r1.Ccy2.Equals(r2.Ccy2) ||
r1.Ccy1.Equals(r2.Ccy2) && r1.Ccy2.Equals(r2.Ccy1)
) return null; // Same with same.
if (r1.Ccy1.Equals(r2.Ccy1))
{ // a/b / a/c = c/b
nr = new FxRate()
{
Ccy1 = r2.Ccy2,
Ccy2 = r1.Ccy2,
Rate = r1.Rate / r2.Rate
};
}
else if (r1.Ccy1.Equals(r2.Ccy2))
{
// a/b * c/a = c/b
nr = new FxRate()
{
Ccy1 = r2.Ccy1,
Ccy2 = r1.Ccy2,
Rate = r2.Rate * r1.Rate
};
}
else if (r1.Ccy2.Equals(r2.Ccy2))
{
// a/c / b/c = a/b
nr = new FxRate()
{
Ccy1 = r1.Ccy1,
Ccy2 = r2.Ccy1,
Rate = r1.Rate / r2.Rate
};
}
else if (r1.Ccy2.Equals(r2.Ccy1))
{
// a/c * c/b = a/b
nr = new FxRate()
{
Ccy1 = r1.Ccy1,
Ccy2 = r2.Ccy2,
Rate = r1.Rate * r2.Rate
};
}
return nr;
}

Categories

Resources