简体   繁体   中英

Codesnippet for sorting collection<T> based on a comma-separated string value

In my code I use a collection<T> as binding source for different controls (WPF/C#). The collection is created from the system every time the application loads. I don't have control on the collection and its order (items within the collection) are randomly for every launch of the app.

For UI reasons I need to allow to sort the collection, display it in a listview and keep the sorting when modified by the user (moveup and movedown buttons). Therefore my Idea was simple. I simply write the items comma-separated into a hidden string variable eg. "itemX,ItemY,ItemZ" .

Now I need a function that sorts the collection based on the string. I was thinking of a few foreach loops, but I am sure there is a better way of sorting the collection.

  • sort the Items within collection<t> in the same order as represented by the string.

     string correctItemOrder = "Application5, Application2, Application4, Application3". 

Collection<T> has items with just the property name (eg "Applicaton3" ) but is sorted randomly. I want to sort the collection in the same order as the string.

T is an interface an I can access a property "Name" that has the value that is stored in the string eg. "ItemX".

Any cool snippets/functions?

Thanks

A couple of ideas...

Either way, you'll want your "item order string" as an array, so...

var sortOrder = correctItemOrder.Split(new[] { ", " }, StringSplitOptions.None);

Then one option is to order your collection by the order of items in sortOrder (meaning you have to traverse through half of sortOrder, on average, for each element in your collection):

var sortedCollection = new Collection<T>(collection.OrderBy(x => Array.IndexOf(sortOrder, x.Name)).ToList());

Another option is to create a dictionary of Name => item, then traverse sortOrder, selecting items from this dictionary as you go...

var dict = collection.ToDictionary(x => x.Name);
var sortedCollection = new Collection<T>(sortOrder.Select(x => dict[x]).ToList());

It's worth noting that if new items are added to the collection, but not sortOrder, the first snippet will place them at the start of the collection, whereas the second one will discard them entirely. Similarly if items are present in sortOrder but not the collection, the first snippet will ignore them, whereas the second one will throw an exception.

EDIT:

The third option, of course, is to create dictionary from sortOrder, and use that.

var dict = sortOrder.Select((x, i) => new { x, i }).ToDictionary(x => x.x, x => x.i);
var sortedCollection = new Collection<T>(collection.OrderBy(x => dict[x.Name]).ToList());

EDIT2:

As Enigmativity has pointed out, using lookups instead of dictionaries allows you to handle the cases where dictionary keys are missing very neatly.

The last example using this technique:

var lookup = sortOrder.Select((x, i) => new {x, i}).ToLookup(x => x.x, x => x.i);
var sortedCollection = new Collection<T>(collection.OrderBy(x => lookup[x.Name].DefaultIfEmpty(Int32.MaxValue).First()).ToList());

I think a comparer like this should do the job:

public interface INamed {
   string Name {get;}
}

public class CustomComparer : Comparer<INamed> {

        Dictionary<string, int> hash;

        public CustomComparer( ) {
           var tokens = "Application5, Application2, Application4, Application3"
                        .Split( ',' )
                        .Select( s => s.Trim( ) )
                        .ToArray( );
           hash = Enumerable.Range(0, tokens.Length)
                            .ToDictionary( i => tokens[i]  );
        }

        public override int Compare( INamed x, INamed y ) {
            return hash[x.Name] - hash[y.Name];
        }

        public static readonly CustomComparer Default = new CustomComparer();
    }

EDIT: I see that Collection has not order by itself, so is needed to build a wrapper

  class SortableCollection<T> : System.Collections.ObjectModel.Collection<T>
  {
    public SortableCollection() : this(new List<T>()) {}
    public SortableCollection(List<T> list) : base(list) {}
    public virtual void Sort() { ((List<T>)Items).Sort(); }
  }

  class CustomSortableCollection<T> : SortableCollection<T> where T: INamed
  {
    public override void Sort() { 
       ((List<INamed>)Items).Sort(CustomComparer.Default); 
    }
  }

This way you can sort the colection when you need it doing:

     your_collection.Sort();

You could do this:

var rank =
    correctItemOrder
        .Split(',')
        .Select((x, n) => new { x = x.Trim(), n, })
        .ToLookup(z => z.x, z => z.x);

var query =
    from i in items
    orderby rank[i.Name]
        .DefaultIfEmpty(int.MaxValue)
        .First()
    select i;

This handles missing values in the correctItemOrder string too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM