Linq - Order by number then letters - c#

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();

Related

c# ordering strings with different formats

I have Licence plate numbers which I return to UI and I want them ordered in asc order:
So let's say the input is as below:
1/12/13/2
1/12/11/3
1/12/12/2
1/12/12/1
My expected output is:
1/12/11/3
1/12/12/1
1/12/12/2
1/12/13/2
My current code which is working to do this is:
var orderedData = allLicenceNumbers
.OrderBy(x => x.LicenceNumber.Length)
.ThenBy(x => x.LicenceNumber)
.ToList();
However for another input sample as below:
4/032/004/2
4/032/004/9
4/032/004/3/A
4/032/004/3/B
4/032/004/11
I am getting the data returned as:
4/032/004/2
4/032/004/9
4/032/004/11
4/032/004/3/A
4/032/004/3/B
when what I need is:
4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11
Is there a better way I can order this simply to give correct result in both sample inputs or will I need to write a custom sort?
EDIT
It wont always be the same element on the string.
This could be example input:
2/3/5/1/A
1/4/6/7
1/3/8/9/B
1/3/8/9/A
1/5/6/7
Expected output would be:
1/3/8/9/A
1/3/8/9/B
1/4/6/7
1/5/6/7
2/3/5/1/A
You should split your numbers and compare each part with each other. Compare numbers by value and strings lexicographically.
var licenceNumbers = new[]
{
"4/032/004/2",
"4/032/004/9",
"4/032/004/3",
"4/032/004/3/A",
"4/032/004/3/B",
"4/032/004/11"
};
var ordered = licenceNumbers
.Select(n => n.Split(new[] { '/' }))
.OrderBy(t => t, new LicenceNumberComparer())
.Select(t => String.Join("/", t));
Using the following comparer:
public class LicenceNumberComparer: IComparer<string[]>
{
public int Compare(string[] a, string[] b)
{
var len = Math.Min(a.Length, b.Length);
for(var i = 0; i < len; i++)
{
var aIsNum = int.TryParse(a[i], out int aNum);
var bIsNum = int.TryParse(b[i], out int bNum);
if (aIsNum && bIsNum)
{
if (aNum != bNum)
{
return aNum - bNum;
}
}
else
{
var strCompare = String.Compare(a[i], b[i]);
if (strCompare != 0)
{
return strCompare;
}
}
}
return a.Length - b.Length;
}
}
If we can assume that
Number plate constist of several (one or more) parts separated by '/', e.g. 4, 032, 004, 2
Each part is not longer than some constant value (3 in the code below)
Each part consist of either digits (e.g. 4, 032) or non-digits (e.g. A, B)
We can just PadLeft each number plate's digit part with 0 in order to compare not "3" and "11" (and get "3" > "11") but padded "003" < "011":
var source = new string[] {
"4/032/004/2",
"4/032/004/9",
"4/032/004/3/A",
"4/032/004/3/B",
"4/032/004/11",
};
var ordered = source
.OrderBy(item => string.Concat(item
.Split('/') // for each part
.Select(part => part.All(char.IsDigit) // we either
? part.PadLeft(3, '0') // Pad digit parts e.g. 3 -> 003, 11 -> 011
: part))); // ..or leave it as is
Console.WriteLine(string.Join(Environment.NewLine, ordered));
Outcome:
4/032/004/2
4/032/004/3/A
4/032/004/3/B
4/032/004/9
4/032/004/11
You seem to be wanting to sort on the fourth element of the string (delimited by /) in numeric rather than string mode.. ?
You can make a lambda more involved/multi-statement by putting it like any other method code block, in { }
var orderedData = allLicenceNumbers
.OrderBy(x =>
{
var t = x.Split('/');
if(t.Length<4)
return -1;
else{
int o = -1;
int.TryParse(t[3], out o);
return o;
}
)
.ToList();
If you're after sorting on more elements of the string, you might want to look at some alternative logic, perhaps if the first part of the string will always be in the form N/NNN/NNN/??/?, then do:
var orderedData = allLicenceNumbers
.OrderBy(w => w.Remove(9)) //the first 9 are always in the form N/NNN/NNN
.ThenBy(x => //then there's maybe a number that should be parsed
{
var t = x.Split('/');
if(t.Length<4)
return -1;
else{
int o = -1;
int.TryParse(t[3], out o);
return o;
}
)
.ThenBy(y => y.Substring(y.LastIndexOf('/'))) //then there's maybe A or B..
.ToList();
Ultimately, it seems that more and more outliers will be thrown into the mix, so you're just going to have to keep inventing rules to sort with..
Either that or change your strings to standardize everything (int an NNN/NNN/NNN/NNN/NNA format for example), and then sort as strings..
var orderedData = allLicenceNumbers
.OrderBy(x =>
{
var t = x.Split('/');
for(int i = 0; i < t.Length; i++) //make all elements in the form NNN
{
t[i] = "000" + t[i];
t[i] = t[i].Substring(t[i].Length - 3);
}
return string.Join(t, "/");
}
)
.ToList();
Mmm.. nasty!

Arranging a list of AlphaNumberic string in c#

I have a list of alpha-numeric strings which are something like this:
v1_2014.
I have these values ranging from v1 to v53 from year 2014 to 2016. I want to arrange them in this order v1_2014 to v53_2014 , v1_2015 to v53_2015 and so on.
When i try to sort the list the order returned is
v1_2014, v1_2015,v1_2016, v10_2014, v10_2015, ... ,v2_2014,v2_2015,v2_2016,v20_2014
and so on.
Can someone give me an idea on how to sort this.
Thanks
You need to implement an own IComparer<string> like that:
public class MyComparer : IComparer<String>
{
public int Compare(string x, string y)
{
// your comparing logic
}
}
Then you can sort your list like that:
List<string> myStrings = // wherever you get them
myString.Sort(new MyComparer());
A possible implementation of MyComparer.Compare could look like this:
public int Compare(string x, string y)
{
string[] xpart = x.Split('_');
int x1 = int.Parse(xpart[0].Trim('v'));
int x2 = int.Parse(xpart[1]);
string[] ypart = y.Split('_');
int y1 = int.Parse(ypart[0].Trim('v'));
int y2 = int.Parse(ypart[1]);
if (x2 < y2) return -1;
if (x2 > y2) return 1;
if (x1 < y1) return -1;
if (x1 > y1) return 1;
return 0;
}
This is only a suggestion that surely can be improved. At first by some error handling if the strings are not always well formed.
The criteria for Compare are
if x is smaller than y return -1
if x is greater than y return 1
if x equals y return 0
with "smaller", "greater" and "equal" according to your required sorting.
if all you versions have a template like this "V{number}_{Year}" you can use this code
List<string> Versions = new List<string>();
// Fill Versions
Versions = Versions.OrderBy(V => Convert.ToInt32(V.Split('_')[1]))
.ThenBy(V => Convert.ToInt32(V.Split('_')[0].Remove(0, 1)))
.ToList();
You could also use a LINQ query to order this, therefore you need to split the value and parse the tokens to int:
int version = 0, year = 0;
IEnumerable<string> orderedByYearAndVersion = values
.Select(v => new { value = v, tokens = v.Split('_') })
.Where(x => x.tokens.Length == 2
&& x.tokens[0].StartsWith("v")
&& int.TryParse(x.tokens[0].Substring(1), out version)
&& int.TryParse(x.tokens[1], out year))
.Select(x => new { x.value, version, year })
.OrderBy(x => x.year)
.ThenBy(x => x.version)
.Select(x => x.value);
But in general it's better to create a custom class with properties like int Year and int Version. Then you could implement IComparable and methods like List.Sort work automatically.

Int.Parse and Sorting

I have the following code..
var strings = new[] {
"FD1","FD5","FD10","FD102","FD105","FD10","FD32","FD80", "FD31", "FD21", "FDnon"
};
strings = strings.Select(str => new
{
str,
num = int.Parse(String.Concat(str.Trim('F', 'D'))),
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
However this fails when it gets to "FDnon" as there are no numbers in it,
How do I get this to work with "FDnon" sorted at the top?
var result = strings.OrderBy(x =>
{
int y = int.MinValue;
int.TryParse(x.Substring(2), out y);
return y;
});
If you want custom ordering, supply your custom SortMethod
var sorted = strings
.OrderBy(SpecialSort)
.ToList();
public static int SpecialSort(string value)
{
int sortOrder = 0;
string numberPart = value.Trim('F', 'D');
int.TryParse(numberPart, out sortOrder);
return sortOrder;
}
Edit: Changed solution to account for the sorting of numbers in String.
If you want FDnon to be presented at the top you can use something like:
strings = strings.Select(str => new
{
str,
num = str=="FDnon" ? Int32.MaxValue : Int32.Parse(String.Concat(str.Trim('F', 'D')))
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
This code just skips FDnon conversion. If you want to allow other values you should be more specific on what are you going to accept.
You need a more sophisticated parse that converts FDnon into an integral value that will sort last (in this case Int32.MaxValue would do).
Something like:
var res = strings.Select(s => {
var numPart = s.Trim('F', 'D');
var i;
if (!Int32.TryParse(numPart, out i)) {
i = Int32.MaxValue;
}
return new {
str = s,
num = i
};
}.OrderBy …
If the strings always start with two letters, you could also use Substring(2). To check if there's a numeric part, you can use Enumerable.All(Char.IsDigit) (assumes that all chars are digits after the first two lewtters):
strings = strings.Select(str => new
{
str,
num = str.Substring(2).All(Char.IsDigit) ? int.Parse(str.Substring(2)) : int.MinValue
})
.OrderBy(x => x.num)
.Select(x => x.str)
.ToArray();
DEMO

Simple LINQ question in C#

I am trying to use LINQ to return the an element which occurs maximum number of times AND the number of times it occurs.
For example:
I have an array of strings:
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
//...
Some LINQ statement here
//...
In this array, the query would return cherry as the maximum occurred element, and 3 as the number of times it occurred. I would also be willing to split them into two queries if that is necessary (i.e., first query to get the cherry, and second to return the count of 3.
The solutions presented so far are O(n log n). Here's an O(n) solution:
var max = words.GroupBy(w => w)
.Select(g => new { Word = g.Key, Count = g.Count() })
.MaxBy(g => g.Count);
Console.WriteLine(
"The most frequent word is {0}, and its frequency is {1}.",
max.Word,
max.Count
);
This needs a definition of MaxBy. Here is one:
public static TSource MaxBy<TSource>(
this IEnumerable<TSource> source,
Func<TSource, IComparable> projectionToComparable
) {
using (var e = source.GetEnumerator()) {
if (!e.MoveNext()) {
throw new InvalidOperationException("Sequence is empty.");
}
TSource max = e.Current;
IComparable maxProjection = projectionToComparable(e.Current);
while (e.MoveNext()) {
IComparable currentProjection = projectionToComparable(e.Current);
if (currentProjection.CompareTo(maxProjection) > 0) {
max = e.Current;
maxProjection = currentProjection;
}
}
return max;
}
}
var topWordGroup = words.GroupBy(word => word).OrderByDescending(group => group.Count()).FirstOrDefault();
// topWordGroup might be a null!
string topWord = topWordGroup.Key;
int topWordCount = topWordGroup.Count;
And in case if we don't like O(N log N):
var topWordGroup = words.GroupBy(word => word).Aggregate((current, acc) => current.Count() < acc.Count() ? acc : current);
First thing that comes to mind (meaning there is probably a more efficient way)
var item = words.GroupBy(x => x).OrderByDescending(x => x.Count()).First()
//item.Key is "cherry", item.Count() is 3
EDIT: forgot op wanted the name and the count
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
var topWordAndCount=words
.GroupBy(w=>w)
.OrderByDescending(g=>g.Count())
.Select(g=>new {Word=g.Key,Count=g.Count()})
.FirstOrDefault();
//if(topWordAndCount!=null)
//{
// topWordAndCount.Word
// topWordAndCount.Count
Try this one:
Converting SQL containing top, count, group and order to LINQ (2 Entities)
string[] words = { "cherry", "apple", "blueberry", "cherry", "cherry", "blueberry" };
var r = words.GroupBy (x => x)
.OrderByDescending (g => g.Count ())
.FirstOrDefault ();
Console.WriteLine (String.Format ("The element {0} occurs {1} times.", r.Key, r.Count ()));
A simpler O(n) solution:
var groups = words.GroupBy(x => x);
var max = groups.Max(x => x.Count());
var top = groups.First(y => y.Count() == max).Key;
Here's a very fast O(n) solution in one line(!):
s.GroupBy(x => x).Aggregate((IGrouping<string,string>)null, (x, y) => (x != null && y != null && x.Count() >= y.Count()) || y == null ? x : y, x => x);
Or this:
s.GroupBy(x => x).Select(x => new { Key = x.Key, Count = x.Count() }).Aggregate(new { Key = "", Count = 0 }, (x, y) => x.Count >= y.Count ? x : y, x => x);

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