How to merge two sorting functions? - c#

I'm using the following class to sort the list by type member:
public class CameraSortByType : IComparer<Camera>
{
private bool asc;
public CameraSortByType(bool a)
{
this.asc = a;
}
public int Compare(Camera x, Camera y)
{
if (x.type > y.type)
return asc? -1 : 1;
if (x.type < y.type)
return asc? 1 : -1;
else
return 0;
}
}
I'm also sorting the same list by name:
myList.Sort((s1, s2) => s2.name.CompareTo(s1.name));
How can I incorporate sorting by name into sorting class by type? So when I sort by type it also sorts by name?
Update: Linq version
var primarySortResult = primarySort ? CameraStorage.CameraList.OrderBy(x => x.type) : CameraStorage.CameraList.OrderByDescending(x => x.type);
var secondarySortResult = secondarySort ? primarySortResult.ThenBy(x => x.name) : primarySortResult.ThenByDescending(x => x.name);
CameraStorage.CameraList = secondarySortResult.ToList();

If LINQ is an option you can use following operators to create ordered sequence:
OrderBy - sorts the elements of a sequence in ascending order
OrderByDescending - sorts in descending order
ThenBy - performs subsequent ordering of the elements in a sequence in ascending order
ThenByDescending- subsequent descending ordering
Sample:
var orderedList = myList.OrderBy(x => x.type)
.ThenByDescending(x => x.name)
.ToList();
If you need to order based on some conditions:
var result = ascendingByType ? myList.OrderBy(x => x.type) :
myList.OrderByDescending(x => x.type);
if (orderByNameRequired)
{
result = ascendingByName ? result.ThenBy(x => x.name) :
result.ThenByDescending(x => x.name);
}
orderedList = result.ToList();
Consider also using Dynamic Linq.
Also here is comparer implementation for sorting on two properties (consider also nulls handling here):
public class CameraComparer : IComparer<Camera>
{
private SortDirection typeSortDirection;
private SortDirection nameSortDirection;
public CameraComparer(SortDirection typeSortDirection,
SortDirection nameSortDirection)
{
this.typeSortDirection = typeSortDirection;
this.nameSortDirection = nameSortDirection;
}
public int Compare(Camera x, Camera y)
{
if (x.Type == y.Type)
return x.Name.CompareTo(y.Name) *
(nameSortDirection == SortDirection.Ascending ? 1 : -1);
return x.Type.CompareTo(y.Type) *
(typeSortDirection == SortDirection.Ascending ? 1 : -1);
}
}
public enum SortDirection
{
Ascending,
Descending
}
Usage:
myList.Sort(new CameraComparer(SortDirection.Ascending, SortDirection.Descending));

I use Tuples for cases like this:
public int Compare(Camera x, Camera y) {
var xx = Tuple.Create(x.name, x.type);
var yy = Tuple.Create(y.name, y.type);
return xx.CompareTo(yy);
}
That is, assuming that you want to compare all properties in the same order. If not, I think you could reverse the order in which a property (e.g. name) is considered by doing something like:
var xx = Tuple.Create(y.name, x.type);
var yy = Tuple.Create(x.name, y.type);
That is, putting it in the "wrong" tuple. But I haven't tested this part at all.

public int Compare(Camera x, Camera y)
{
if (x.type > y.type)
return asc ? -1 : 1;
if (x.type < y.type)
return asc ? 1 : -1;
if (x.name.CompareTo(y.name) > 0)
return asc ? -1 : 1;
if (x.name.CompareTo(y.name) < 0)
return asc ? 1 : -1;
return 0;
}

Related

Order a list of Sorted Set of Strings by Count and then by strings

