I have a dictionary:
<string,List<string>>
The key is the product code say "product1" then the list is a list of properties:
"Brand","10.40","64","red","S"
Then I 'can' have a list of rules/filters e.g.
var tmpFilter = new customfilters();
tmpFilter.Field = "2";
tmpFilter.Expression = ">";
tmpFilter.Filter = "10";
So for the above example this would pass because at index 2 (tmpFilter.Field) it is more than 10; then I have another object which defines which fields within the list I want to write to file. For that dictionary item I just want to write the product brand and price where the filters match.
At the moment without the filter I have:
var tmp = new custom();
tmp.Columns = "0,1";
tmp.Delimiter = ",";
tmp.Extention = ".csv";
tmp.CustomFilters = new List<customfilters>() {new customfilters(){ Field = "2", Expression = ">", Filter = "10"} };
public static void Custom(custom custom)
{
foreach (var x in Settings.Prods)
{
//Get Current Product Code
var curprod = Settings.ProductInformation[x];// the dictionary value
foreach (var column in custom.Columns)
{
var curVal = curprod[Convert.ToInt32(column)];
tsw.Write(curVal + custom.Delimiter);
}
Settings.Lines++;
tsw.WriteLine();
}
tsw.Close();
}
I only want to write the curprod if all the filters pass for that list of strings.
How I can do this?
There's a really nice Nuget package based on an example published by Microsoft, that they have decided to make really hard to find for some reason, that allows dynamic linq queries:
https://www.nuget.org/packages/System.Linq.Dynamic/1.0.2
Source:
https://github.com/kahanu/System.Linq.Dynamic
Using that you can do stuff like this very easily (note: I used strings here because the OP states they have a List<string>):
List<string> stuff = new List<string> { "10.40", "64", "5", "56", "99", "2" };
var selected = stuff.Select(s => new { d = double.Parse(s) }).Where("d > 10");
Console.WriteLine(string.Join(", ", selected.Select(s => s.d.ToString()).ToArray()));
Outputs:
10.4, 64, 56, 99
That may give you a place to start. One thing you are going to have to tackle is identifying which of your fields are numeric and should be converted to a numeric type before trying to apply your filter. Otherwise you are going to comparing as strings.
Related
I'm trying to use Roslyn to execute C# code that is defined by the user at runtime, similar to this example:
public class Globals
{
public int X;
public int Y;
}
var globals = new Globals { X = 1, Y = 2 };
Console.WriteLine(await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals));
Example copied from here
My problem is that the variable names used in the script are unknown at compile time. In other words, I don't know what member-names I should use for my globals class and how many members (script-parameters) there will be.
I tried to use ExpandoObject to solve the problem but couldn't get it to work. Is ExpandoObject supposed to work in this context? Are there other ways to solve the problem?
Update
For my use case, the best solution is probably to use System.Linq.Dynamic:
//Expression typed in by the user at runtime
string Exp = #"A + B > C";
//The number of variables and their names are given elsewhere,
//so the parameter-array doesn't need to be hardcoded like in this example.
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[]
{
Expression.Parameter(typeof(double), "A"),
Expression.Parameter(typeof(double), "B"),
Expression.Parameter(typeof(double), "C")
},
null, Exp);
var Lambda = e.Compile();
//Fake some updates...
foreach (var i in Enumerable.Range(0,10))
{
Console.WriteLine(Lambda.DynamicInvoke(i, 3, 10));
}
If you can retrieve at runtime all member names, their count and their values that were passed from input you can generate execution code at runtime and evaluate it. As a simple example of execution code you can generate variable declarations for all input values and then sum all of them:
// here you should put retrieved member names and their values. Just for example, currently here exist a couple of args
var variablesAndValues = new Dictionary<string, object> { ["arg_1"] = 5, ["arg_2"] = 6, ["arg_3"] = 7 };
// and you should create an execution code looks like this:
// var arg_1 = value_1;
// ..
// var arg_n = value_n;
// arg_1 + .. + arg_n
StringBuilder builder = new StringBuilder();
foreach (var item in variablesAndValues)
{
builder.Append("var ").Append(item.Key).Append(" = ").Append(item.Value).AppendLine(";");
}
var variablesCount = variablesAndValues.Count;
foreach (var item in variablesAndValues.Keys)
{
builder.Append(item);
if (--variablesCount > 0)
{
builder.Append(" + ");
}
}
var scriptState = CSharpScript.RunAsync(builder.ToString()).Result;
var result = scriptState.ReturnValue;
Be careful, this example assumes that the all value types has sum_operation and them are known by default script options, else you will receive compile error when try to execute the code.
Upd.
If your cases are performance critically you may create a script that will sum all input arguments and then run this script repeatedly when you need it.
public class Globals
{
public int[] Args;
}
...
// create script that sum all input arguments
var script = CSharpScript.Create(#"var result = 0;
foreach (var item in Args)
{
result += item;
}
return result; ", globalsType: typeof(Globals));
// run it at twice on the user values that were received before
// also you can reuse an array, but anyway
var res1 = script.RunAsync(new Globals { Args = new[] { 5, 6, 7 } }).Result.ReturnValue;
var res2 = script.RunAsync(new Globals { Args = new[] { 7, 8, 9, 10} }).Result.ReturnValue;
This approach ignore in the script code the input variable names from an user, and seems that it doesn't matter in your cases.
I am trying to order a list of lists by one of the fields.
I have two lists in one grouped list. The first contains the Id, the second contains the count. I was successfully able to group the list of lists by Id and reformat it.
for (int i = 0; i < GroupedListofLists.Count; i++)
{
tempo_Id.Add(GroupedListofLists[i][0]);
tempo_count.Add(GroupedListofLists[i][1]);
}
GroupedListofLists.Clear();
GroupedListofLists.Add(tempo_Id);
GroupedListofLists.Add(tempo_count);
If I print out the GroupedListofLists I will have distinct Ids (GroupedListofLists[0]) each with their count in the second(GroupedListofLists [1]).
Now when I try to sort this list with lambda expression I have a problem. I tried these two methods:
GroupedlistofLists.Sort( (a, b) => Convert.ToDouble(a[idx]).CompareTo(Convert.ToDouble(b[idx])));
AND
GroupedlistofLists = GroupedlistofLists.OrderBy(x => Convert.ToDouble(x[idx])).ToList();
A problem arises.
In the first method no matter what values I used for the variable 'idx', a will be assigned the values of GroupedListofLists[0] and b the values of GroupedListofLists[1].
In the second, no matter what values I use for the variable 'idx' (0 or 1), x will always contain the values for GroupedListofLists[0]. Which are the Id value and I need to sort them by Count, so GroupedListofLists[1].
I hope I was clear.
Thank you in advance.
I don't exacly know what you're trying to do. But i'll do a guess:
// Build some List you want to sort
List<string> myList = new List<string>() { "3", "1", "2" };
// Sort the List
myList = myList.OrderBy(a => Convert.ToDouble(a));
The Lambda-Expression in OrderBy needs to select a value which will be ordered in default manner. The Lambda-Expression will be run for every item in your list. The results will be used as order-key. you don't need any index.
Sort can be used so sort complex types.
Better version should work on a List with correct class and type. A Convert within a lambda-expression should be avoided!
// Some example-class with multiple properties
public class MyItem
{
public string Name { get; set; }
public double Value { get; set; }
}
// Some test-data
List<MyItem> myList = new List<MyItem>()
{
new MyItem() { Name = "One", Value = 1 },
new MyItem() { Name = "Three", Value = 3 },
new MyItem() { Name = "Two", Value = 2 }
}
// select the value you want to use for ordering the list.
myList = myList.OrderBy(item => item.Value);
// Expected output: 1, 2, 3
foreach(MyItem item in myList)
{
Console.WriteLine(item.Value + ", " + item.Name);
}
// Expected output: 3, 2, 1
myList = myList.OrderBy(item => item.Name);
foreach(MyItem item in myList)
{
Console.WriteLine(item.Value + ", " + item.Name);
}
(I got no Visual Studio installed, so check for typos ;))
So I have
List<string[]> listy = new List<string[]>();
listy.add('a','1','blue');
listy.add('b','2','yellow');
And i want to search through all of the list ti find the index where the array containing 'yellow' is, and return the first element value, in this case 'b'.
Is there a way to do this with built in functions or am i going to need to write my own search here?
Relatively new to c# and not aware of good practice or all the built in functions. Lists and arrays im ok with but lists of arrays baffles me somewhat.
Thanks in advance.
As others have already suggested, the easiest way to do this involves a very powerful C# feature called LINQ ("Language INtegrated Queries). It gives you a SQL-like syntax for querying collections of objects (or databases, or XML documents, or JSON documents).
To make LINQ work, you will need to add this at the top of your source code file:
using System.Linq;
Then you can write:
IEnumerable<string> yellowThings =
from stringArray in listy
where stringArray.Contains("yellow")
select stringArray[0];
Or equivalently:
IEnumerable<string> yellowThings =
listy.Where(strings => strings.Contains("yellow"))
.Select(strings => strings[0]);
At this point, yellowThings is an object containing a description of the query that you want to run. You can write other LINQ queries on top of it if you want, and it won't actually perform the search until you ask to see the results.
You now have several options...
Loop over the yellow things:
foreach(string thing in yellowThings)
{
// do something with thing...
}
(Don't do this more than once, otherwise the query will be evaluated repeatedly.)
Get a list or array :
List<string> listOfYellowThings = yellowThings.ToList();
string[] arrayOfYellowThings = yellowThings.ToArray();
If you expect to have exactly one yellow thing:
string result = yellowThings.Single();
// Will throw an exception if the number of matches is zero or greater than 1
If you expect to have either zero or one yellow things:
string result = yellowThings.SingleOrDefault();
// result will be null if there are no matches.
// An exception will be thrown if there is more than one match.
If you expect to have one or more yellow things, but only want the first one:
string result = yellowThings.First();
// Will throw an exception if there are no yellow things
If you expect to have zero or more yellow things, but only want the first one if it exists:
string result = yellowThings.FirstOrDefault();
// result will be null if there are no yellow things.
Based on the problem explanation provided by you following is the solution I can suggest.
List<string[]> listy = new List<string[]>();
listy.Add(new string[] { "a", "1", "blue"});
listy.Add(new string[] { "b", "2", "yellow"});
var target = listy.FirstOrDefault(item => item.Contains("yellow"));
if (target != null)
{
Console.WriteLine(target[0]);
}
This should solve your issue. Let me know if I am missing any use case here.
You might consider changing the data structure,
Have a class for your data as follows,
public class Myclas
{
public string name { get; set; }
public int id { get; set; }
public string color { get; set; }
}
And then,
static void Main(string[] args)
{
List<Myclas> listy = new List<Myclas>();
listy.Add(new Myclas { name = "a", id = 1, color = "blue" });
listy.Add(new Myclas { name = "b", id = 1, color = "yellow" });
var result = listy.FirstOrDefault(t => t.color == "yellow");
}
Your current situation is
List<string[]> listy = new List<string[]>();
listy.Add(new string[]{"a","1","blue"});
listy.Add(new string[]{"b","2","yellow"});
Now there are Linq methods, so this is what you're trying to do
var result = listy.FirstOrDefault(x => x.Contains("yellow"))?[0];
Sorry for the incoherent title. I don't know how to concisely explain my problem, which is why I didn't really know how to look it up. I'll explain using an example...
Let's say I have a class:
public class cas
{
public string name { get; set; }
public int num { get; set; }
}
With that class, I make several objects and stick them into a list. For the sake of example, I will make 4:
var list = new List<cas>
{
new cas { name = "firstname", num = 1 },
new cas { name = "firstname", num = 2 },
new cas { name = "lastname", num = 3 },
new cas { name = "lastname", num = 4 }
};
Is there a way to take this List and combine any objects with the same name field?
So, the new list would be 1 object with:
name = "firstname", num = 3,
name = "lastname", num = 7
There's the obvious "long" way to do it, but it would be clunky and expensive (go through the list several times to find like-objects). I was wondering if I was missing any clean way of doing it. I intentionally made a simple example so that the answer would be a proof of concept rather than writing my code for me. My actual problem is more complex than this, but I can't figure out this one aspect of it.
Using Linq, you have a GroupBy Method and a Select Method:
list = list.GroupBy(x=> x.name)
.Select(x=> new cas() { name = x.Key, num = x.Sum(y=> y.num) }).ToList();
Or using Elegant query-syntax:
list = (from item in list
group item by item.name into grouping
select new cas()
{
name = grouping.Key,
num = grouping.Sum(x => x.num)
}).ToList();
Note that to use these methods, you have to add using System.Linq at the top of your source file.
You can use linq, you would have to group them on name property and then sum on the num property of each group like:
var result = list.GroupBy(x=>x.name)
.Select(g=> new cas
{
name = g.Key,
num = g.Sum(x=>x.num)
});
Let's say I have the following data (in pseudo-code for readability):
var myVariations = [
{ Name = "Color", Values = ["Red", "Yellow", "Green" /*, etc. */] },
{ Name = "Size", Values = ["S", "M", "L" /*, etc. */] },
{ Name = "Length", Values = ["34", "35", "36" /*, etc. */] },
/* and so on...(up to 5 total) */
];
And I can get that data with LINQ like so:
var myVariations = myProduct.Variations.ToList();
How can I go about mapping those variations into a structure like this (for the eBay Trading API):
var ebayVariations = [
{
Name = "Red-S-34",
Value = [
// yes, these are arrays with only one item
{ Name = "Color", Values = [{Value = "Red"}] },
{ Name = "Size", Values = [{Value = "S"}] },
{ Name = "Length", Values = [{Value = "34" }] }
]
},
/* etc for all possible combinations */
];
Obviously the fact that the Values array holds only one value is a bit strange; but with eBay's Trading API if I list multiple values in a single Variation (which is easy to do compared to this recursive stuff) it complains. So alternatively, if you are familiar with the eBay Trading API, how can I get this to work in an "optimal" fashion, in-line with the way eBay intended Variations to be listed (called via AddFixedPricedItem, if you care).
I don't know anything about the eBay Trading API, but here's an article on computing a Cartesian Product with LINQ (the very last step drops the recursion in favor of aggregation).
I've changed terminology insignificantly, but wrote clarifying comments.
public IEnumerable<Combination> GetCombinations(Variation[] variations, int variationIndex, IEnumerable<VariationPosition> aggregatedPositions)
{
// We should choose one position from every variation,
// so we couldn't produce combination till we reach end of array.
if (variationIndex < variations.Length)
{
// Pick current variation.
var currentVariation = variations[variationIndex];
// Every variation has list of possible positions (Color could be Green, Redm, Blue, etc.).
// So we should walk through all the positions
foreach (var val in currentVariation.Positions)
{
// Current position. Variation's name will be used during creating result Combination.
var position = new VariationPosition()
{
Name = currentVariation.Name,
Value = val
};
// Add position to already aggregated on upper levels of recursion positions.
var newPositions = aggregatedPositions.Concat(Enumerable.Repeat(position, 1));
// So we picked some variation position
// Let's go deeper.
var combinations = this.GetCombinations(variations, variationIndex + 1, newPositions );
// This piece of code allows us return combinations in iterator fashion.
foreach (var combination in combinations)
{
yield return combination;
}
}
}
else
{
// We reached end of variations array
// I mean we have one position of every variation.
// We concatenate name of positions in order to create string like "Red-S-34"
var name = aggregatedPositions.Aggregate("", (res, v) => res += v.Name);
// This code is a little bit naive, I'm too lazy to create proper infrastructure,
// But its mission is to create content for property Value of your ebayVariations item.
var value = aggregatedPositions
.Select(v => new { Name = v.Name, Values = new[] { new { Value = v.Value } } })
.ToArray();
// And we return completed combination.
yield return new Combination()
{
Name = name,
Value = value,
};
}
}
And usage:
var allCombinations = this.GetCombinations(inputVariations, 0, new VariationPosition[0]).ToArray();