Sort objects by string propery, empty string last - c#

I have an array of objects which all contain string property. I want to sort objects by string property alphabetically in a way that objects with empty string property come at the end of the list. Currently I have this:
switches = switches.OrderBy(n => n.GetCurrentUser()).ToArray();
The problem is that it puts empty strings at the top of the list. How do I put objects with strings with value (sorted alphabetically) at the top and objects with empty strings at the bottom?

You can use:
switches = switches
.Select(n => new { TheObject = n, User = n.GetCurrentUser() })
.OrderBy(x => String.IsNullOrEmpty(x.User) ? 1 : 0)
.ThenBy(x => x.User)
.Select(x => x.TheObject)
.ToArray();
This will first build two groups, the one with empty user and others. OrderBy will move them to the end because 1 is more than 0. If you want them at the top use OrderByDescending.
Then i use ThenBy to sort alphabetically which will only matter for the non-empty users.

You can also use inline Comparer creation:
switches.OrderBy(n => n.GetCurrentUser(),
Comparer<string>.Create((a, b) =>
string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b)? 1
: !string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b) ? -1
: string.Compare(a, b)));

OrderBy has an overload that accepts an IComparer<>T. This allows you to define your own sorting rules. You can start with the generic Comparer class and override the Compare method, eg :
public class EmptyLastComparer: Comparer<string>
{
public override int Compare(string x, string y)
{
if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
{
return 1;
}
else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return 0;
}
else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return -1;
}
else
{
return x.CompareTo(y);
}
}
}
To use it, creatre a new EmptyLastComparer() instance and pass it to OrderBy :
var myStrings = new[] { "c", "A","a", "A","b", " "," ",null };
var ordered=myStrings.OrderBy(x => x, new EmptyLastComparer());
String comparison is more complex than just comparing two strings. String.Compare has overloads that allow case-insensitive comparisons, using specific cultures etc. The custom comparer could accepts a StringComparison parameter in its constructor to allow something similar, eg :
public class EmptyLastComparer : Comparer<string>
{
private readonly StringComparison _comparison;
public EmptyLastComparer(StringComparison comparison=StringComparison.CurrentCulture)
{
_comparison = comparison;
}
public override int Compare(string x, string y)
{
if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
{
return 1;
}
else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return 0;
}
else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
{
return -1;
}
else
{
return String.Compare(x,y, _comparison);
}
}
}
Perhaps even add some predefined comparers, just like StringComparer does :
public static EmptyLastComparer CurrentCulture =>
new EmptyLastComparer();
public static EmptyLastComparer CurrentCultureIgnoreCase =>
new EmptyLastComparer(StringComparison.CurrentCultureIgnoreCase);
public static EmptyLastComparer InvariantCulture =>
new EmptyLastComparer(StringComparison.InvariantCulture);
public static EmptyLastComparer InvariantCultureIgnoreCase =>
new EmptyLastComparer(StringComparison.InvariantCultureIgnoreCase);
public static EmptyLastComparer Ordinal =>
new EmptyLastComparer(StringComparison.Ordinal);
public static EmptyLastComparer OrdinalIgnoreCase =>
new EmptyLastComparer(StringComparison.OrdinalIgnoreCase);
And use them the same way, without allocating a new comparer each time :
var ordered=myStrings.OrderBy(x => x, EmptyLastComparer.InvariantCultureIgnoreCase);

Related

How can I use OrderBy() function in c# allowing several element position formattings?