I have a custom class called PairString
public class PairString: IComparer<PairString>
{
public string first;
public string second;
public PairString(string f, string s)
{
first = f;
second = s;
}
public int Compare([AllowNull] PairString x, [AllowNull] PairString y)
{
if (x == null || y == null) return -1;
var f = string.Compare(x.first, y.first);
var s = string.Compare(x.second, y.second);
return f == s ? s : f;
}
}
I want to create groups by count and then by lexical order of strings in that groups, from a list of input PairString List. Below method does the grouping right. But when I try to sort the groups in lexical order for equal count groups, it throws "Atleast one object must implement IComparer error"
public static List<string> MaxItemAssociatoinGroup(List<PairString> input)
{
if (input == null || input.Count == 0) return null;
List<SortedSet<string>> output = new List<SortedSet<string>>();
foreach (var item in input)
{
if (output.Any(x => x.Contains(item.first) || x.Contains(item.second)))
{
//Take the set containing one or two or both items
var set1 = output.FirstOrDefault(x => x.Contains(item.first));
var set2 = output.FirstOrDefault(x => x.Contains(item.second));
if (set1 == null)
set2.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set2 == null)
set1.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set1 != set2)
{
set1.UnionWith(set2);
output.Remove(set2);
}
}
else
output.Add(new SortedSet<string>(new List<string>() { item.first, item.second }));
}
var maxlistAssociation = output.OrderByDescending(x => x.Count).First();
return new List<string>(maxlistAssociation);
}
I am not sure how to achieve lexical order for same count groups,
Sample input is
new PairString("item3","item4"),
new PairString("item3","item6"),
new PairString("item5","item6"),
new PairString("item2","item8"),
new PairString("item8","item9"),
new PairString("item1","item2")
it groups into 2 groups of equal count {item3,item4,item5,item6} & {item1,item2,item8,item9} but returns {item3,item4,item5,item6} as its first in the list. but I want the second group as it contains the item that lexicographically first than first group. what am I missing here?
It appears that you're missing a method that will compare two SortedSet<string> objects and return the one which comes first lexically. One way to do this is to compare each item from one set with the corresponding one in the other set, and return the first non-equal comparison:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
var minCount = Math.Min(x.Count, y.Count);
// Compare each item from one set with the corresponding one in the other set
for (var i = 0; i < minCount; i++)
{
var result = x.ElementAt(i).CompareTo(y.ElementAt(i));
// Return the first non-equal result
if (result != 0) return result;
}
// If all the items were equal, return the comparison of the Count
return x.Count.CompareTo(y.Count);
}
}
Then we can order our results (after sorting by size) by passing an instance of this class to the ThenBy method:
var maxlistAssociation = output
.OrderByDescending(x => x.Count)
.ThenBy(x => x, new SortedSetComparer<string>())
.First();
Depending on the behavior you want from this method, we could also incorporate the ordering by Count into our comparison method, so that it puts the sets with the most items first, then sorts them alphabetically:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
// Compare the counts first, in descending order
var countComparison = x.Count.CompareTo(y.Count);
if (countComparison != 0) return countComparison * -1;
// Then compare each item from one set lecially
// with the corresponding one in the other set
return x.Select((item, index) =>
x.ElementAt(index).CompareTo(y.ElementAt(index)))
.FirstOrDefault(result => result != 0);
}
}
And now we only need one OrderBy clause:
var maxlistAssociation = output
.OrderBy(x => x, new SortedSetComparer<string>())
.First();

Comparing equality by list property

I am trying to find the number of items in a list that differ in a property which itself is a list. I found this example using Linq here:
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();
This works nicely if the property PersonId is scalar. But in my case this does not work and in the following the items in SelectedTargets are always returned as distinct even though the ListOfActions is equal in all items:
List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions).Select(g => g.First()).ToList();
If instead I pick the first item in ListOfActions it works:
List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions[0]).Select(g => g.First()).ToList();
So how can I check for equality of the whole list ListOfActions? (it doesn't necessarily have to user Linq)
The definition of SelectedTargets is:
List<Target> SelectedTargets = new List<Target>();
and is DispensingActionList:
private DispensingActionList ListOfActions = new DispensingActionList();
public class DispensingActionList : List<DispensingAction>
{ ...
You could use a custom IEqualityComparer<T> for the GroupBy overload which compares sequences. For example this which uses Enumerable.SequenceEqual:
public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
var comparer = EqualityComparer<T>.Default;
return x.SequenceEqual(y, comparer);
}
public int GetHashCode(IEnumerable<T> items)
{
unchecked
{
int hash = 17;
foreach (T item in items)
{
hash = hash * 23 + (item == null ? 0 : item.GetHashCode());
}
return hash;
}
}
}
Now this should work:
List<Target> distinctTargets = SelectedTargets
.GroupBy(p => p.ListOfActions, new SequenceComparer<DispensingAction>())
.Select(g => g.First())
.ToList();
Of course DispensingAction also needs to override Equals to compare the objects meaningfully and not only checks whether they're the same reference or not.
You could use Enumerable.SequenceEqual<TSource>
You will have to override the GetHashCode and Equals methods for your types if you didn't do so yet.

