How to perform this task?
int Amount, x=1000,y=200;
string BasedOn="x*12/100+y*5/100";
//In place of x and y in BasedOn I want to replace with x,y values like (1000*12%+200*5%)
//and the calculated result(130) should be assigned to Amount variable
For now, I split the BasedOn string
string[][] strings = BasedOn
.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries)
.Select(w => w.Split('*').ToArray())
.ToArray();
What to do next? Please help me.
I made your code more flexible
static void Main(string[] args)
{
Dictionary<string, object> variables = new Dictionary<string, object>();
variables.Add("x", 1000);
variables.Add("y", 200);
string equation = "x*12/100+y*5/100";
var result = Calculate(variables, equation);
}
static object Calculate(Dictionary<string, object> variables, string equation)
{
variables.ToList().ForEach(v => equation = equation.Replace(v.Key, v.Value.ToString()));
return new DataTable().Compute(equation, string.Empty);
}
You can take a look on the DataTable.Compute method.
It can be used like this on your case:
using System.Data;
DataTable dt = new DataTable();
var Amount = dt.Compute("1000*12%+200*5%","");
For replacing "x" and "y" with numeric values you can use string.Replace
Take a look NCalc (http://ncalc.codeplex.com/)
Expression e = new Expression("2 + 3 * 5");
Debug.Assert(17 == e.Evaluate());
If you want to replace the string AND calculate the result, you can do:
int Amount, x=1000,y=200;
string BasedOn=$"{x}*12/100+{y}*5/100";
DataTable dt = new DataTable();
var v = dt.Compute(BasedOn,"");
v will be your result (130).
EDIT: You have to replace your %'s with division by 100, as the datatable thinks its a mod operator.
With DataColumn.Expression:
int Amount, x = 1000, y = 200; string BasedOn = "x*12%+y*5%";
var dt = new DataTable();
dt.Columns.Add("x", typeof(int)); // in Visual Studio 2015 you can use nameof(x) instead of "x"
dt.Columns.Add("y", typeof(int));
dt.Columns.Add("Amount", typeof(int), BasedOn.Replace("%", "/100")); // "x*12/100+y*5/100" ("%" is considered modulus operator)
Amount = (int)dt.Rows.Add(x, y)["Amount"]; // 130
With RegEx.Replace and DataTable.Compute:
string expression = Regex.Replace(BasedOn.Replace("%", "/100"), "[a-zA-Z]+",
m => (m.Value == "x") ? x + "" : (m.Value == "y") ? y + "" : m.Value); // "1000*12/100+200*5/100"
Amount = (int)(double)new DataTable().Compute(expression, ""); // 130.0 (double)
Related
I have a datatable that was imported from a CSV file. There are a multitude of different tables that can be imported.
The datatables have four columns (type=double): LookupColumn L M S
LookupColumn will usually have a unique name (e.g., Length, Height, Weight). The other column names remain the same. This is immaterial as you can just use dt.Column[0] mostly. The lookup column will always be the first column imported.
I need to search the datatable on LookupColumn for a LookupValue passed from the app (from a textbox).
If a LookupValue matches exactly a number in LookupColumn, then, return the values for L, M, S.
If there is no match, then I need to find the rows on either side of where the LookupValue would lie and return the min/max values for each variable in L,M,S.
Once I have those, I can interpolate the values for L, M, S.
For example:
Col_0
L
M
S
45.0
-0.3521
2.441
0.09182
45.5
-0.3521
2.524
0.09153
46.0
-0.3521
2.608
0.09124
46.5
-0.3521
2.691
0.09094
47.0
-0.3521
2.776
0.09065
47.5
-0.3521
2.861
0.09036
48.0
-0.3521
2.948
0.09007
If my LookupValue in Col[0] = 46.5, the program would return L=-0.3521 M=2.691 S=0.09094
These values will be put in textboxes on the form the viewer sees.
If there was no match (assuming LookupValue was within the range LookupColumn min/max) then I need to return the rows on both sides of where the value would lie if it were present--that is, Lmin Lmax, Mmin Mmax, Smin Smax and use those in the following formula to get the interpolated value (IntVal) for LookupColumn (Col_0).
For example, if the LookupValue in (Col_0) = 46.8, the returned results (array?, list?) would be the rows where Col_0 = 46.5 and 47.0:
Col_0
L Values
M Values
S Values
LookupMin = 46.5
Lmin = -0.3521
Mmin = 2.691
Smin = 0.09094
LookupMax = 47.0
Lmax = -0.3521
Mmax = 2.776
Smax = 0.09065
Interpolated Value = LMSmin + (46.8 - LookupMin) * (LMSmax - LMSmin / LookupMax - LookupMin)
Interpolated L = -0.3521 because Lmin = Lmax
Interpolated M = 2.691 + (46.8 - 46.5) * (2.776 - 2.691 / 47.0 - 46.5)
Interpolated M = 2.7418
Interpolated S = 0.09094 + (46.8 - 46.5) * (0.09065 - 0.09094 / 47.0 - 46.5)
Interpolated S = 0.09088
So, given the Min/Max values for Col_0 and either L, M, or S min/max values, I can interpolate any value the user provides that isn't in the lookup even if the LookupValue has more decimals. The interpolated L,M,S values will be put in textboxes for the user.
I have a bit of code that works when there's a match, but, I think there's a better/more concise way either using Linq or Tuples. I realize this isn't the best code and I'm open to suggestions.
I have scoured StackOverflow and found several posts on interpolation and Lookup Tables. It seems that the best practice for the lookup is to use a tuple, but, I'm not very clear on on their use.
For the most part, this question is focused on returning the Min/Max values of the lookup when there's no match. Once I have those, I don't think the interpolation is a big feat as I know the formula. Also, I know that the user could enter values out of range--I will account for those issues later.
Any help is appreciated.
private void tbLookup_Leave(object sender, System.EventArgs e)
{
string colName = tmpDT.Columns[0].ColumnName;
string colSearch = colName + " = '" + tbLookup.Text + "'";
if (tbLookup.Text.Length > 0)
{
// Exact match
while (true)
{
DataRow[] foundRow = tmpDT.Select(colSearch);
if (foundRow.Length == 0)
{
break;
}
foreach (DataRow row in foundRow)
{
string L = row.Field<string>("L");
string M = row.Field<string>("M");
string S = row.Field<string>("S");
tbLkupL.Text = L;
tbLkupM.Text = M;
tbLkupS.Text = S;
}
// No match
// Call interpolation method
}
}
else
{
MessageBox.Show("Please enter a lookup value", "Missing Data");
}
You inquired about possibly using LINQ, so I checked my code chest and found something similar, that I adapted to your needs.
using System.Linq; // Add this at the top of the Program.cs file.
The extension method returns three output parameters, that contain found indices, or -1 if not found.
// An extension methods class must be the first class in a file.
// Add this class inside the namespace of a console app, before the Program class (in the Program.cs file).
public static class ExtensionMethods
{
public static bool GetNearestOrEqual<TSource, TValue>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TValue> valueSelector, TValue referenceValue, out int indexOfLowerMax, out int indexOfEqual, out int indexOfHigherMin)
where TValue : struct, System.IComparable<TValue>
{
using var e = source.GetEnumerator();
var ltCurrent = new TValue?();
var gtCurrent = new TValue?();
indexOfLowerMax = -1;
indexOfEqual = -1;
indexOfHigherMin = -1;
var index = 0;
while (e.MoveNext())
{
var currentValue = valueSelector(e.Current);
switch (currentValue.CompareTo(referenceValue))
{
case int lo when lo < 0:
if (!ltCurrent.HasValue || currentValue.CompareTo(ltCurrent.Value) > 0)
{
indexOfLowerMax = index;
ltCurrent = currentValue;
}
break;
case int hi when hi > 0:
if (!gtCurrent.HasValue || currentValue.CompareTo(gtCurrent.Value) < 0)
{
indexOfHigherMin = index;
gtCurrent = currentValue;
}
break;
default:
indexOfEqual = index;
break;
}
index++;
}
return indexOfLowerMax != -1 || indexOfEqual != -1 || indexOfHigherMin != -1;
}
}
Sample of how you could use it (created a simple console app):
// Replace the Main() inside the Program class of a console app.
static void Main(string[] args)
{
var dt = new System.Data.DataTable();
dt.Columns.Add("Col_0", typeof(double));
dt.Columns.Add("L", typeof(double));
dt.Columns.Add("M", typeof(double));
dt.Columns.Add("S", typeof(double));
dt.Rows.Add(new object[] { 45.0, -0.3521, 2.441, 0.09182 });
dt.Rows.Add(new object[] { 45.5, -0.3521, 2.524, 0.09153 });
dt.Rows.Add(new object[] { 46.0, -0.3521, 2.608, 0.09124 });
dt.Rows.Add(new object[] { 46.5, -0.3521, 2.691, 0.09094 });
dt.Rows.Add(new object[] { 47.0, -0.3521, 2.776, 0.09065 });
dt.Rows.Add(new object[] { 47.5, -0.3521, 2.861, 0.09036 });
dt.Rows.Add(new object[] { 48.0, -0.3521, 2.948, 0.09007 });
var lookupValue = 46.8;
var foundAnything = dt.Rows.Cast<System.Data.DataRow>().GetNearestOrEqual(o => (double)o.ItemArray[0], lookupValue, out var indexOfLowerMax, out var indexOfEqual, out var indexOfHigherMin);
// Assuming example for when both low and high are found...
var dr = dt.NewRow();
var lookuploDiff = lookupValue - (double)dt.Rows[indexOfLowerMax][0];
var hiloDiff = (double)dt.Rows[indexOfHigherMin][0] - (double)dt.Rows[indexOfLowerMax][0];
dr.ItemArray = new object[] {
lookupValue,
(double)dt.Rows[indexOfLowerMax][1] + lookuploDiff * (((double)dt.Rows[indexOfHigherMin][1] - (double)dt.Rows[indexOfLowerMax][1]) / hiloDiff),
(double)dt.Rows[indexOfLowerMax][2] + lookuploDiff * (((double)dt.Rows[indexOfHigherMin][2] - (double)dt.Rows[indexOfLowerMax][2]) / hiloDiff),
(double)dt.Rows[indexOfLowerMax][3] + lookuploDiff * (((double)dt.Rows[indexOfHigherMin][3] - (double)dt.Rows[indexOfLowerMax][3]) / hiloDiff),
};
dt.Rows.InsertAt(dr, indexOfHigherMin);
}
As always, if there are any questions, this is the place. :)
I need to process an invoice which is given to me as a single Excel column of strings, which I have converted to a List<string>. A simplified sample looks like this.
This is a nothing line
No001 FOO67 368.80
No001 FOO67 17.68
SHORT 12345
In this example, I might need to extract data from each line that begins with "No" - the Reference (e.g. FOO67) and amount (e.g. 368.80). However, if I encounter a line that starts with "SHORT", that means that the previous line amount was really an adjustment and the reference should be whatever I find on the "SHORT" line, with the sign of the amount reversed. In the case above, the data I hope to extract would be as follows (the first line is column headings):
Reference Amount
FOO67 368.80
12345 -17.68
I cannot find any way to achieve this using a linq query against the list. A mock-up of what I think a solution might look like is this (the below will not parse as I have added "nextline" as a pseudocode addition):
var inv = new List<string> { "This is a nothing line", "No001 FOO67 368.80", "No001 FOO67 17.68", "SHORT 123456" };
var myTable = new DataTable();
myTable.Columns.Add("Id", typeof(int));
myTable.Columns.Add("Ref", typeof(string));
myTable.Columns.Add("Amount", typeof(double));
var foo = from l in inv
where (l.Substring(0, 2) == "No" && double.TryParse(l.Split(' ')[2], out i))
select myTable.LoadDataRow(new object[]
{ inv.IndexOf(l)
,nextline.Contains("SHORT")? nextline.Split(' ')[1] : l.Split(' ')[1]
,nextline.Contains("SHORT")? -1:1 * double.Parse(l.Split(' ')[2].Replace(",", "").Replace("-", ""))
}, LoadOption.OverwriteChanges);
Is there a known way to get the value from the next line of a query and use it to decide which rows to return? If so, what is the way to do it?
You could use something like following.
var inv = new List<string>
{
"This is a nothing line",
"No001 FOO67 368.80",
"No001 FOO67 17.68",
"SHORT 123456"
};
var filterValidLines = inv.ToLookup(c=>c.StartsWith("No")
||c.StartsWith("SHORT"));
var result = filterValidLines[true].Zip(
filterValidLines[true].Skip(1),
(x,y)=>
{
var subData= x.Split(new []{" "},StringSplitOptions.RemoveEmptyEntries);
var multiplier = y.StartsWith("SHORT")? -1:1;
return new Data{Reference= subData[1], Amount = double.Parse(subData[2]) * multiplier};
});
Output
Reference Amount
FOO67 368.8
FOO67 -17.68
A quick and dirty solution:
void Main()
{
var list = new List<string>
{
"This is a nothing line",
"No001 FOO67 368.80",
"No001 FOO67 17.68",
"SHORT 12345"
};
var invoices = list
.Select((l, i) => new InvoiceData
{
Line = l,
NextLine = i < list.Count - 1 ? list[i + 1] : string.Empty
})
.Where(x => x.Line.StartsWith("No"))
.ToList();
}
public class InvoiceData
{
public string Line { get; set; }
public string NextLine { get; set; }
public bool IsAdjustment => NextLine.StartsWith("SHORT");
public decimal Amount =>
IsAdjustment
? -decimal.Parse(Line.Split(' ')[2])
: decimal.Parse(Line.Split(' ')[2]);
public string Reference =>
IsAdjustment
? NextLine.Split(' ')[1]
: Line.Split(' ')[1];
}
I have 3 string ---
m60_CLDdet2_LOSS2CLF_060520469434_R0RKE_52_GU
m60_CLDdet2_LOSS2CLF_060520469434_R10KE_52_TCRER
m60_CLDdet2_LOSS2CLF_060520469434_R0HKE_52_NT
and I want R0RKE_52_GU, R10KE_52_TCRER,R0HKE_52_NT.
Note: m60_CLDdet2_LOSS2CLF_060520469434 is varying so I want to find substring if R0RKE or R10KE or R0HKE exists
I would suggest using a Regular expression for this, it is much more versatile for pattern matching.
var matches = System.Text.RegularExpressions.Regex.Matches(text, #"(R0RKE|R10KE|R0HKE).*");
I want to find substring if R0RKE or R10KE or R0HKE exists
This LINQ query returns the desired result:
var strings=new[]{"m60_CLDdet2_LOSS2CLF_060520469434_R0RKE_52_GU","m60_CLDdet2_LOSS2CLF_060520469434_R10KE_52_TCRER","m60_CLDdet2_LOSS2CLF_060520469434_R0HKE_52_NT"};
string[] starts = { "R0RKE", "R10KE", "R0HKE" };
var result = strings
.Select(str => new { str, match = starts.FirstOrDefault(s => str.IndexOf("_" + s) >= 0)})
.Where(x => x.match != null)
.Select(x => x.str.Substring(x.str.IndexOf(x.match)));
Console.Write(String.Join(",", result)); // R0RKE_52_GU,R10KE_52_TCRER,R0HKE_52_NT
I write it into static method:
private static string TakeIt(string inputString)
{
if (!Regex.IsMatch(inputString, "(R0RKE|R10KE|R0HKE)"))
{
return string.Empty;
}
var regex = new Regex(#"_");
var occurances = regex.Matches(inputString);
var index = occurances[3].Index + 1;
return inputString.Substring(index, inputString.Length - index);
}
void Main()
{
var string1 = "m60_CLDdet2_LOSS2CLF_060520469434_R0RKE_52_GU";
var string2 = "m60_CLDdet2_LOSS2CLF_060520469434_R10KE_52_TCRER";
var string3 = "m60_CLDdet2_LOSS2CLF_060520469434_R0HKE_52_NT";
var string4 = "m60_CLDdet2_LOSS2CLF_060520469434_hhhhh";
Console.WriteLine(TakeIt(string1));
Console.WriteLine(TakeIt(string2));
Console.WriteLine(TakeIt(string3));
Console.WriteLine(TakeIt(string4));
}
Hope this help.
Update: added .Any - it simplifies the code and it's just as same efficient.
If you just need to check for three strings inside string array you can do :
static string[] GetStrings(string[] dirty, string[] lookUpValues)
{
List<string> result = new List<string>();
for (int i = 0; i < dirty.Length; i++) if (lookUpValues.Any(dirty[i].Contains)) result.Add(dirty[i]);
return result.ToArray();
}
Usage: string[] result = GetStrings(dirty, new[] {"R0RKE", "R10KE", "R0HKE"});
Also you can use LINQ query and Regex.Matches as others advised.
Trying for wrap each string in array but it doesn't works, means foreach loop, please explain why
string s = "keepsakes,table runners,outdoor accessories";
List<string> keys = s.Split(',').ToList();
keys.ForEach(x => x = String.Concat("%", x, "%"));
s = String.Join(",", keys);
Console.WriteLine(s);
need to get "%keepsakes%,%table runners%,%outdoor accessories%"
UPD:
Thanks a lot for suggestions(it's a same way)
but some one can answer why this is works and not works under:
object
public class MyItems
{
public string Item { get; set; }
}
and func
string s = "keepsakes,table runners,outdoor accessories";
List<MyItems> keys = s.Split(',').ToList().Select(x => new MyItems(){ Item = x }).ToList();
keys.ForEach(x => x.Item = String.Concat("%", x.Item, "%"));
s = String.Join(",", keys.Select(x => x.Item).ToList());
Console.WriteLine(s);
You are not modifying the list within the ForEach, you are just creating strings that are assigned to the local variable x but then thrown away. You could use a for-loop:
for(int i = 0; i < keys.Count; i++)
{
keys[i] = String.Concat("%", keys[i], "%");
}
For what it's worth, here the shorter LINQ version which also circumvents the core issue:
s = string.Join(",", s.Split(',').Select(str => "%" + str + "%"));
You can use Join and Select and Format
string s = "keepsakes,table runners,outdoor accessories";
var output = string.Join(",", s.Split(',').Select(x => string.Format("%{0}%", x)));
you can do easier: replace each comma with %,%
string s = "keepsakes,table runners,outdoor accessories";
string s2 = "%" + s.Replace("," , "%,%") + "%";
Another approach could be using Regex instead of LINQ:
string s = "keepsakes,table runners,outdoor accessories";
string pattern = "\\,+";
string replacement = "%,";
Regex rgx = new Regex(pattern);
string result = string.Format("%{0}%", rgx.Replace(s, replacement));
Edit:
The reason why it works using a class to assign the string it is because when you use the foreach in your first instance:
keys.ForEach(x => x = String.Concat("%", x, "%"));
x, elements of keys, which is a string, is a reference passed by value to the function ForEach. Take this as an example:
var myString = "I'm a string";
Console.WriteLine(myString);
ChangeValue(myString);
Console.WriteLine(myString);
void ChangeValue(string s)
{
s = "something else";
}
If you run that snippet you'll see that myString won't be changed inside the method ChangeValue because we are trying to replace the reference. The same thing happens for the method ForEach, this is the main reason you cannot change the value of your list within the ForEach.
Instead if you do:
class MyClass{
public string aString;
}
void ChangeValue(MyClass s)
{
s.aString = "something else";
}
var myClass = new MyClass();
myClass.aString = "I'm a string";
Console.WriteLine(myClass.aString);
ChangeValue(myClass);
Console.WriteLine(myClass.aString);
You acknowledge that in the second Console.WriteLine the value of the field aString will be changed to "something else". Here is a good explanation of how reference types are passed by value
Just another approach (without lambdas):
string result = string.Concat("%", s, "%").Replace(",", "%,%");
List<>.ForEach cannot be used to change the contents of the list. You can either create a new list or use a for loop.
keys = keys.Select(x => "%" + x + "%").ToList();
or
for(int i = 0; i < keys.Count; i++)
{
keys[i] = "%" + keys[i] + "%";
}
As others have noted, the lambda passed to List.ForEach does not return a value.
LINQ is lazy, but String.Join will force enumeration:
var res = String.Join(",", input.Split(',').Select(s => "%" + s + "%"));
The ForEach doesn't change your list of string, it will only perform an action using each string. Instead you can do it this way :
string s = "keepsakes,table runners,outdoor accessories";
List<string> keys = s.Split(',').Select(x => String.Concat("%", x, "%")).ToList();
s = String.Join(",", keys);
Console.WriteLine(s);
For example I have a string "99,999 ABC XYZ"
Now I want to convert to integer "99999"
I have made a code that worked:
var regex = new Regex("[A-z]");
linksOnPage = regex.Replace(linksOnPage, "");
regex = new Regex(" ");
linksOnPage = regex.Replace(linksOnPage, "");
int noPage = int.Parse(regex.Replace(linksOnPage, ""),
NumberStyles.AllowThousands);
But I feel it's not good enough, can anyone help me to make it shorter?
This Regex will remove all the letters and spaces:
var regex = new Regex(" |[A-z]");
linksOnPage = regex.Replace(linksOnPage, "");
You could use int.Parse and add the NumberStyles.AllowThousands flag:
int num = int.Parse(linksOnPage , NumberStyles.AllowThousands);
Or int.TryParse letting you know if the operation succeeded:
int num;
if (int.TryParse(linksOnPage , NumberStyles.AllowThousands,
CultureInfo.InvariantCulture, out num))
{
// parse successful, use 'num'
}
Or you can also try this:
int num = int.Parse(linksOnPage.Replace(",", ""));
This would be my approach, it's not really shorter though:
public static void Main(string[] args)
{
string input = "99,999 ABC XYZ";
var chars = input.ToCharArray().ToList();
var builder = new StringBuilder();
foreach (var character in chars)
{
if (char.IsNumber(character))
builder.Append(character);
}
int result = 0;
int.TryParse(builder.ToString(), out result);
Console.WriteLine(result);
Console.ReadKey();
}
you could do something like this:
int? ParseIntFromString( string s )
{
Match m = rxInt.Match(s) ;
int? value = m.Success ? int.Parse( m.Value , NumberStyles.AllowLeadingSign|NumberStyles.AllowThousands ) : (int?)null ;
return value ;
}
static Regex rxInt = new Regex( #"^-?\d{1,3}(,\d\d\d)*");
the return value is null if no integer value was found and the parsed value if it was.
Note that the match is anchored at start-of-string via ^; if you want to match an number anywhere in the string, simply remove it.
You could also kick it old-school and do something like this:
public int? ParseIntFromString( string s )
{
bool found = false ;
int acc = 0 ;
foreach( char c in s.Where( x => char.IsDigit )
{
found = true ;
acc += c - '0' ;
}
return found ? (int?)acc : (int?) null ;
}