Order list by List<int> property - linq

I need to order my list by points, then by positions. How can I order my list by the Positions List property?
public class Sailor
{
public string Name { get; set; }
public int Points { get; set; }
public List<int> Positions { get; set; }
public Sailor(string name, int points, List<int> positions)
{
Name = name;
Points = points;
Positions = positions;
}
}
var sailors = new List<Sailor>
{
new Sailor("Carl", 20, new List<int> { 2, 2, 4, 1, 1 }),
new Sailor("Paul", 10, new List<int> { 4, 5, 3, 2, 5 }),
new Sailor("Anna", 20, new List<int> { 1, 1, 1, 3, 4 }),
new Sailor("Lisa", 11, new List<int> { 3, 4, 5, 5, 2 }),
new Sailor("Otto", 11, new List<int> { 5, 3, 2, 4, 3 })
};
foreach (var sailor in sailors)
{
sailor.Positions.Sort();
}
var orderedListOfSailors = sailors.OrderByDescending(x => x.Points);
This gives me:
Carl, Anna, Lisa, Otto, Paul
What I want it to be:
Anna, Carl, Otto, Lisa, Paul
Why? Because Anna have 3 first places, Carl have 2. Otto have 2, 3, 3, Lisa have 2, 3, 4.

The problem can be solved by using lexicographical odering on instances of Sailor; either you could implement a custom comparator for Sailor or use the ThenBy extension method.

After ordering them by Points, order them again by the number of places.
var orderedListOfSailors = sailors
.OrderByDescending(x => x.Points)
.ThenByDescending(x => x.Positions.Count(y => y == 1))
.ThenByDescending(x => x.Positions.Count(y => y == 2))
.ThenByDescending(x => x.Positions.Count(y => y == 3))
.ThenByDescending(x => x.Positions.Count(y => y == 4));

Related

Linq subselect filter