In my wpf application I have a listview containing elements where I want to
be able to write position attributes following formats: 1,2,3; 1a, 1b, 1c; 1)a), 1)b), 1)c); 1.1, 1.2, 1.3;
These position attributes are of type string and I want to order them automatically by size and substeps.
I tried this:
public class ClassXY
{
public string Position;
}
ObservableCollection<ClassXY> _myCollection = new ...;
_myCollection.OrderBy(p => p.Position);
_myCollection.OrderBy(p => Convert.ToDouble(p.Position));
Of course this didn't work for me, substeps in any format are always added at the end. Is there a way to to this without completely doing it on my own?
Besides needing ThenBy, you also need to remember that OrderBy isn't in place but returns an ordered IEnumerable
public class ClassXY
{
public string Position;
}
ObservableCollection<ClassXY> _myCollection = new ...;
var orderedCollection =
myCollection.OrderBy(p => p.Position)
.ThenBy(p => Convert.ToDouble(p.Position));
Just as an FYI, OrderBy and ThenBy also come as OrderByDescending and ThenByDescending so you don't have to negate a condition and sacrifice readability for simple descending ordering.
If anyone is interesed how I solved the problem:
I used the solution found here
How do I sort strings alphabetically while accounting for value when a string is numeric?
It worked fine for my purpose.
ObservableCollection<ClassXY> _myCollection = new ...;
_myCollection.OrderBy(p => p.Position, new SemiNumericComparer());
public class SemiNumericComparer : IComparer<string>
{
public int Compare(string s1, string s2)
{
if (IsNumeric(s1) && IsNumeric(s2))
{
if (Convert.ToDouble(s1) > Convert.ToDouble(s2)) return 1;
if (Convert.ToDouble(s1) < Convert.ToDouble(s2)) return -1;
if (Convert.ToDouble(s1) == Convert.ToDouble(s2)) return 0;
}
if (IsNumeric(s1) && !IsNumeric(s2)) { return -1; }
if (!IsNumeric(s1) && IsNumeric(s2)) { return 1; }
return string.Compare(s1, s2, true);
}
public static bool IsNumeric(object value)
{
try
{
var i = Convert.ToDouble(value.ToString());
return true;
}
catch (FormatException)
{
return false;
}
}
}

Intersection of List of List

I have a list of lists which looks like the following
public class FilteredVM
{
public int ID { get; set; }
public string Name { get; set; }
public string Number { get; set; }
}
List<List<FilteredVM>> groupedExpressionResults = new List<List<FilteredVM>>();
I would like to Intersect the lists within this list based upon the ID's, whats the best way to tackle this?
Here's an optimized extension method:
public static HashSet<T> IntersectAll<T>(this IEnumerable<IEnumerable<T>> series, IEqualityComparer<T> equalityComparer = null)
{
if (series == null)
throw new ArgumentNullException("series");
HashSet<T> set = null;
foreach (var values in series)
{
if (set == null)
set = new HashSet<T>(values, equalityComparer ?? EqualityComparer<T>.Default);
else
set.IntersectWith(values);
}
return set ?? new HashSet<T>();
}
Use this with the following comparer:
public class FilteredVMComparer : IEqualityComparer<FilteredVM>
{
public static readonly FilteredVMComparer Instance = new FilteredVMComparer();
private FilteredVMComparer()
{
}
public bool Equals(FilteredVM x, FilteredVM y)
{
return x.ID == y.ID;
}
public int GetHashCode(FilteredVM obj)
{
return obj.ID;
}
}
Like that:
series.IntersectAll(FilteredVMComparer.Instance)
You could just write
series.Aggregate((a, b) => a.Intersect(b, FilteredVMComparer.Instance))
but it 'd be wasteful because it'd have to construct multiple sets.
Intersect will work when the type are dead equals, which in your case won't apply because you haven't implemented the GetHashCode and Equals methods, which is the best and complete way.
Thus, If you only intended to take elements that contains in both lists, than the following solution will suit you right.
Assuming list1 and list2 are type List<FilteredVM> than, The most simple way, will be doing this:
var intersectByIDs = list1.Where(elem => list2.Any(elem2 => elem2.ID == elem.ID));
If you are a fan of one-liner solutions you can use this:
List<FilteredVM> result = groupedExpressionResults.Aggregate((x, y) => x.Where(xi => y.Select(yi => yi.ID).Contains(xi.ID)).ToList());
And if you just want the IDs you can just add .Select(x => x.ID), like this:
var ids = groupedExpressionResults.Aggregate((x, y) => x.Where(xi => y.Select(yi => yi.ID).Contains(xi.ID)).ToList()).Select(x => x.ID);
Working Demo

