I have an array:
static Array results = new[] { new { Id, RoomCount, Cover, Space, Floor,
FloorCount, NameHouse, Price, NameStreet } };
then I have the second one:
var res = (//.......//
select new { f.Id, f.RoomCount, f.Cover, f.Space, f.Floor, f.FloorCount,
h.NameHouse, f.Price, s.NameStreet }).ToArray();
then I wanna copy res to results:
var lres = res.Length;
res.CopyTo(results, lres);
the lenght of the res varies, and the lenght of the results is fixed, cause it is just declared.
I got an exeption: the length of the dest array is too short.
I also tried to resize dest array:
var lres = res.Length;
Array.Resize(results, lres);
Also crashed: The type arguments for method System.Array.Resize<T>(ref T[], int) cannot be inferred from the usage. Try specifying the type arguments explicitly.
Any suggestions?
public partial class Search : System.Web.UI.Page
{
public static int Id { get; set; }
public static int RoomCount { get; set; }
public static string Cover { get; set; }
public static int Space { get; set; }
public static int Floor { get; set; }
public static int FloorCount { get; set; }
public static string NameHouse { get; set; }
public static decimal Price { get; set; }
public static string NameStreet { get; set; }
static Array results = new[] { new { Id, RoomCount, Cover, Space, Floor, FloorCount, NameHouse, Price, NameStreet } };
private Array FormData()
{
//some code
var lng = ht.Length;
while (lng != 0)
{
//
var var = ht[lng - 1];
lng--;
var res = (from f in db.FlatSet
.Where(x => x.RoomCount >= rMin && x.RoomCount <= rMax)
.Where(x => x.Space >= sMin && x.Space <= sMax)
.Where(x => x.Floor >= fMin && x.Floor <= fMax)
.Where(x => x.FloorCount >= fcMin && x.FloorCount <= fcMax)
.Where(x => x.HouseTypeId == var)// || x.HouseTypeId == 2)
.Where(x => x.Price >= pMin && x.Price <= pMax)
.Where(x => x.Rent == r)
join h in db.HouseTypeSet on f.HouseTypeId equals h.Id
join s in db.StreetSet on f.StreetId equals s.Id
select new { f.Id, f.RoomCount, f.Cover, f.Space, f.Floor, f.FloorCount, h.NameHouse, f.Price, s.NameStreet }).ToArray();
var lres = res.Length;
Array.Resize(ref results, lres);
res.CopyTo(results, lres);
}
return results;
}
FIXED:
public class result
{
public int Id { get; set; }
public int RoomCount { get; set; }
public string Cover { get; set; }
public int Space { get; set; }
public int Floor { get; set; }
public int FloorCount { get; set; }
public string NameHouse { get; set; }
public decimal Price { get; set; }
public string NameStreet { get; set; }
}
public partial class Search : System.Web.UI.Page
{
DB_9AB8FB_lisogorEntities db = new DB_9AB8FB_lisogorEntities();
List<result[]> myList = new List<result[]>();
List<result> NewMyList = new List<result>();
}
//////some code
private List<result[]> FormData()//
{
int[] ht = (int[])arr;
var lng = ht.Length;
while (lng != 0)
{
//some code
var var = ht[lng - 1];
lng--;
myList.Add((from f in db.FlatSet
.Where(x => x.RoomCount >= rMin && x.RoomCount <= rMax)
.Where(x => x.Space >= sMin && x.Space <= sMax)
.Where(x => x.Floor >= fMin && x.Floor <= fMax)
.Where(x => x.FloorCount >= fcMin && x.FloorCount <= fcMax)
.Where(x => x.HouseTypeId == var)// || x.HouseTypeId == 2)
.Where(x => x.Price >= pMin && x.Price <= pMax)
.Where(x => x.Rent == r)
join h in db.HouseTypeSet on f.HouseTypeId equals h.Id
join s in db.StreetSet on f.StreetId equals s.Id
select new result{ Id = f.Id, RoomCount = f.RoomCount, Cover = f.Cover, Space = f.Space, Floor = f.Floor,
FloorCount = f.FloorCount, NameHouse = h.NameHouse, Price = f.Price, NameStreet=s.NameStreet }).ToArray());
}
return myList;
}
private void BindData()
{
var i = myList.Count;
while (i != 0)
{
if (myList[i - 1].Length != 0)
{
var j = myList[i - 1].Length;
while (j != 0)
{
NewMyList.Add(myList[i - 1][j-1]);
j--;
}
}
i--;
}
Results1.DataSource = NewMyList;
Results1.DataBind();
}
}
Try putting the explicit type in your Array.Resize<T>:
Array.Resize<String>(ref results, lres);
Then change lres to 0 because lres is the length of the array and the second parameter for CopyTo is the starting index of the destination not the length of the array:
res.CopyTo(results, 0);
The index is the starting index and you start with the last index and that's why destination array was not long enough.
Or use Array.Copy():
Array.Copy(res, results, res.length);
you are using an anonymous type and the cocmpiler doesn't recognize how it will infer the union of the two arrays as the compiler should know the type of the object for which to create the instance
you are getting this exception at
Array.Resize<T>
method which should be strongly typed
Just to understand more
class Program
{
static void Main(string[] args)
{
Array results = new[] { new { RoomCount = "2", Cover = "5", Space = "5", Floor = "5", FloorCount = "5", NameHouse = "5", Price = "5", NameStreet = "5" } };
//with this line not cpmmented does not compile
Array.Resize(ref results, results.Length + 5);
String[] myArr = {"The", "quick", "brown", "fox", "jumps",
"over", "the", "lazy", "dog"};
Array.Resize(ref myArr, myArr.Length + 5);
MyTest[] resultsMyTest = new MyTest[] {new MyTest{RoomCount=3,Cover="Red"}};
//here it will work as the compiler know how to infer it
Array.Resize(ref resultsMyTest, results.Length + 5);
}
public class MyTest
{
public int RoomCount
{
get;
set;
}
public string Cover
{
get;
set;
}
}
}
Hope this help
If you are after a solution for your current code as it is, go with this,
var arguments=new object[] { results, results.Length + res.Length };
var method= typeof(Array).GetMethod("Resize").MakeGenericMethod(res.GetType().GetElementType());
method.Invoke(null,arguments );
var lastIndex = results.Length;
results = arguments[0] as Array;
res.CopyTo(results, lastIndex);
But I strongly suggest you not to do this. But create a class with all those properties and use a List<YourClass> for a clean and maintainable code. A generic Array of anonymous class won't take you long before you are forced to do a complete re-factoring.
Related
Let's suppose we have the following 2 objects (Please ignore the strange situation, as it is used only to model the real issue):
public class Person
{
public string FullName { get; set; }
public decimal TotalSalary { get; set; }
public List<Position> Positions { get; set; } = new List<Position>();
}
public class Position
{
public string Name { get; set; }
public bool IsCurrentPosition { get; set; }
public DateTime ExpirationDate { get; set; }
public int? MonthsWorked { get; set; }
public bool HasAverageLimitStatistic { get; set; } = false;
public int AverageMonthLimit { get; set; } = 0;
public decimal Salary { get; set; }
public string CompanyName { get; set; }
}
How do I write a query in NEST (C#) to find all people who work (or worked) on positions which are coming close to AverageMonthLimit in a timespan of a year, (positions without AverageMonthLimit are ignored); in case MonthsWorked is provided than compare it to AverageMonthLimit, otherwise take today's date and compare it to ExpirationDate.
PS: If the position has the statistic, then minimal AverageMonthLimit is of 36 months. People might work on several jobs.
NOTE: MSSQL script would probably look like:
SELECT *
FROM Person p
JOIN Position pos
ON pos.PersonId = p.Id
WHERE
pos.HasAverageLimitStatistic = 1
AND pos.AverageMonthLimit > pos.MonthsWorked
AND pos.AverageMonthLimit - 12 < pos.MonthsWorked
OR pos.HasAverageLimitStatistic = 0
AND DATEDIFF(MONTH, GETDATE(), pos.ExpirationDate) > 0
AND DATEDIFF(MONTH, GETDATE(), pos.ExpirationDate) < 12
UPDATE
Correct me if I'm wrong, but I presume the question boils down to the following, where query is unknown:
var response = _elasticClient.Search<Person>(s => s.Query(q =>
.DateRange(x => x
.Field(p => p.Employees
.First(pos => pos.HasAverageLimitStatistic == null)
.ExpirationDate)
.GreaterThan(DateTime.Now)
.LessThan(DateTime.Now.AddYear(1)))
// upper code finds customers who don't have statistic
|| q.Nested(x => x
.Path(p => p.Positions)
.Query(qq => qq.Script.Inline(" ?????? "))));
When querying against nested types, you need to use nested queries, which will take care of querying against the internal documents that nested types are modelled with.
Here's what your SQL query would look like in Elasticsearch
var searchResponse = client.Search<Person>(s => s
.Query(q => q
.Nested(n => n
.Path(p => p.Positions)
.Query(nq => (+nq
.Term(f => f.Positions.First().HasAverageLimitStatistic, true) && +nq
.Script(sq => sq
.Inline("doc['positions.monthsWorked'].value != 0 && doc['positions.averageMonthLimit'].value > doc['positions.monthsWorked'].value")
) && +nq
.Script(sq => sq
.Inline("doc['positions.monthsWorked'].value != 0 && (doc['positions.averageMonthLimit'].value - 12) < doc['positions.monthsWorked'].value")
)) || (+nq
.Term(f => f.Positions.First().HasAverageLimitStatistic, false) && +nq
.DateRange(d => d
.Field(f => f.Positions.First().ExpirationDate)
.GreaterThan(DateTime.Now.Date)
.LessThan(DateTime.Now.Date.AddYears(1))
))
)
)
)
);
There's a fair bit going here, so I'll break it down
Inside the top level query, a nested query will be performed. Since all predicates (i.e. WHERE clauses) are based on properties of nested types, these queries will all be inside of the nested query.
Specify the Path to the nested type on the top level type
Inside the nested query's query, 3 queries are &&ed together within parentheses for the first set of predicates where HasAverageLimitStatistic is true and 3 queries are &&ed together in parentheses for the second set of predicates where HasAverageLimitStatistic is false.
Since all queries test whether some condition is true or not, we can forgo calculating a score for them by making them filter clauses in a bool query. NEST has handy shorthand for bool query filter clauses in the form of the unary + operator. Similarly, there are overloaded operators combining queries with && and ||
Because you want to compare two properties on an indexed document, you need to use a script query to do this. In Elasticsearch 5.x, the default language for this is Painless.
The query DSL JSON for the above query is
{
"query": {
"nested": {
"query": {
"bool": {
"should": [
{
"bool": {
"filter": [
{
"term": {
"positions.hasAverageLimitStatistic": {
"value": true
}
}
},
{
"script": {
"script": {
"inline": "doc['positions.monthsWorked'].value != 0 && doc['positions.averageMonthLimit'].value > doc['positions.monthsWorked'].value"
}
}
},
{
"script": {
"script": {
"inline": "doc['positions.monthsWorked'].value != 0 && (doc['positions.averageMonthLimit'].value - 12) < doc['positions.monthsWorked'].value"
}
}
}
]
}
},
{
"bool": {
"filter": [
{
"term": {
"positions.hasAverageLimitStatistic": {
"value": false
}
}
},
{
"range": {
"positions.expirationDate": {
"gt": "2017-06-03T00:00:00-07:00",
"lt": "2018-06-03T00:00:00-07:00"
}
}
}
]
}
}
]
}
},
"path": "positions"
}
}
}
Finally, here's a complete example to demonstrate
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(pool)
.InferMappingFor<Person>(m => m
.IndexName("people")
)
.PrettyJson()
.DisableDirectStreaming()
.OnRequestCompleted(response =>
{
// log out the request
if (response.RequestBodyInBytes != null)
{
Console.WriteLine(
$"{response.HttpMethod} {response.Uri} \n" +
$"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}");
}
else
{
Console.WriteLine($"{response.HttpMethod} {response.Uri}");
}
Console.WriteLine();
// log out the response
if (response.ResponseBodyInBytes != null)
{
Console.WriteLine($"Status: {response.HttpStatusCode}\n" +
$"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" +
$"{new string('-', 30)}\n");
}
else
{
Console.WriteLine($"Status: {response.HttpStatusCode}\n" +
$"{new string('-', 30)}\n");
}
});
var client = new ElasticClient(connectionSettings);
if (client.IndexExists("people").Exists)
{
client.DeleteIndex("people");
}
client.CreateIndex("people", c => c
.Mappings(m => m
.Map<Person>(mm => mm
.AutoMap()
.Properties(p => p
.Nested<Position>(n => n
.Name(nn => nn.Positions)
.AutoMap()
)
)
)
)
);
client.IndexMany(new[] {
new Person
{
FullName = "Person 1",
TotalSalary = 100000,
Positions = new List<Position>
{
new Position
{
Name = "Janitor",
AverageMonthLimit = 5,
MonthsWorked = 3,
HasAverageLimitStatistic = true
}
}
},
new Person
{
FullName = "Person 2",
TotalSalary = 150000,
Positions = new List<Position>
{
new Position
{
Name = "Coach",
AverageMonthLimit = 5,
HasAverageLimitStatistic = true
}
}
},
new Person
{
FullName = "Person 3",
TotalSalary = 200000,
Positions = new List<Position>
{
new Position
{
Name = "Teacher",
HasAverageLimitStatistic = false,
ExpirationDate = DateTime.Now.AddMonths(6)
}
}
},
new Person
{
FullName = "Person 4",
TotalSalary = 250000,
Positions = new List<Position>
{
new Position
{
Name = "Head",
HasAverageLimitStatistic = false,
ExpirationDate = DateTime.Now.AddYears(2)
}
}
}
});
client.Refresh(IndexName.From<Person>());
var searchResponse = client.Search<Person>(s => s
.Query(q => q
.Nested(n => n
.Path(p => p.Positions)
.Query(nq => (+nq
.Term(f => f.Positions.First().HasAverageLimitStatistic, true) && +nq
.Script(sq => sq
.Inline("doc['positions.monthsWorked'].value != 0 && doc['positions.averageMonthLimit'].value > doc['positions.monthsWorked'].value")
) && +nq
.Script(sq => sq
.Inline("doc['positions.monthsWorked'].value != 0 && (doc['positions.averageMonthLimit'].value - 12) < doc['positions.monthsWorked'].value")
)) || (+nq
.Term(f => f.Positions.First().HasAverageLimitStatistic, false) && +nq
.DateRange(d => d
.Field(f => f.Positions.First().ExpirationDate)
.GreaterThan(DateTime.Now.Date)
.LessThan(DateTime.Now.Date.AddYears(1))
))
)
)
)
);
}
public class Person
{
public string FullName { get; set; }
public decimal TotalSalary { get; set; }
public List<Position> Positions { get; set; } = new List<Position>();
}
public class Position
{
public string Name { get; set; }
public bool IsCurrentPosition { get; set; }
public DateTime ExpirationDate { get; set; }
public int? MonthsWorked { get; set; }
public bool HasAverageLimitStatistic { get; set; } = false;
public int AverageMonthLimit { get; set; } = 0;
public decimal Salary { get; set; }
public string CompanyName { get; set; }
}
Based on the query criteria, only Person 1 and Person 3 are returned.
I'm trying to bind a linkedlist to a datagridview. The method below works for the properties in the class except the array.
If I declare the array as a new instance the linkedlist is created correctly, but the array is not bound into the datagridview.
If the array is created as a property (I think the code is correct) causes An unhandled exception of type 'System.StackOverflowException' occurred when the linkedlist is created.
Thanks for any help.
public class PayoffNode
{
public int DealNo { get; set; }
public string Category { get; set; }
public string Strategy { get; set; }
public string GreekType { get; set; }
// declare array as instance or as a property?
//public double[] Data = new double[22];
public double[] Data
{
get { return Data; }
set { Data = value; }
}
}
LinkedList<Globals.PayoffNode> PayLL = new LinkedList<Globals.PayoffNode>();
Random Rnd = new Random();
for (int K = 1; K <= 10; K++)
{
var T = new Globals.PayoffNode();
T.Category = "Account==" + K;
T.GreekType = "Greek==" + K;
T.DealNo = K;
T.Strategy = "Strategy==" + K;
for (int DP = 1; DP <= 21; DP++)
{
T.Data[DP] = Rnd.Next(10, 99);
}
PayLL.AddLast(T);
}
List<Globals.PayoffNode> qP = (from P in PayLL
where P.Category == "Account==4" && P.DealNo == 4 && P.GreekType == "Greek==4" && P.Strategy == "Strategy==4"
select P).ToList();
PayoffTable.DataSource = qP;
Update:
Thanks for the comments, this seems to be working.
public class PayoffNode
{
public int DealNo { get; set; }
public string Category { get; set; }
public string Strategy { get; set; }
public string GreekType { get; set; }
public double Data1 { get; set; }
public double Data2 { get; set; }
public double Data3 { get; set; }
public double[] Data = new double[22];
}
LinkedList<Globals.PayoffNode> PayLL = new LinkedList<Globals.PayoffNode>();
Random Rnd = new Random();
for (int K = 1; K <= 10; K++)
{
var T = new Globals.PayoffNode();
T.Category = "Account==" + K;
T.GreekType = "Greek==" + K;
T.DealNo = K;
T.Strategy = "Strategy==" + K;
for (int DP = 1; DP <= 21; DP++)
{
T.Data[DP] = Rnd.Next(10, 99);
}
PayLL.AddLast(T);
}
List<Globals.PayoffNode> qP = (from P in PayLL
where P.Category == "Account==4" && P.DealNo == 4 && P.GreekType == "Greek==4" && P.Strategy == "Strategy==4"
select new Globals.PayoffNode()
{
Category=P.Category,
DealNo=P.DealNo,
GreekType=P.GreekType,
Strategy=P.Strategy,
Data1=P.Data[1],
Data2 = P.Data[2],
Data3 = P.Data[3],
}).ToList();
PayoffTable.DataSource = qP;
One way to avoid making 21 Data properties is to convert the List to DataTable:
class PayoffNode
{
public int DealNo;
public string Category;
public double[] Data; // = new double[21];
}
and then
Random Rnd = new Random();
List<PayoffNode> PayLL = Enumerable.Range(1, 10).Select(i => new PayoffNode {
DealNo = i,
Category = "Account==" + i,
Data = Enumerable.Range(1, 21).Select(d => (double)Rnd.Next(10, 99)).ToArray()
}).ToList();
// List<PayoffNode> to DataTable
var dt = new DataTable();
dt.Columns.Add("DealNo", typeof(int));
dt.Columns.Add("Category"); // typeof(string) by default
for (int i = 1; i <= 21; i++)
dt.Columns.Add("Data" + i, typeof(double));
foreach (var P in PayLL)
{
var dr = dt.Rows.Add(P.DealNo, P.Category);
for (int i = 0; i < 21; i++)
dr[i+2] = P.Data[i]; // +2 is the number of fields before the Data fields
}
PayoffTable.DataSource = dt;
dt.DefaultView.RowFilter = " Category = 'Account==4' ";
The advantage is that you set the DataSource only once and just change the RowFilter to filter it. Also, any changes made in the DataGridView change the DataTable and vice-versa.
Note that arrays in C# and most other languages start from 0 and not from 1 ( .Data[0] to access the first item in the array ), so the for loop to access the Data array in my example is from 0 to 20.
public double[] Data
{
get { return Data; }
set { Data = value; }
}
This is the problem, the object in the set and get method needs to refer to a different object otherwise whenever the object value is set or trying to be retrieved it will just keep calling these methods infinitely. should be...
private double[] _data;
public double[] Data
{
get { return _data; }
set { _data = value; }
}
or...
public double[] Data { get; set; }
Code:
string[] seperators = { "," };
string[] typesList = types.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
string[] topicsList = topics.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
if (typesList.Length > 0)
filter = filter & builder.Where(t => typesList.Contains(t.Type));
if (topicsList.Length > 0)
filter = filter & builder.Where(t => topicsList.Any(id => t.Topics.Any(p => id == p.ToString())));
// above doesn't work
My class has these properties:
public List<int> Topics { get; set; }
public string Type { get; set; }
How can I write query to make this work?
Edit:
I could be unclear as you mentioned in comments so let's focus on this code instead:
var list = new List<MyClass>();
list = LoadElements();
string[] parametr = { "8", "11" };
var result = list.Where(pm => pm.Topics.Any(t=> t.Equals(8) == true )).ToList();
//this above works for static '8' but I want to query through my 'parametr' list.
MyClass object is:
public class MyClass
{
public string Data { get; set; }
public List<int> Topics{ get; set; }
}
var result = list.Where(record => parametr.Any(parm => record.Topics.Any(t => parm == t.ToString()))).ToList();
How do I filter an item from a list based on two different columns one being a number(smallest number) using LINQ C#?
public class Line
{
public int Id { get; set; }
public List<LineItem> LineItems { get; set; }
public Line()
{
LineItems = new List<LineItem> {
new LineItem{Num = 1, Name="i", Qty = 0, Active = false},
new LineItem{Num = 2, Name="j", Qty = 2,Active = false},
new LineItem{Num = 3, Name="k", Qty = 3,Active = false},
};
}
}
public class LineItem
{
public int Num { get; set; }
public string Name { get; set; }
public int Qty { get; set; }
public bool Active { get; set; }
}
I want to filter this list and get a LineItem based on Qty = 0 and smallest num value.
Try filtering by Qty == 0, sorting according to Num and keep the first one:
var lineItem = LineItems.Where(l => l.Qty == 0).OrderBy(l => l.Num).FirstOrDefault();
or just keep the first that Qty equals to 0 and Num equals to minimum possible:
var minNum = LineItems.Where(l => l.Qty == 0).Min(l => l.Num);
var lineItem = LineItems.FirstOrDefault(l => l.Qty == 0 && l.Num == minNum);
You could try to get the min value for Qty is 0 and order by Num in ascending mode, then taking the first item. For sample:
var item = LineItems.Where(x => x.Qty == 0).OrderBy(x => x.Num).First();
If you have your LineItem class implement IComparable<T>, then you can do something like this:
public class LineItem : IComparable<LineItem>
{
public int Num { get; set; }
public string Name { get; set; }
public int Qty { get; set; }
public bool Active { get; set; }
public int CompareTo(LineItem other)
{
if (other.Num > this.Num)
return -1;
else if (other.Num == this.Num)
return 0;
else
return 1;
}
}
then
var item = l.LineItems.Where(p => p.Qty == 0).Min();
I have an object in a list that I need to rank several different ways. Currently the code is rather unwieldy as it requires me to individually address each column. Example:
public class Data
{
public int AValue { get; set; }
public int ARanking { get; set; }
public int BValue { get; set; }
public int BRanking { get; set; }
public int CValue { get; set; }
public int CRanking { get; set; }
}
public class Container
{
public List<Data> RankingData { get; set; }
public void RankData()
{
int count = 1;
foreach (Data item in RankingData.OrderBy(d => d.AValue))
{
item.ARanking = count;
count++;
}
count = 1;
foreach (Data item in RankingData.OrderBy(d => d.BValue))
{
item.BRanking = count;
count++;
}
count = 1;
foreach (Data item in RankingData.OrderBy(d => d.CValue))
{
item.CRanking = count;
count++;
}
}
}
The problem I am trying to solve is I want to write something roughly like this:
public void RankData<V, R>()
{
int count = 1;
foreach(Data item in RankingData.OrderBy(V))
{
item.R = count;
count++;
}
}
So that as I need to alter the ranking logic (for example, handle tie breaking rules) that I write the code once instead of copying the code 20 times to make the rules match. What am I missing?
UPDATE
Using Tanzelax's solution as a base this is the extension class I came up with:
public static class RankingExtension
{
public static void SetRanking<TKey>(this List<Data> dataSet, bool Ascending, Func<Data, TKey> getOrderBy, Action<Data, int> setRank)
where TKey : IComparable
{
var ordered = (Ascending) ? dataSet.OrderBy(getOrderBy) : dataSet.OrderByDescending(getOrderBy);
int i = 1;
foreach (Data item in ordered)
{
setRank(item, i);
i++;
}
}
}
I had to add in a switch so that I could control whether or not the field was being sorted ascending or not. And in my test scenarios it produces the appropriate output:
List<Data> test = new List<Data>();
test.Add(new Data { AValue = 25, BValue = 1.25, CValue = 99.99 });
test.Add(new Data { AValue = 89, BValue = 2.10, CValue = 1.01 });
test.Add(new Data { AValue = 10, BValue = 6, CValue = 45.45 });
test.Add(new Data { AValue = 15, BValue = 2.33, CValue = 2.99 });
test.Add(new Data { AValue = 90, BValue = 5.43, CValue = 27.89 });
test.SetRanking(false, d => d.AValue, (d, i) => d.ARank = i);
test.SetRanking(false, d => d.BValue, (d, i) => d.BRank = i);
test.SetRanking(true, d => d.CValue, (d, i) => d.CRank = i);
Not tested, but something like this:
void SetRanking(this List<Data> dataSet, Func<Data,int> getOrderBy, Action<Data,int> setRank)
{
var ordered = dataSet.OrderBy(getOrderBy).ToArray();
int i = i;
foreach (Data item in ordered)
{
setRank(item, i);
i++;
}
}
RankingData.SetRanking(d => d.AValue, (d,i) => d.ARanking = i);
RankingData.SetRanking(d => d.BValue, (d,i) => d.BRanking = i);
RankingData.SetRanking(d => d.CValue, (d,i) => d.CRanking = i);
This is similar to Tanzelax's answer but is a generic extension method.
public static void RankData<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Action<TSource, int> rankSetter
)
{
int count = 1;
foreach (var item in source.OrderBy(keySelector))
{
rankSetter(item, count);
++count;
}
}
It would be called similar to Tanzelax's answer also.
RankingData.RankData(d => d.AValue, (d,i) => d.ARanking = i);
Pass in a Func<Data,K> that returns the ranking key. K should implement IComparable
public static void Rank<K>( IEnumerable<Data> source, Func<Data,K> rankBy ) where K : IComparable
{
int count = 1;
foreach (var item in source.OrderBy( rankBy ))
{
item.R = count;
++count;
}
}