probably someone can help me with this (at least for me) complicated problem.
Lets say i have the following data (in DB)
Tab1 (id_t1): Item
(1)
(2)
(3)
Tab2 (id_t2, id_t1): Group
(4, 1)
(5, 1)
(6, 2)
(7, 3)
Tab3 (id_t3, id_t2, v): GroupField
(10, 4, 100)
(11, 4, 300)
(12, 5, 200)
(13, 6, 100)
(14, 6, 200)
(15, 7, 100)
(16, 7, 300)
Now i'd like to select all Items that include all of some specific GroupFields.
Eg. i have v = list(100,200)
and i like to get back 1,2 but not 3
1 because Group4 holds the Field10 with v=100 and Group5 holds Field12 with v=200
and 2 because Group6 holds Field13 with v=100 and Field14 with v=200
Is something like this possible in Linq? (i allready tried different ways (any/all) but without success so far.
I don't get the point how to overcome that "field can be in any Group and not all in one Group"...
I don't even know how to do this in SQL in one command without using temp-tables/cursors.
_rene
Try this:
var result =
groups.Join(fields, o => o.Id, i => i.GroupId,
(o, i) => new { Group = o, Field = i } )
.GroupBy(x => x.Group.ItemId)
.Where(x => values.All(y => x.Any(z => z.Field.Value == y)))
.Select(x => x.Key)
.Distinct();
The following classes are used:
class Group
{
public Group(int id, int itemId)
{
Id = id;
ItemId = itemId;
}
public int Id { get; set; }
public int ItemId { get; set; }
}
class GroupField
{
public GroupField(int id, int groupId, int value)
{
Id = id;
GroupId = groupId;
Value = value;
}
public int Id { get; set; }
public int GroupId { get; set; }
public int Value { get; set; }
}
and the following initialization:
var groups = new [] { new Group(4, 1), new Group(5, 1),
new Group(6, 2), new Group(7, 3) };
var fields = new [] { new GroupField(10, 4, 100),
new GroupField(11, 4, 300),
new GroupField(12, 5, 200),
new GroupField(13, 6, 100),
new GroupField(14, 6, 200),
new GroupField(15, 7, 100),
new GroupField(16, 7, 300)
};
var values = new [] { 100, 200 };

Linq GroupBy without merging groupings with the same key if they are separated by other key

I'd like to archieve behaviour similar to Pythons' groupby.
[1, 1, 2, 1].GroupBy() => [[1, 1], [2], [1]]
I think this is what you're looking for:
var data = new int[] { 1, 1, 2, 1 };
var results = Enumerable.Range(0, data.Count ())
.Where (i => i == 0 || data.ElementAt(i - 1) != data.ElementAt(i))
.Select (i => new
{
//Key = data.ElementAt(i),
Group = Enumerable.Repeat(
data.ElementAt(i),
data.Skip(i).TakeWhile (d => d == data.ElementAt(i)).Count ())
}
);
Here's an example of it running and the results: http://ideone.com/NJGQB
Here's a lazy, generic extension method that does what you want.
Code:
public static IEnumerable<IEnumerable<T>> MyGroupBy<T>(this IEnumerable<T> source)
{
using(var enumerator = source.GetEnumerator())
{
var currentgroup = new List<T>();
while (enumerator.MoveNext())
{
if (!currentgroup.Any() || currentgroup[0].Equals(enumerator.Current))
currentgroup.Add(enumerator.Current);
else
{
yield return currentgroup.AsReadOnly();
currentgroup = new List<T>() { enumerator.Current };
}
}
yield return currentgroup.AsReadOnly();
}
}
Test:
void Main()
{
var data = new int[] { 1, 1, 2, 1 };
foreach(var g in data.MyGroupBy())
Console.WriteLine(String.Join(", ", g));
}
Output:
1, 1
2
1

Linq left join with multiple columns and default values [duplicate]

This question already has answers here:
Closed 10 years ago.
I am fairly new to linq and I need to join two tables with the following requirements:
Should left join t1 and t2.
If t2 is empty then the query should not fail - should use default values.
My query:
var final = from t1 in saDist.AsEnumerable()
from t2 in sapGrouped.AsEnumerable()
where
t1.Supplier.Id == t2.Supplier.Id && t1.VatRate == t2.VatRate
select
new
{
t1.Supplier,
Amount = t1.Amount - t2.Amount,
Advance = t1.Advance - t2.Advance,
Balance = t1.Balance - t2.Balance,
t1.VatRate
};
Can someone correct this?
This works in Linqpad as a C# program.
Basically your join syntax needed tweaking (see this), and you needed to take into account when there was nothing to join to for "t2" (so we do a null check and use 0 when null, otherwise t2.Amount, etc)
I created some dummy data so you can play around.
See http://codingsense.wordpress.com/2009/03/08/left-join-right-join-using-linq/ for another example.
I hope it does what you want it to do.
Thanks,
Dominique
public class A
{
void Main()
{
Distributor dist1 = new Distributor() { SupplierID = 1, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "A", DeptSupplierID = 1 };
Distributor dist2 = new Distributor() { SupplierID = 2, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "B", DeptSupplierID = 1 };
Distributor dist3 = new Distributor() { SupplierID = 3, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "C", DeptSupplierID = 1 };
Distributor dist4 = new Distributor() { SupplierID = 4, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "D", DeptSupplierID = 2 };
Distributor dist5 = new Distributor() { SupplierID = 5, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "E", DeptSupplierID = 2 };
Distributor dist6 = new Distributor() { SupplierID = 6, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "F", DeptSupplierID = 2 };
Distributor dist7 = new Distributor() { SupplierID = 7, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "G", DeptSupplierID = 6 };
Distributor dist8 = new Distributor() { SupplierID = 8, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "H", DeptSupplierID = 3 };
Distributor dist9 = new Distributor() { SupplierID = 9, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "I", DeptSupplierID = 3 };
Distributor dist10 = new Distributor() { SupplierID = 10, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "J", DeptSupplierID = 7 };
Distributor dist11 = new Distributor() { SupplierID = 11, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "K", DeptSupplierID = 7 };
Distributor dist12 = new Distributor() { SupplierID = 12, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "L", DeptSupplierID = 5 };
SAPGroup Dept1 = new SAPGroup() { SupplierID = 1, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Development" };
SAPGroup Dept2 = new SAPGroup() { SupplierID = 2, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Testing" };
SAPGroup Dept3 = new SAPGroup() { SupplierID = 3, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Marketing" };
SAPGroup Dept4 = new SAPGroup() { SupplierID = 4, Amount = 3, Balance = 4, Advance = 3, VatRateID = 1, Name = "Support" };
List ListOfDistributors = new List();
ListOfDistributors.AddRange((new Distributor[] { dist1, dist2, dist3, dist4, dist5, dist6, dist7,
dist8, dist9, dist10, dist11, dist12 }));
List ListOfSAPGroup = new List();
ListOfSAPGroup.AddRange(new SAPGroup[] { Dept1, Dept2, Dept3, Dept4 });
var final = from t1 in ListOfDistributors
join t2 in ListOfSAPGroup
on new { t1.SupplierID, t1.VatRateID } equals new { t2.SupplierID, t2.VatRateID }
into JoinedDistAndGrouped
from t2 in JoinedDistAndGrouped.DefaultIfEmpty()
select new
{
Name1 = t1.Name,
Name2 = (t2 == null) ? "no name" : t2.Name,
SupplierID = t1.SupplierID,
Amount = t1.Amount - (t2 == null ? 0 : t2.Amount),
Advance = t1.Advance - (t2 == null ? 0 : t2.Advance),
Balance = t1.Advance - (t2 == null ? 0 : t2.Balance),
VatRateID = t1.VatRateID
};
final.Dump();
}
}
class Distributor
{
public string Name { get; set; }
public int SupplierID { get; set; }
public int VatRateID { get; set; }
public int DeptSupplierID { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
}
class SAPGroup
{
public int SupplierID { get; set; }
public int VatRateID { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
}
public class Result
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public int SupplierID { get; set; }
public int Amount { get; set; }
public int Advance { get; set; }
public int Balance { get; set; }
public int VatRateID { get; set; }
}
Thanks for your input. None of the answers did quite what I wanted, but I managed to get my original code working:
var final = from t2 in saDist.AsEnumerable()
from t1 in sapGrouped.AsEnumerable().DefaultIfEmpty()
where
t1 == null || (t2.Supplier.Id == t1.Supplier.Id && t2.VatRate == t1.VatRate)
select
new
{
t2.Supplier,
Amount = t2.Amount - (t1 == null ? 0 : t1.Amount),
Advance = t2.Advance - (t1 == null ? 0 : t1.Advance),
Balance = t2.Balance - (t1 == null ? 0 : t1.Balance),
t2.VatRate
};
If you have any comments or improvements on this let me know, thanks.
According to this, you are looking for something like (this is untested, but hopefully leads you on the right track):
var final = from t1 in saDist.AsEnumerable()
join t2 in sapGrouped.AsEnumerable()
on t1.Supplier.Id equals t2.Supplier.Id
and t1.VatRate equals t2.VatRate into t1_t2 //not sure about this line
from t2 in t1_t2.DefaultIfEmpty()
{
t1.Supplier,
Amount = t1.Amount - t2.Amount,
Advance = t1.Advance - t2.Advance,
Balance = t1.Balance - t2.Balance,
t1.VatRate
};
Notice the .DefaultIfEmpty(), this satisfies: "If t2 is empty then the query should not fail - should use default values."

using LINQ getting previous and next element

One of my colleague was looking for something like picking up previous and next values from a list for a given value. I wrote a little function with some help of Google, which works but I wanted to see
1. if is this an efficient way to do this?
2. Any other way in LINQ to do this?
private static List<double> GetHighLow(double value)
{
List<double> tenorList = new List<double> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 20, 30 };
double previous = tenorList.OrderByDescending(s => s).Where(s => s.CompareTo(value) < 0).FirstOrDefault();
double next = tenorList.OrderBy(s => s).Where(s => s.CompareTo(value) > 0).FirstOrDefault();
List<double> values = new List<double> { previous, next };
return values;
}
thanks
Pak
Ordering just to find a single item would make me suspicious.
You can do it in linear time this way:
double prev = double.MinValue;
double nx = double.MaxValue;
foreach (var item in tenorList) {
if (item < value && item > prev) { prev = item; }
if (item > value && item < nx) { nx = item; }
}
List<double> values = new List<double> { prev, nx };

How do I transfer this logic into a LINQ statement?

I can't get this bit of logic converted into a Linq statement and it is driving me nuts. I have a list of items that have a category and a createdondate field. I want to group by the category and only return items that have the max date for their category.
So for example, the list contains items with categories 1 and 2. The first day (1/1) I post two items to both categories 1 and 2. The second day (1/2) I post three items to category 1. The list should return the second day postings to category 1 and the first day postings to category 2.
Right now I have it grouping by the category then running through a foreach loop to compare each item in the group with the max date of the group, if the date is less than the max date it removes the item.
There's got to be a way to take the loop out, but I haven't figured it out!
You can do something like that :
from item in list
group item by item.Category into g
select g.OrderByDescending(it => it.CreationDate).First();
However, it's not very efficient, because it needs to sort the items of each group, which is more complex than necessary (you don't actually need to sort, you just need to scan the list once). So I created this extension method to find the item with the max value of a property (or function) :
public static T WithMax<T, TValue>(this IEnumerable<T> source, Func<T, TValue> selector)
{
var max = default(TValue);
var withMax = default(T);
var comparer = Comparer<TValue>.Default;
bool first = true;
foreach (var item in source)
{
var value = selector(item);
int compare = comparer.Compare(value, max);
if (compare > 0 || first)
{
max = value;
withMax = item;
}
first = false;
}
return withMax;
}
You can use it as follows :
from item in list
group item by item.Category into g
select g.WithMax(it => it.CreationDate);
UPDATE : As Anthony noted in his comment, this code doesn't exactly answer the question... if you want all items which date is the maximum of their category, you can do something like that :
from item in list
group item by item.Category into g
let maxDate = g.Max(it => it.CreationDate)
select new
{
Category = g.Key,
Items = g.Where(it => it.CreationDate == maxDate)
};
How about this:
private class Test
{
public string Category { get; set; }
public DateTime PostDate { get; set; }
public string Post { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
List<Test> test = new List<Test>();
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 5, 12, 0, 0), Post = "A1" });
test.Add(new Test() { Category = "B", PostDate = new DateTime(2010, 5, 5, 13, 0, 0), Post = "B1" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 12, 0, 0), Post = "A2" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 13, 0, 0), Post = "A3" });
test.Add(new Test() { Category = "A", PostDate = new DateTime(2010, 5, 6, 14, 0, 0), Post = "A4" });
var q = test.GroupBy(t => t.Category).Select(g => new { grp = g, max = g.Max(t2 => t2.PostDate).Date }).SelectMany(x => x.grp.Where(t => t.PostDate >= x.max));
}
Reformatting luc's excellent answer to query comprehension form. I like this better for this kind of query because the scoping rules let me write more concisely.
from item in source
group item by item.Category into g
let max = g.Max(item2 => item2.PostDate).Date
from item3 in g
where item3.PostDate.Date == max
select item3;

Resources