Converting LINQ (OrderBy/ThenBy) Code to CompareTo

I have written a generic sort funciton to sort list and dicitonary. But LINQ doesnt works on Unity due to JIT errors. I want to have the same generics and convert it into myList.Sort() which uses CompraeTo. But Im unable to figure out how to accomplish this as generic as this.
public static List<T> MySort<T>(this List<T> source, Type typeOfObject, bool isAscending = false, params string[] param)
{
if(param.Length == 0)
return source;
if (isAscending)
{
var temp = source.OrderBy (a => (typeOfObject.GetProperty (param [0])).GetValue (a, null));
for (int i=1; i<param.Length; i++)
{
var myVar = i;
temp = temp.ThenBy((a => (typeOfObject.GetProperty(param[myVar])).GetValue (a, null)));
}
return temp.ToList();
}
else
{
var temp = source.OrderByDescending (a => (typeOfObject.GetProperty (param [0])).GetValue (a, null));
for (int i=1; i<param.Length; i++)
{
var myVar = i;
temp.ThenByDescending((a => (typeOfObject.GetProperty(param[myVar])).GetValue (a, null)));
}
return temp.ToList();
}
}
USage of this function
RealEstateItems.MySort(typeof(mIsoObjectExt), true, "UnlockLevel", "Coins", "Diamonds");
My current CompareTo Approac
myList.Sort ((a,b) => {
int result = ((a.Value) as mIsoObjectExt).UnlockLevel.CompareTo(((b.Value) as mIsoObjectExt).UnlockLevel);
// result == 0 ? result = a.Value.Coins.CompareTo(a.Value.Coins);
if(result == 0)
{
result = ((a.Value) as mIsoObjectExt).Coins.CompareTo(((b.Value) as mIsoObjectExt).Coins);
}
else
{
return result;
}
if(result == 0)
{
return ((a.Value) as mIsoObjectExt).Diamonds.CompareTo(((b.Value) as mIsoObjectExt).Diamonds);
}
return result;
});
But Im not satisfied with this i have to do this every time i have to sort even on the same properties. Basically i want to make something like above that i tell the function the type its properties to sort on and it sorts. How can i do this with Compare/CompareTo?
So we're going to need a few different building blocks to begin with. First off, what you're really doing here is sorting each item on a collection of values, as is seen in this other question. We can pull the solution from there to have a comparer for sorting items based on a collection of values:
public class SequenceComparer<T> : IComparer<IEnumerable<T>>
{
private IComparer<T> comparer;
public SequenceComparer(IComparer<T> compareer = null)
{
this.comparer = comparer ?? Comparer<T>.Default;
}
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
using (var first = x.GetEnumerator())
using (var second = x.GetEnumerator())
{
while (true)
{
var firstHasMore = first.MoveNext();
var secondHasMore = second.MoveNext();
if (!firstHasMore && !secondHasMore)
return 0;
var lengthComparison = firstHasMore.CompareTo(secondHasMore);
if (lengthComparison != 0)
return lengthComparison;
var nextComparison = comparer.Compare(first.Current, second.Current);
if (nextComparison != 0)
return nextComparison;
}
}
}
}
We also want a way of creating a Comparison<T> delegate (which List.Sort accepts) from a projection delegate. This method is simple enough to write:
public static Comparison<T> CreateComparison<T, TKey>(Func<T, TKey> selector,
IComparer<TKey> comparer = null)
{
comparer = comparer ?? Comparer<TKey>.Default;
return (a, b) => comparer.Compare(selector(a), selector(b));
}
It'll also be useful for us to be able to reverse a Comparison<T> (to handle descending ordering):
public static Comparison<T> Reverse<T>(this Comparison<T> comparison)
{
return (a, b) => comparison(b, a);
}
Now to pull all of the pieces together. We can create a comparison that, for the projection, projects each item into a sequence of values that represent fetching each of the property names from the item using reflection. We can then reverse the comparer if we need a descending sort.
public static void MySort<T>(this List<T> source,
bool isAscending = false,
params string[] properties)
{
var type = typeof(T);
var comparison = CreateComparison((T item) =>
properties.Select(prop => type.GetProperty(prop).GetValue(item)),
new SequenceComparer<object>());
if (!isAscending)
comparison = comparison.Reverse();
source.Sort(comparison);
}
Note that if you can also use the sequence comparer to simplify the LINQ approach:
public static IEnumerable<T> MyOrdering<T>(this IEnumerable<T> source,
bool isAscending = false,
params string[] properties)
{
var type = typeof(T);
Func<T, IEnumerable<object>> selector = item =>
properties.Select(prop => type.GetProperty(prop).GetValue(item))
.ToList();
if (isAscending)
return source.OrderBy(selector, new SequenceComparer<object>());
else
return source.OrderByDescending(selector, new SequenceComparer<object>());
}
You can use Servy's approach with reflection. If you decide against reflection, you can use the below approach, but it still needs the comparison to be provided from the caller.
public class MultiValueComparer<T> : IComparer<T>
{
private IEnumerable<Comparison<T>> _comparisons;
public MultiValueComparer(IEnumerable<Comparison<T>> comparisons)
{
_comparisons = comparisons;
}
public int Compare(T x, T y)
{
foreach (var comparison in _comparisons)
{
var result = comparison(x, y);
if (result != 0)
return result;
}
return 0;
}
}
An extension method which takes a variable number of parameters
public static void Sort<T>(List<T> source, params Comparison<T>[] comparisons)
{
if (comparisons.Count() == 0)
return;
source.Sort(new MultiValueComparer<T>(comparisons));
}
Usage:
Ascending Order:
Sort(samples, (x, y) => x.Name.CompareTo(y.Name), (x, y) => x.Test.CompareTo(y.Test));
Descending Order:
Sort(samples, (x, y) => y.Name.CompareTo(x.Name), (x, y) => y.Test.CompareTo(x.Test));

