Ordering a list in-place by two properties - c#

I have a class with two properties, Name and Position.
I would like to order the list with this class by Position, and the elements with the same position should be ordered by Name. I am working on a static list, so I would like to work in-place.
So far I managed to order the list by one property:
list.Sort((x, y) => x.Position.CompareTo(y.Position));
this code is working and I have the list ordered by Position, but I don't know how to implement the second part. I found this question, but I don't understand the answer.
Could anyone please help me?

I would use "OrderBy" and "ThenBy":
IEnumerable<Person> orderedPersons = persons.OrderBy(item => item.Position)
.ThenBy(item => item.Name);

The answer you linked to is correct. The key in sorting by multiple values is that the secondary property only matters if the primaries are equal. A psuedocode implementation of your sort comparison might be:
compare x and y position
if they differ, return order
else compare name, return order
On the Sort method, the code following the (x,y)=> must return 0 if the items are equal, a negative number if the first should be before the second, and a positive number if the second should come before the first. The CompareTo method will return -1, 0, or 1 based on these cases and its arguments. Since you need to compare two different properties, you need two calls to CompareTo. If you decided to add them together, you could have a case like this:
x.position < y.position (compare returns -1)
x.name > y.name (compare returns 1)
result 0, items are considered equal, where your rules
clearly say x should come first in this case.
To solve this issue, we need to make sure the Name comparison only matters when the positions are equal. Since CompareTo only returns -1, 0, or 1, if we multiply the position result by 2 (or any larger number) then the Name comparison will only change the result if the positions are equal. (Because -2 + 1 = -1 and 2 - 1 = 1)
So using the method in your original linked answer, your code would be something like:
list.Sort((x, y) =>
2 * x.Position.CompareTo(y.Position)
+ x.Name.CompareTo(y.Name));

list = list.OrderBy(item => item.Name).ToList();
list.Sort((x, y) => x.Position.CompareTo(y.Position));

Related

Count of integer values in a List using Lambda Expression

