?
Solved

Generic collection Sort doesn't work when there is training number in the data to sort.

Posted on 2014-12-23
4
Medium Priority
?
207 Views
Last Modified: 2014-12-24
Hi, I'm using VS2012.
I have a collection of List<UserPrincipal>.  Using the following code to sort based on the userid works until the userid ends with numbers larger than 9.  UserId1, UserId2.... UserId9, will sort correctly.  UserId10, UserId11...   will fall right er under UserId1.  UserId20, UserId21...  will fall under UserId2..


users.Sort(delegate(UserPrincipal a, UserPrincipal b) { return string.Compare(a.SamAccountName, b.SamAccountName); });

How can I fix this?  Thank you.
0
Comment
Question by:lapucca
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
4 Comments
 
LVL 63

Assisted Solution

by:Fernando Soto
Fernando Soto earned 400 total points
ID: 40515491
Numeric characters in a string do not sort as you may think. If you want them to sort like alpha characters zero fill the beginning numeric sequence. So lets say that the largest numeric value is 999 then use userID's like this:

UserId001
UserId002
...
UserId010
...
UserId060
..
UserId100

This pattern will handle digits from 000 to 999.
0
 
LVL 34

Accepted Solution

by:
it_saige earned 1600 total points
ID: 40515582
Just to add another option (although I see nothing wrong with Raheman's implementation): Natural Sort Comparer

Implementation -
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Text.RegularExpressions;

namespace EE_Q28585582
{
	class Program
	{
		private static List<UserPrincipal> users = new List<UserPrincipal>();

		static void Main(string[] args)
		{
			PrincipalContext context = new PrincipalContext(ContextType.Domain);
			UserPrincipal user = new UserPrincipal(context);
			PrincipalSearcher searcher = new PrincipalSearcher(user);
			foreach (Principal result in searcher.FindAll())
			{
				UserPrincipal upe = result as UserPrincipal;
				if (upe != null)
					users.Add(upe);
			}

			using (NaturalComparer comparer = new NaturalComparer())
				users.Sort((a, b) => comparer.Compare(a.SamAccountName, b.SamAccountName));

			foreach (UserPrincipal up in users)
				Console.WriteLine("User: {0}; SamAccount: {1}", up.Name, up.SamAccountName);

			Console.ReadLine();
		}
	}

	public class NaturalComparer : Comparer<string>, IDisposable
	{
		private Dictionary<string, string[]> table;

		public NaturalComparer()
		{
			table = new Dictionary<string, string[]>();
		}

		public void Dispose()
		{
			table.Clear();
			table = null;
		}

		public override int Compare(string x, string y)
		{
			if (x == y)
			{
				return 0;
			}
			string[] x1, y1;
			if (!table.TryGetValue(x, out x1))
			{
				x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
				table.Add(x, x1);
			}
			if (!table.TryGetValue(y, out y1))
			{
				y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
				table.Add(y, y1);
			}

			for (int i = 0; i < x1.Length && i < y1.Length; i++)
			{
				if (x1[i] != y1[i])
					return PartCompare(x1[i], y1[i]);
			}

			if (y1.Length > x1.Length)
				return 1;
			else if (x1.Length > y1.Length)
				return -1;
			else
				return 0;
		}

		private static int PartCompare(string left, string right)
		{
			int x, y;
			if (!int.TryParse(left, out x))
				return left.CompareTo(right);

			if (!int.TryParse(right, out y))
				return left.CompareTo(right);

			return x.CompareTo(y);
		}
	}
}

Open in new window


ADUC -Capture.JPG
Output -Capture.JPG
-saige-
0
 
LVL 34

Expert Comment

by:it_saige
ID: 40515610
Just for completeness and to test I implemented Raheman's recommendation:
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.Text.RegularExpressions;

namespace EE_Q28585582
{
	class Program
	{
		private static List<UserPrincipal> users = new List<UserPrincipal>();

		static void Main(string[] args)
		{
			PrincipalContext context = new PrincipalContext(ContextType.Domain);
			UserPrincipal user = new UserPrincipal(context);
			PrincipalSearcher searcher = new PrincipalSearcher(user);
			foreach (Principal result in searcher.FindAll())
			{
				UserPrincipal upe = result as UserPrincipal;
				if (upe != null && upe.Name.Contains("Natural"))
					users.Add(upe);
			}

			Console.WriteLine("Using Justin Jones' Natural Sort Comparer:");
			using (NaturalComparer comparer = new NaturalComparer())
				users.Sort((a, b) => comparer.Compare(a.SamAccountName, b.SamAccountName));

			foreach (UserPrincipal up in users)
				Console.WriteLine("User: {0}; SamAccount: {1}", up.Name, up.SamAccountName);

			users = new List<UserPrincipal>();
			foreach (Principal result in searcher.FindAll())
			{
				UserPrincipal upe = result as UserPrincipal;
				if (upe != null && upe.Name.Contains("Natural"))
					users.Add(upe);
			}

			Console.WriteLine();
			Console.WriteLine("Using Raheman's comparer:");
			users.Sort((a, b) => CompareUsers(a.SamAccountName, b.SamAccountName));
			foreach (UserPrincipal up in users)
				Console.WriteLine("User: {0}; SamAccount: {1}", up.Name, up.SamAccountName);

			Console.ReadLine();
		}

		public static int CompareUsers(string x, string y)
		{
			var regex = new Regex("^(d+)");

			// run the regex on both strings
			var xRegexResult = regex.Match(x);
			var yRegexResult = regex.Match(y);

			// check if they are both numbers
			if (xRegexResult.Success && yRegexResult.Success)
				return int.Parse(xRegexResult.Groups[1].Value).CompareTo(int.Parse(yRegexResult.Groups[1].Value));

			// otherwise return as string comparison
			return x.CompareTo(y);
		}
	}

	public class NaturalComparer : Comparer<string>, IDisposable
	{
		private Dictionary<string, string[]> table;

		public NaturalComparer()
		{
			table = new Dictionary<string, string[]>();
		}

		public void Dispose()
		{
			table.Clear();
			table = null;
		}

		public override int Compare(string x, string y)
		{
			if (x == y)
			{
				return 0;
			}
			string[] x1, y1;
			if (!table.TryGetValue(x, out x1))
			{
				x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
				table.Add(x, x1);
			}
			if (!table.TryGetValue(y, out y1))
			{
				y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
				table.Add(y, y1);
			}

			for (int i = 0; i < x1.Length && i < y1.Length; i++)
			{
				if (x1[i] != y1[i])
					return PartCompare(x1[i], y1[i]);
			}

			if (y1.Length > x1.Length)
				return 1;
			else if (x1.Length > y1.Length)
				return -1;
			else
				return 0;
		}

		private static int PartCompare(string left, string right)
		{
			int x, y;
			if (!int.TryParse(left, out x))
				return left.CompareTo(right);

			if (!int.TryParse(right, out y))
				return left.CompareTo(right);

			return x.CompareTo(y);
		}
	}
}

Open in new window


ADUC -Capture.JPG
Results -Capture.JPG
Seeing this, I would recommend Justin Jones' Natural Sort Comparer.

-saige-
0
 

Author Closing Comment

by:lapucca
ID: 40516946
Thank you both
0

Featured Post

Veeam Task Manager for Hyper-V

Task Manager for Hyper-V provides critical information that allows you to monitor Hyper-V performance by displaying real-time views of CPU and memory at the individual VM-level, so you can quickly identify which VMs are using host resources.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
Monitoring a network: how to monitor network services and why? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the philosophy behind service monitoring and why a handshake validation is critical in network monitoring. Software utilized …
In this video, Percona Director of Solution Engineering Jon Tobin discusses the function and features of Percona Server for MongoDB. How Percona can help Percona can help you determine if Percona Server for MongoDB is the right solution for …
Suggested Courses

777 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question