Grouping by IEnumerable<string> does not work at all

I'm not really sure, why grouping by IEnumerable<string> does not work. I provide custom IEqualityComparer, of course.
public class StringCollectionEqualityComparer : EqualityComparer<IEnumerable<string>>
{
public override bool Equals(IEnumerable<string> x, IEnumerable<string> y)
{
if (Object.Equals(x, y) == true)
return true;
if (x == null) return y == null;
if (y == null) return x == null;
return x.SequenceEqual(y, StringComparer.OrdinalIgnoreCase);
}
public override int GetHashCode(IEnumerable<string> obj)
{
return obj.OrderBy(value => value, StringComparer.OrdinalIgnoreCase).Aggregate(0, (hashCode, value) => value == null ? hashCode : hashCode ^ value.GetHashCode() + 33);
}
}
class A
{
public IEnumerable<string> StringCollection { get; set; }
}
IEnumerable<A> collection = // collection of A
var grouping = collection.GroupBy(obj => a.StringCollection, StringCollectionEqualityComparer.Default).ToList();
(ToList() is to force evaluation, I have breakpoints in StringCollectionEqualityComparer, but unfortunately, they're not invoked, as expected)
When I group collection in this dumb way, it actually works.
var grouping = collection.GroupBy(obj => String.Join("|", obj.StringCollection));
Unfortunately, obviously it is not something I want to use.
By not working, I mean the results are not the ones I expect (using dumb way, the results are correct).
StringCollectionEqualityComparer.Default is a valid alternative way to access EqualityComparer<IEnumerable<string>>.Default, since the latter is a base class of the former. You need to create an instance of StringCollectionEqualityComparer, simply using new StringCollectionEqualityComparer(), instead.

How to remove duplicate combinations from a List<string> using LINQ

I'm having a List of String like
List<string> MyList = new List<string>
{
"A-B",
"B-A",
"C-D",
"C-E",
"D-C",
"D-E",
"E-C",
"E-D",
"F-G",
"G-F"
};
I need to remove duplicate from the List i.e, if "A-B" and "B-A" exist then i need to keep only "A-B" (First entry)
So the result will be like
"A-B"
"C-D"
"C-E"
"D-E"
"F-G"
Is there any way to do this using LINQ?
Implement IEqualityComparer witch returns true on Equals("A-B", "B-A"). And use Enumerable.Distinct method
This returns the sequence you look for:
var result = MyList
.Select(s => s.Split('-').OrderBy(s1 => s1))
.Select(a => string.Join("-", a.ToArray()))
.Distinct();
foreach (var str in result)
{
Console.WriteLine(str);
}
In short: split each string on the - character into two-element arrays. Sort each array, and join them back together. Then you can simply use Distinct to get the unique values.
Update: when thinking a bit more, I realized that you can easily remove one of the Select calls:
var result = MyList
.Select(s => string.Join("-", s.Split('-').OrderBy(s1 => s1).ToArray()))
.Distinct();
Disclaimer: this solution will always keep the value "A-B" over "B-A", regardless of the order in which the appear in the original sequence.
You can use the Enumerable.Distinct(IEnumerable<TSource>, IEqualityComparer<TSource>) overload.
Now you just need to implement IEqualityComparer. Here's something for you to get started:
class Comparer : IEqualityComparer<String>
{
public bool Equals(String s1, String s2)
{
// will need to test for nullity
return Reverse(s1).Equals(s2);
}
public int GetHashCode(String s)
{
// will have to implement this
}
}
For a Reverse() implementation, see this question
You need to implement the IEqualityComparer like this:
public class CharComparer : IEqualityComparer<string>
{
#region IEqualityComparer<string> Members
public bool Equals(string x, string y)
{
if (x == y)
return true;
if (x.Length == 3 && y.Length == 3)
{
if (x[2] == y[0] && x[0] == y[2])
return true;
if (x[0] == y[2] && x[2] == y[0])
return true;
}
return false;
}
public int GetHashCode(string obj)
{
// return 0 to force the Equals to fire (otherwise it won't...!)
return 0;
}
#endregion
}
The sample program:
class Program
{
static void Main(string[] args)
{
List<string> MyList = new List<string>
{
"A-B",
"B-A",
"C-D",
"C-E",
"D-C",
"D-E",
"E-C",
"E-D",
"F-G",
"G-F"
};
var distinct = MyList.Distinct(new CharComparer());
foreach (string s in distinct)
Console.WriteLine(s);
Console.ReadLine();
}
}
The result:
"A-B"
"C-D"
"C-E"
"D-E"
"F-G"
Very basic, but could be written better (but it's just working):
class Comparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return (x[0] == y[0] && x[2] == y[2]) || (x[0] == y[2] && x[2] == y[0]);
}
public int GetHashCode(string obj)
{
return 0;
}
}
var MyList = new List<String>
{
"A-B",
"B-A",
"C-D",
"C-E",
"D-C",
"D-E",
"E-C",
"E-D",
"F-G",
"G-F"
}
.Distinct(new Comparer());
foreach (var s in MyList)
{
Console.WriteLine(s);
}
int checkID = 0;
while (checkID < MyList.Count)
{
string szCheckItem = MyList[checkID];
string []Pairs = szCheckItem.Split("-".ToCharArray());
string szInvertItem = Pairs[1] + "-" + Pairs[0];
int i=checkID+1;
while (i < MyList.Count)
{
if((MyList[i] == szCheckItem) || (MyList[i] == szInvertItem))
{
MyList.RemoveAt(i);
continue;
}
i++;
}
checkID++;
}

Categories