Sorting List of Strings - c#

I'm having a List<String> l_lstTemp and which contains
"A1"
"A1_1"
"A1_2"
"1A"
"B2_1"
"B1_2"
"B1_1_2"
"A10"
"B11"
"A"
"Z"
I need to sort the items based on the character and numeric value.
So the sorted list will be like
"1A"
"A"
"A1"
"A1_1"
"A1_2"
"A10"
"B1_1_2"
"B1_2"
"B2_1"
"B11"
"Z"
Here is my code:
l_lstTemp.Sort(delegate(String One, String Two)
{
Match l_mOne = Regex.Match(One, #"(\D*)(\d*)");
Match l_mTwo = Regex.Match(Two, #"(\D*)(\d*)");
int Result;
if (l_mOne.Success || l_mTwo.Success)
{
String l_strX, l_strY;
l_strX = l_mOne.Groups[1].Value;
l_strY = l_mTwo.Groups[1].Value;
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
l_strX = l_mOne.Groups[2].Value;
l_strY = l_mTwo.Groups[2].Value;
if (l_strX == String.Empty || l_strY == String.Empty)
{
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
}
else
{
long X = long.Parse(l_strX);
long Y = long.Parse(l_strY);
Result = X.CompareTo(Y);
if (Result != 0)
return Result;
}
}
return 0 ;
}
);
But its not working (sorting) properly.
How do I modify my code to sort the list properly?
Please post me a way to do this.
Thanks in advance.

I did some modifications to your code. The thing was that when both Group 1 and Group 2 are equals, you still need to check what remains.
Important: I did the modifications inside your code, so this could be a little tricky. I really suggest you refactoring your code now that you know it works:
l.Sort(delegate(String One, String Two)
{
while (One != "" && Two != "")
{
if (One == Two)
return 0;
//Add one more group to capture what remains of the expression
Match l_mOne = Regex.Match(One, #"_*(\D*)(\d*)(.*)$");
Match l_mTwo = Regex.Match(Two, #"_*(\D*)(\d*)(.*)$");
int Result;
if (l_mOne.Success || l_mTwo.Success)
{
String l_strX, l_strY;
l_strX = l_mOne.Groups[1].Value;
l_strY = l_mTwo.Groups[1].Value;
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
l_strX = l_mOne.Groups[2].Value;
l_strY = l_mTwo.Groups[2].Value;
if (l_strX == String.Empty || l_strY == String.Empty)
{
Result = l_strX.CompareTo(l_strY);
if (Result != 0)
return Result;
}
else
{
long X = long.Parse(l_strX);
long Y = long.Parse(l_strY);
Result = X.CompareTo(Y);
if (Result != 0)
return Result;
One = l_mOne.Groups[3].Value; //Store in 'One' the remaining part of the regex
Two = l_mTwo.Groups[3].Value; //The same in Two
continue; //The result will be the result of the comparison of those two new values.
}
}
}
return One.CompareTo(Two);
});
Edit:
I also added _* to remove all the _ characters from the begining of the strings. I assumed here that the strings will only contain _ after the numbers and not something like B1B or B1$.
The thing here is that you don't really explain how the comparison should be made, and I had to assume those things from your original data and the sorted data, otherwise what would happen if you want to sort A1A and A1_? What should it return?

Here's how I would implement such a comparer. Much easier to follow IMHO.
var re = new Regex(#"^(\d+)?([A-Z]+)(\d+)?(?:_(\d+)(?:_(\d+))?)?$");
Func<Group, int, int> intOrDefault = (g, d) => g.Success ? Convert.ToInt32(g.Value) : d;
list.Sort((x, y) =>
{
var xm = re.Match(x);
var ym = re.Match(y);
int cmp;
// compare the first group
// compare the leading numbers (if any)
cmp = intOrDefault(xm.Groups[1], int.MaxValue).CompareTo(intOrDefault(ym.Groups[1], int.MaxValue));
if (cmp != 0)
return cmp;
// compare letters
cmp = xm.Groups[2].Value.CompareTo(ym.Groups[2].Value);
if (cmp != 0)
return cmp;
// compare the trailing numbers (if any)
cmp = intOrDefault(xm.Groups[3], 0).CompareTo(intOrDefault(ym.Groups[3], 0));
if (cmp != 0)
return cmp;
// compare the next group
cmp = intOrDefault(xm.Groups[4], 0).CompareTo(intOrDefault(ym.Groups[4], 0));
if (cmp != 0)
return cmp;
// compare the last group
cmp = intOrDefault(xm.Groups[5], 0).CompareTo(intOrDefault(ym.Groups[5], 0));
return cmp;
});

For this example, just calling sort l_lstTemp.Sort() would get you the result you are looking for

Related

How should NULL properties be handled in a CompareTo method?

I want to compare 2 instances of a class to see if they are equal.
I created this method that is faulty:
public int CompareTo(AdminEngineTableRowModel other)
{
int result;
if (other != null)
{
result = NXSTDT.CompareTo(other.NXSTDT);
if (result == 0)
result = DALOC.CompareTo(other.DALOC);
if (result == 0)
result = DealerID.CompareTo(other.DealerID);
if (result == 0)
result = Status.CompareTo(other.Status);
if (result == 0)
result = OrderNumber.CompareTo(other.OrderNumber);
if (result == 0)
result = Dealership.CompareTo(other.Dealership);
if (result == 0)
result = Serial.CompareTo(other.Serial);
if (result == 0)
result = Model.CompareTo(other.Model);
if (result == 0)
result = OrderDate.CompareTo(other.OrderDate);
if (result == 0)
result = StartDate.CompareTo(other.StartDate);
if (result == 0)
result = Confirmed.CompareTo(other.Confirmed);
if (result == 0)
result = Deposit.CompareTo(other.Deposit);
if (result == 0)
result = Engine.CompareTo(other.Engine);
if (result == 0)
result = Color.CompareTo(other.Color);
if (result == 0)
result = Name.CompareTo(other.Name);
}
else
{
result = 1;
}
return result;
}
The problem comes when any of the properties, like the first string NXSTDT is NULL:
result = NULL.CompareTo(other.NXSTDT); // This causes an error
I could create a class ctor constructor to initialize all of the properties, or I could edit the CompareTo method to check that each property is not null before I test it:
public int CompareTo(AdminEngineTableRowModel other)
{
int result;
if (other != null)
{
if ((NXSTDT == null) && (other.NXSTDT != null))
result = 1;
if (result == 0)
result = NXSTDT.CompareTo(other.NXSTDT);
if ((DALOC == null) && (other.DALOC != null))
result = 1;
if (result == 0)
result = DALOC.CompareTo(other.DALOC);
// {snip}
}
else
{
result = 1;
}
return result;
}
That just seems like poor programming.
Is there a better way to do this?
One idea would be to write a generic Compare<T> method that works will any object that implements IComparable<T>. Then you can write the null-handling once and not worry about it again. This does assume that the types you're comparing implement that interface.
public static int Compare<T>(T first, T second) where T : IComparable<T>
{
if (ReferenceEquals(first, second)) return 0;
if (first == null) return -1;
return first.CompareTo(second);
}
Then your code would look like:
public int CompareTo(AdminEngineTableRowModel other)
{
if (ReferenceEquals(this, other)) return 0;
if (other == null) return 1;
int result = Compare(NXSTDT, other.NXSTDT);
if (result != 0) return result;
result = Compare(DALOC, other.DALOC);
if (result != 0) return result;
result = Compare(DealerID, other.DealerID);
if (result != 0) return result;
result = Compare(Status, other.Status);
if (result != 0) return result;
result = Compare(OrderNumber, other.OrderNumber);
if (result != 0) return result;
result = Compare(Dealership, other.Dealership);
if (result != 0) return result;
result = Compare(Serial, other.Serial);
if (result != 0) return result;
result = Compare(Model, other.Model);
if (result != 0) return result;
result = Compare(OrderDate, other.OrderDate);
if (result != 0) return result;
result = Compare(StartDate, other.StartDate);
if (result != 0) return result;
result = Compare(Confirmed, other.Confirmed);
if (result != 0) return result;
result = Compare(Deposit, other.Deposit);
if (result != 0) return result;
result = Compare(Engine, other.Engine);
if (result != 0) return result;
result = Compare(Color, other.Color);
if (result != 0) return result;
return Compare(Name, other.Name);
}
If you just want to compare 2 instances for equality, it is simpler to use a boolean Equals() method, like so:
public bool Equals(AdminEngineTableRowModel other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
// If applicable, use <Property>.Equals(other.<Property>)
// For example, to compare strings according to culture
// and case
return (NXSTDT == other.NXSTDT &&
DALOC == other.DALOC &&
DealerId == other.DealerId &&
Status == other.Status &&
OrderNumber == other.OrderNumber &&
....);
}
This way, due to short-circuiting, the method will return false on the first un-equal property value.
If you use at least C# 9 you don't need to actually implement anything, just add record keyword to your class:
public record class YourClass {}
it will automatically generate the Equals, GetHashCode methods among others and overload the == operator so you can simply do:
if (instanceA == instanceB) {}
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/records

Merging of Two space objects into single space

Here, Space is a class with (xposition, yposition, zposition, length, depth, height) as its elements and there is a list of type space.
I need to check with in the list whether it follows some conditions which are in if condition.
If it satisfies, then I merge the two spaces into single space. After that, I remove both the spaces which I have used. It actually means merging of two spaces into single space.
The new List is created. Again I treat it as new list and do the same procedure until it does not satisfies the conditions.
My problem is, it is going into infinite loop. I want to resolve this.
public class MergeSpace
{
public List<Space> Mergespace(List<Space> Listofspaces)
{
foreach (Space space1 in Listofspaces)
{
foreach (Space space2 in Listofspaces)
{
//int count = 0;
if ((space1.sheight == space2.sheight)
&& (space1.sdepth == space2.sdepth)
&& (space2.xposition == space1.xposition + space1.slength)
&& (space2.yposition == space1.yposition)
&& (space2.zposition == space1.zposition)
&& (space1.semptyspace == true)
&& (space2.semptyspace == true))
{
Space space = new Space();
space.xposition = space1.xposition;
space.yposition = space1.yposition;
space.zposition = space1.zposition;
space1.slength = space1.slength + space2.slength;
space.sheight = space1.sheight;
space.sdepth = space1.sdepth;
space.semptyspace = true;
Listofspaces.Add(space);
Listofspaces.Remove(space1);
Listofspaces.Remove(space2);
Mergespace(Listofspaces);
}
public class MergeSpace
{
public List<Space> Mergespace(List<Space> Listofspaces)
{
List<Space> mergedspacelist = new List<Space>();
int count=0;
foreach (Space space1 in Listofspaces)
{
foreach (Space space2 in Listofspaces)
{
//int count = 0;
if ((space1.sheight == space2.sheight)
&& (space1.sdepth == space2.sdepth)
&& (space2.xposition == space1.xposition + space1.slength)
&& (space2.yposition == space1.yposition)
&& (space2.zposition == space1.zposition)
&& (space1.semptyspace == true)
&& (space2.semptyspace == true))
{
Space space = new Space();
space.xposition = space1.xposition;
space.yposition = space1.yposition;
space.zposition = space1.zposition;
space1.slength = space1.slength + space2.slength;
space.sheight = space1.sheight;
space.sdepth = space1.sdepth;
space.semptyspace = true;
mergedspacelist .Add(space);
count++;
}
}
}
if(count>0)
{
Mergespace(mergedspacelist );
}
}
i dont know what your actual need is, i think this will avoid the infinite loop
Your conditions always will satisfy for same instance of Space. The instance will merge with itself.
if (!space1.Equals(space2))
{
if ((space1.sheight == space2.sheight)
...
}

Check if array is null and has content

I'm in search for some code-improvement. I currently have the following piece of code:
if (pMyDocAction.s_locatie_st != null)
{
String[] myLocaties = Globals.GlobalTools.DeserializeValueToStringArray(pMyDocAction.s_locatie_st);
if (myLocaties != null)
if (myLocaties.Length > 0)
row.Locatie = myLocaties[0];
else
row.Locatie = String.Empty;
else
row.Locatie = String.Empty;
}
else
row.Locatie = String.Empty;
Mylocaties is a Array of String and this cannot change. How can i shorten this piece of code (or how can i combine the != null and .length > 0?
Thnx
You can use conditional operator and write that statement like this:
row.Locatie = (myLocaties != null &&
myLocaties.Length > 0) ? myLocaties[0] : String.Empty
I would suggest you to create a small extension method:
public static class ArrayExtension{
public static bool HasContent<T>(Array<T> array) {
return array != null && array.Length > 0;
}
}
Then you can check :
int[] x = null;
x.HasContent(); // false
string[] strs = new string[] {};
strs.HasContent(); // false
string[] strs2 = new string[] {"foo", "bar" };
strs.HasContent(); // true
This can be extended to simplify your syntax:
public static class ArrayExtension{
public static T FirstValueOrDefault<T>(Array<T> array, T #default) {
if( array != null && array.Length >0 ){
return array[0];
}
else {
return #default;
}
}
}
int[] x = null;
int y = x.FirstValueOrDefault(42); // 42
string[] strs = new string[] {};
string some = strs.FirstValueOrDefault("default"); // default
string[] strs2 = new string[] {"foo", "bar" };
string some2 = strs.FirstValueOrDefault("default"); // foo
Use && operator on two conditions, it will do short-circuit evaluation and if first condition is false, it will not evaluate the second condition.
if (myLocaties != null && myLocaties.Length > 0)
{
row.Locatie = myLocaties[0];
}
else
{
row.Locatie = String.Empty;
}
Since all other answers seem to ignore the if (pMyDocAction.s_locatie_st != null), something like this seems to be the most reusable:
row.Locatie = DeserializeLocation(pMyDocAction.s_locatie_st);
string DeserializeLocation(string locationString)
{
var result = "";
if (!string.IsNullOrEmpty(locationString))
{
String[] deserializedLocations =
Globals.GlobalTools.DeserializeValueToStringArray(locationString);
if (deserializedLocations != null && deserializedLocations.Any())
{
result = deserializedLocations[0];
}
}
return result;
}
You might even consider putting this method in your "GlobalTools" class, so you can call it from anywhere were you need to deserialize a potentially null-bearing serialized location string into a location string.

Convert String To Int in LINQ

I have a LINQ query that queries a DataTable. In the DataTable, the field is a string and I need to compare that to an integer, basically:
if ((electrical >= 100 && electrical <= 135) || electrical == 19)
{
// The device passes
}
the problem is, I am trying to do this in LINQ like this:
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where (Int32.Parse(row.Field<String>("electrical")) >= 100 &&
Int32.Parse(row.Field<String>("electrical")) <= 135) &&
Int32.Parse(row.Field<String>("electrical")) != 19 &&
row.Field<String>("print") == printName
select row;
I keep getting the exception:
Input string was not in a correct format
The main problem occurs when electrical == ""
Unfortunately, the framework doesn't provide a nice clean way to handle parsing scenarios where it fails. Of what's provided, they only throw exceptions or use out parameters, both of which does not work well with linq queries. If any one value you're parsing fails, the entire query fails and you just can't really use out parameters. You need to provide a method to handle the parsing without that does not throw and does not require using out parameters.
You can handle this in many ways. Implement it where upon failure, you return some default sentinel value.
public static int ParseInt32(string str, int defaultValue = 0)
{
int result;
return Int32.TryParse(str, out result) ? result : defaultValue;
}
Or what I would recommend, return a nullable value (null indicating it failed).
public static int? ParseInt32(string str)
{
int result;
return Int32.TryParse(str, out result) ? result : null;
}
This simplifies your query dramatically while still leaving it readable.
public bool GetElectricalStatus(string printName)
{
var query =
from row in singulationOne.Table.AsEnumerable()
where row.Field<string>("print") == printName
// using the nullable implementation
let electrical = ParseInt32(row.Field<string>("electrical"))
where electrical != null
where electrical == 19 || electrical >= 100 && electrical <= 135
select row;
return !query.Any();
}
p.s., your use of the Convert.ToInt32() method is incorrect. It is the same as calling Int32.Parse() and does not return a nullable, it will throw on failure.
I would check if the data in the column does not contain leading/trailing whitespaces - i.e. "15 " rather than "15" and if it does (or might do) trim it before trying to convert:
Int32.Parse(row.Field<String>("electrical").Trim())
BTW: not related to the error but I'd use let statement to introduce a local variable and do the conversion once:
let x = Int32.Parse(row.Field<String>("electrical").Trim())
where x >= 100...
I could not get anything to work, so I re-did the whole method:
public bool GetElectricalStatus(string printName)
{
List<object> eGoodList = new List<object>();
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where row.Field<String>("print") == printName
select row.Field<String>("electrical");
foreach (var eCode in eGoodCountQuery)
{
if (!string.IsNullOrEmpty(eCode.ToString()))
{
int? eCodeInt = Convert.ToInt32(eCode);
if (eCodeInt != null &&
(eCodeInt >= 100 && eCodeInt <= 135) || eCodeInt == 19)
{
eGoodList.Add(eCode);
}
}
}
if (eGoodList.Count() > 0)
{
return false;
}
else
{
return true;
}
}
The main problem occurs when electrical == ""
Why not make a function that does your evaluation, and call it in your Linq query. Put logic in to check the validity of the data contained within (so if you can't parse the data, it should return false)...
The function:
bool IsInRange(string text, int lower, int upper, params int[] diqualifiers)
{
int value = int.MinValue;
if (!int.TryParse(text, out value)) {
return false;
}
if (!(value >= lower && value <= upper)) {
return false;
}
if (disqualifiers != null && disqualifiers.Any(d => d == value)) {
return false;
}
return true;
}
The Linq query...
var eGoodCountQuery =
from row in singulationOne.Table.AsEnumerable()
where
IsInRange(row.Field<String>("electrical"), 100, 135, 19)
&& row.Field<String>("print") == printName
select row;

Why is this dynamic list comparison failing?

I have a search that returns a result that is dynamic. So I am trying to just show a label if there are no results found. The problem i am having is i dont know how to count the result because it is dynamic and is not equal to a type.
The error message is :
Operator '!=' Cannot be applied ot operands of type
System.Collections.Generic.List and int
if (Page.IsValid)
{
string keyword = txtSearch.Text.Trim();
List<dynamic> results = SearchItems(keyword);
List<dynamic> Cresults = SearchContacts(keyword);
if(results != 0 || Cresults !=0)
{
//bind and return
LVI.DataSource = results;
LVI.DataBind();
// System.Threading.Thread.Sleep(500);
//Contact Bind return
LVC.DataSource = Cresults;
LVC.DataBind();
// System.Threading.Thread.Sleep(250);
lvAdmin.DataSource = results;
lvAdmin.DataBind();
LVCAdmin.DataSource = Cresults;
LVCAdmin.DataBind();
}
else{
NoResults.Visible = true;
}
You cannot just do:
if(results != 0 || Cresults !=0)
{
}
That way your comparing the actual List to 0, which obviously fails.
Just do:
if(results.Count != 0 || Cresults.Count !=0)
{
}
Or:
if(results.Any() || Cresults.Any())
{
}
Use the Count property of the List class:
if (results.Count != 0 || Cresults.Count != 0)
{
//rest of code
}
Docs: http://msdn.microsoft.com/en-us/library/a7f69ad7.aspx
you can try using count
if(results.Count > 0 || Cresults.Count > 0)

Categories

Resources