Find indexes in bool array changed from false to true?

I have two arrays:
bool[] oldValues = GetCurrentValuesFromSomewhere ();
ChangeCurrentValues ();
bool[] newValues = GetCurrentValuesFromSomewhere ();
List<int> whichIndexsHasBeenChangedFromFalseToTrue = /* linq */
Any idea? Instead of list, it can be bool[] array too.
You could do use something like this:
var changedValues =
(from i in Enumerable.Range(0, oldValues.Length)
where !oldValues[i] && newValues[i]
select i)
.ToList();
Or if you prefer fluent syntax:
var changedValues = Enumerable
.Range(0, oldValues.Length)
.Where(i => !oldValues[i] && newValues[i])
.ToList();
If you wanted a bool[] result, you can use this:
var changedValues =
(from i in Enumerable.Range(0, oldValues.Length)
select !oldValues[i] && newValues[i])
.ToArray();
Or in fluent syntax:
var changedValues = Enumerable
.Range(0, oldValues.Length)
.Select(i => !oldValues[i] && newValues[i])
.ToArray();
I would prefer using the lambda that gives you the index, so you do not have to generate the range:
var changed = newValues.
Select((value, index) => oldValues[index] == value ? -1 : index).
Where(i => i >= 0);
This should return a list of the indexes that have changed; .Count() will give you how many values have changed.
UPDATE: An alternative version
var changed = newValues.
Select((value, index) =>
value ? (oldValues[index] ? 0 : index + 1) : (oldValues[index] ? - (index + 1) : 0)).
Where(i => i != 0);
Will give you as index+1 those values that were false and are now true, and as -(index + 1) those values that were true and now are false. I am learning LINQ myself so I like to play with it quite a bit.
If there are always the same number of new and old, and you're just doing a diff, which is what you seem to be doing, I'd do something like this:
int index;
whichIndexsHasBeenChangedFromFalseToTrue = oldValues.Zip(newValues, (old, new) =>
{
int result = -1;
if(old != new) result = index;
index++;
return result;
}).Where(x => x != -1);
This is only for changed, but if you specifically want false to true, that's just a change to the if.
EDIT: Fixed a serious issue.

Linq - Order by number then letters