I have a list like this:
myList.Add(1);
myList.Add(1);
myList.Add(3);
myList.Add(4);
myList.Add(5);
myList.Add(6);
I want to find the number of '1's upto an index of 4. So, in this case, the result should be 2.
How do you I put a condition in the Count().
I need the number of integers, which fulfills the criterion n<2 && n>0 (i.e. n==1).
You only care abou the first 4 entries in the list (as you said, values with higher indexes don't matter).
So at first, restrict the search to these four numbers:
myList.Take(4)
from these you want to count only the entries that are 1. You can achieve that using the Count() linq extension that takes a predicate:
int numberOfOnes = myList.Take(4).Count(i => i == 1);
int value = myList.Take(4).Where(w=>w == 1).Sum()
Additionally, 'Where' has a less commonly used overload that provides the index: myList.Where((w,ix)=>ix < 4 && w == 1).Sum().
I would probably use .Take() for readability and performance reasons (see #mjwills comment) though.

GroupBy of List<double> with tolerance doesn't work [duplicate]

This question already has answers here:
writing a custom comparer for linq groupby
(2 answers)
Closed 6 years ago.
I have a question about Groupby of C#.
I made a List like shown below:
List<double> testList = new List<double>();
testList.Add(1);
testList.Add(2.1);
testList.Add(2.0);
testList.Add(3.0);
testList.Add(3.1);
testList.Add(3.2);
testList.Add(4.2);
I'd like to group these number list like this:
group 1 => 1
group 2 => 2.1 , 2.0
group 3 => 3.0 , 3.1 , 3.2
group 4 => 4.2
so, I wrote code like this:
var testListGroup = testList.GroupBy(ele => ele, new DoubleEqualityComparer(0.5));
DoubleEqualityComparer definition is like this:
public class DoubleEqualityComparer : IEqualityComparer<double>
{
private double tol = 0;
public DoubleEqualityComparer(double Tol)
{
tol = Tol;
}
public bool Equals(double d1,double d2)
{
return EQ(d1,d2, tol);
}
public int GetHashCode(double d)
{
return d.GetHashCode();
}
public bool EQ(double dbl, double compareDbl, double tolerance)
{
return Math.Abs(dbl - compareDbl) < tolerance;
}
}
Yet the GroupBy clause doesn't work like the this:
group 1 => 1
group 2 => 2.1
group 3 => 2.0
group 4 => 3.0
group 5 => 3.1
group 6 => 3.2
group 7 => 4.2
I don't know what the problem is. Please let me know if there is problem, and solutions.
use simple Math.Floor to get lower range of the number so that 5.8 should not be treated as 6.
List<double> testList = new List<double>();
testList.Add(1);
testList.Add(2.1);
testList.Add(2.0);
testList.Add(3.0);
testList.Add(3.1);
testList.Add(3.2);
testList.Add(4.2);
testList.Add(5.8);
testList.Add(5.5);
var testListGroup = testList.GroupBy(s => Math.Floor(s)).ToList();
Your GetHashCode method should return the same value for numbers, that should be "equal".
EqualityComparer works in two steps:
Checked GetHashCode if the value with this hash code was not
processed yet, then this value gets into new single group
If value with this hash code was obtained - then checkinq result of
Equals method. If it is true - adding current element to existing group, else adding it to new group.
In your case every double returns the different hash codes, so method Equals does not called.
So, if you do not care about processing time, you can simple return constant value in GetHashCode method as #FirstCall suggested. And if you care about it, I recommend to modify your method as follows:
public int GetHashCode(double d)
{
return Math.Round(d).GetHashCode();
}
Math.Round should correctly work for tolerance = 0.5, for another tolerance values you should improve this.
I recommend you to read this blog post to get familiar with IEqualityComparer and Linq.
The simplest way with less amount of code is always return the constant value from the GetHashCode - it will work for any tolerance value, but, as I wrote, it is quite inefficient solution on large amounts of data.
In these types of situations, the debugger is your friend. Put a break point on the Equals method. You will notice that the Equals method of your DoubleEqualityComparer class is not getting hit.
Linq extension methods rely on GetHashCode for equality comparisons. Since the GetHashCode method is not returning equivalent hashes for the doubles in your list, the Equals method is not getting called.
Each GetHashCode method should be atomic in execution and should return the same int value for any two equal comparisons.
This is one working example, though it is not necessarily recommended depending on your usage of this comparer.
public int GetHashCode(double d)
{
return 1;
}
You can group by using below code sample,
var list = testList.GroupBy(s => Convert.ToInt32(s) ).Select(group => new { Key = group.Key, Elements = group.ToList() });
//OutPut
//group 1 => 1
//group 2 => 2.1 , 2
//group 3 => 3 , 3.1 , 3.2
//group 4 => 4.2
Explanation of the code,
When we apply GroupBy for a list which have only a single data column,It groups by looking same content. For an example think you have string list (foo1, foo2, foo3, foo1, foo1, foo2). So then it groups into three separate group leading by foo1, foo2 and foo3.
But in this scenario you can't find any same content(1.0,2.1,2.2,2.3,3.1,3.2...)So what we should do is bring them as a same content. When we convert them to int then it gives (1,2,2,2,3,3...). Then we can easily group it.
Everyone here is discussing what is wrong with your code, but you may actually have a worse problem than that.
If you truly want to group with a tolerance like your title says, rather than group by integer part like these answers assume (and your test data supports), this isn't supported by GroupBy.
GroupBy demands an equivalence relation - your equality comparer must establish that
x == x for all x
if x == y, y == x for all x and y
if x == y and y == z, x == z for all x, y and z
"Within 0.5 of each other" matches the first two points, but not the third. 0 is close so 0.4, and 0.4 is close to 0.8, but 0 is not close to 0.8. Given an input of 0, 0.4 and 0.8, what groups would you expect?
Your problem is your implementation of GetHashCode which must return equal values for everything you consider to be equal. So for two different values d1 and d2 that should go to the same group the method should return the same hash-code. To do so round the given number to the nearest intereger and compute its hashcode afterwards:
public int GetHashCode(double d)
{
return Convert.ToInt32(d).GetHashCode();
}
Right after that hashcode is calculated the actual Equal-check is done. In your current case the hash-values returned by GetHashCode are different, thus Equals won´t be executed at all.

Linq sort first two numbers in long type

I want to sort a list of members with respect to the first two digits of the property Civic Number.
If the search year is 82, everyone who has a Civic Number beginning with 82 are left in the list returned.
Here is a method I have written returns a sorted list with respect to first letters in a name.
private static List<Member> GetNameList(string searchString)
{
return _sortMemberList.Where(x => x.FirstName.ToLower().StartsWith(searchString.ToLower())).ToList();
}
I want to write a simular method but with regards on Civic Number. The Civic Number is 10 digits long and is of the data type "long". I know this doesn't work but well, here is something:
private static List<Member> GetMonthList(int searchYear)
{
return _sortMemberList.OrderBy(x => x.CivicNumber > searchYear).ToList();
}
Best regards
Robert Jarlvik
The simplest, very inefficient approach would simply be
_sortMemberList.Where(x => x.CivicNumber.ToString().StartsWith(searchYear.ToString())).ToList()
Otherwise, you could do
_sortMemberList.Where(x => (x.CivicNumber/100000000) == searchYear ).ToList()
assuming you know that all numbers are indeed 10 positions and searchYear is always 2 digits
If the list is already sorted, you can make it more efficient:
_sortMemberList
.SkipWhile(x => x.CivicNumber < searchYear*100000000 )
.TakeWhile(x => (x.CivicNumber/100000000) == searchYear )
.ToList()
Even more efficient would be to use binary-search to locate the lowerbound of the target year, but that can only be done if _sortMemberList allows random access (indexing): see
Can LINQ use binary search when the collection is ordered?
or the List<T>.BinarySearch Method

Check if a list of integers increments by one

Is there a way, with LINQ, to check if a list of integers are "sequential" - ie 1,2,3,4,5 or 14,15,16,17,18?
You could do this via Enumerable.Zip:
bool sequential = values.Zip(values.Skip(1), (a,b) => (a+1) == b).All(x => x);
This works by taking each pair of values, and checking to see if the second is 1 more than the first, and returning booleans. If all pairs fit the criteria, the values are sequential.
Given that this is a list of integers, you can do this slightly more efficiently using:
bool sequential = values.Skip(1).Select((v,i) => v == (values[i]+1)).All(v => v);
This will only work on sequences which can be accessed by index. Note that we use values[i], not values[i-1], as the Skip call effectively shifts the indices.
bool isSequential = Enumerable.Range(values.Min(), values.Count())
.SequenceEqual(values);
One more option is to use Aggregate to iterate sequence only once.
Note that unlike All suggested by Reed Copsey Aggregate can't stop in the middle when condition fails...
var s = new int[] {3,4,5,6}.ToList();
var isSequential = s.Aggregate
(
new {PrevValue = 0, isFirst = true, Success = true} ,
(acc, current) =>
new {
PrevValue = current,
isFirst = false,
Success = acc.Success && (acc.isFirst || (acc.PrevValue == current - 1))
}
)
.Success;
Fancier version would be to have iterator that carries previous value along or special code that would split iterator on "First and the rest" allowing to implement Reed's solution with single iteration for any enumerable.
If you already know that the numbers you have in your list is unique, and also sorted, then the simplest check for sequential is just
lst[lst.Count - 1] - lst[0] == lst.Count - 1
Assume atleast 1 element in list.

How to match / connect / pair integers from a List <T>

I have a list, with even number of nodes (always even). My task is to "match" all the nodes in the least costly way.
So I could have listDegree(1,4,5,6), which represents all the odd-degree nodes in my graph. How can I pair the nodes in the listDegree, and save the least costly combination to a variable, say int totalCost.
Something like this, and I return the least totalCost amount.
totalCost = (1,4) + (5,6)
totalCost = (1,5) + (4,6)
totalCost = (1,6) + (4,5)
--------------- More details (or a rewriting of the upper) ---------------
I have a class, that read my input-file and store all the information I need, like the costMatrix for the graph, the edges, number of edges and nodes.
Next i have a DijkstrasShortestPath algorithm, which computes the shortest path in my graph (costMatrix) from a given start node to a given end node.
I also have a method that examines the graph (costMatrix) and store all the odd-degree nodes in a list.
So what I was looking for, was some hints to how I can pair all the odd-degree nodes in the least costly way (shortest path). To use the data I have is easy, when I know how to combine all the nodes in the list.
I dont need a solution, and this is not homework.
I just need a hint to know, when you have a list with lets say integers, how you can combine all the integers pairwise.
Hope this explenation is better... :D
Perhaps:
List<int> totalCosts = listDegree
.Select((num,index) => new{num,index})
.GroupBy(x => x.index / 2)
.Select(g => g.Sum(x => x.num))
.ToList();
Demo
Edit:
After you've edited your question i understand your requirement. You need a total-sum of all (pairwise) combinations of all elements in a list. I would use this combinatorics project which is quite efficient and informative.
var listDegree = new[] { 1, 4, 5, 6 };
int lowerIndex = 2;
var combinations = new Facet.Combinatorics.Combinations<int>(
listDegree,
lowerIndex,
Facet.Combinatorics.GenerateOption.WithoutRepetition
);
// get total costs overall
int totalCosts = combinations.Sum(c => c.Sum());
// get a List<List<int>> of all combination (the inner list count is 2=lowerIndex since you want pairs)
List<List<int>> allLists = combinations.Select(c => c.ToList()).ToList();
// output the result for demo purposes
foreach (IList<int> combis in combinations)
{
Console.WriteLine(String.Join(" ", combis));
}
(Without more details on the cost, I am going to assume cost(1,5) = 1-5, and you want the sum to get as closest as possible to 0.)
You are describing the even partition problem, which is NP-Complete.
The problem says: Given a list L, find two lists A,B such that sum(A) = sum(B) and #elements(A) = #elements(B), with each element from L must be in A or B (and never both).
The reduction to your problem is simple, each left element in the pair will go to A, and each right element in each pair will go to B.
Thus, there is no known polynomial solution to the problem, but you might want to try exponential exhaustive search approaches (search all possible pairs, there are Choose(2n,n) = (2n!)/(n!*n!) of those).
An alternative is pseudo-polynomial DP based solutions (feasible for small integers).

Categories

Resources