I have a list of strings, and these strings contain numbers and words.
What I wanted to do is order it by the numbers (numeric order) followed by the words (alphabetical order)
My list does not contain a mix of the two... here is an example
1, 5, 500 , LT, RT, 400 -> LINQ -> 1,
5, 400, 500, LT, RT
Here is a example of what I have, it works but I was wondering if there is a better way of writing it?
int results = 0;
// Grabs all voltages
var voltage = ActiveRecordLinq.AsQueryable<Equipment>()
.OrderBy(x => x.Voltage)
.Select(x => x.Voltage)
.Distinct()
.ToList();
// Order by numeric
var numberVoltage = voltage
.Where( x => int.TryParse(x, out results))
.OrderBy( x => Convert.ToInt32(x));
// Then by alpha
var letterVoltage = voltage
.Where(x=> !String.IsNullOrEmpty(x))
.Where(x => !int.TryParse(x, out results))
.OrderBy(x => x);
return numberVoltage.Union(letterVoltage)
Thanks for the help!
Given that you're doing it all in-process (as you've got a ToList call) I think I'd just use a custom comparer:
return ActiveRecordLinq.AsQueryable<Equipment>()
.Select(x => x.Voltage)
.Distinct()
.AsEnumerable() // Do the rest in-process
.Where(x => !string.IsNullOrEmpty(x))
.OrderBy(x => x, new AlphaNumericComparer())
.ToList();
Where AlphaNumericComparer implements IComparer<string>, something like this:
public int Compare(string first, string second)
{
// For simplicity, let's assume neither is null :)
int firstNumber, secondNumber;
bool firstIsNumber = int.TryParse(first, out firstNumber);
bool secondIsNumber = int.TryParse(second, out secondNumber);
if (firstIsNumber)
{
// If they're both numbers, compare them; otherwise first comes first
return secondIsNumber ? firstNumber.CompareTo(secondNumber) : -1;
}
// If second is a number, that should come first; otherwise compare
// as strings
return secondIsNumber ? 1 : first.CompareTo(second);
}
You could use a giant conditional for the latter part:
public int Compare(string first, string second)
{
// For simplicity, let's assume neither is null :)
int firstNumber, secondNumber;
bool firstIsNumber = int.TryParse(first, out firstNumber);
bool secondIsNumber = int.TryParse(second, out secondNumber);
return firstIsNumber
? secondIsNumber ? firstNumber.CompareTo(secondNumber) : -1;
: secondIsNumber ? 1 : first.CompareTo(second);
}
... but in this case I don't think I would :)
This solution attempts parsing once for each value.
List<string> voltage = new List<string>() { "1", "5", "500" , "LT", "RT", "400" };
List<string> result = voltage
.OrderBy(s =>
{
int i = 0;
return int.TryParse(s, out i) ? i : int.MaxValue;
})
.ThenBy(s => s)
.ToList();

How to get the maximum item ordered by two fields using a Linq expression?

Is it possible to get result1 as a single linq expression? I understand that it may not be the best practise but I would just like to know how to do so out of curiousity.
result2 has a different answer but it correct too. However, it has a complexity of O(NlogN) as opposed to O(N).
void Main()
{
A[] a = new A[4]{new A(0,0,0),new A(1,1,0),new A(1,2,1),new A(1,2,0)};
/*
//Grossly inefficient: replaced
var tmpList = a.Where(x => (x.one == a.Max(y => y.one)));
var result1 = tmpList.First(x => (x.two == tmpList.Max(y => y.two)));
*/
var maxOneValue = a.Max(x => x.one);
var tmpList = a.Where(x => (x.one == maxOneValue));
var maxTwoValueOfTmpList = tmpList.Max(x => x.two);
var result1 = tmpList.First(x => (x.two == maxTwoValueOfTmpList));
//A: 1, 2, 1
var result2 = a.OrderBy(x => x.one)
.ThenBy(x => x.two)
.Last();
//A: 1, 2, 0
}
class A
{
public int one;
public int two;
public int three;
public A(int one, int two, int three)
{
this.one = one;
this.two = two;
this.three = three;
}
}
edit: I have edited by question and hence some answers may not tally.
This query gives the same result :
var result = a.OrderByDescending(x => x.one + x.two)
.First();
But then you could get items without max 'one' field..
This one should work :
var result = a.OrderByDescending(x => x.two)
.Where(x => (x.one == a.Max(y => y.one)))
.First();
maybe this solves your problem:
a.OrderBy(x => x.one + x.two).Last()
One way to do it is to implement IComparable<A> on your A class. Then your solution simply becomes:
var result1 = a.Max(); // 1,2,1
Here's how you would implement IComparable<A>:
class A : IComparable<A>
{
...
public int CompareTo(A other)
{
return this.one == other.one ? this.two - other.two : this.one - other.one;
}
}
Here is a demo: http://ideone.com/ufIcgf. The nice thing about this is that it still has a complexity of O(N), and is also fairly concise.

Categories

Resources