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.
Related
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!
I know there is a bunch of questions out there already on this but I still cannot get it to work due to the type conversion (I think).
I have a list of custom class and I normally group this either by months (mt) or by quarters (qr) depending on user choice. This works fine. However, now I have the request to allow grouping by say Q413 and the rest of 2013 in months. Mt and Qr are strings. Quartpos is a double and equals 4 if only Q4 is in quarters and rest in months.
Here is what I have:
List<MyClass> results = classlist
.GroupBy(a => new {
a.rg,
a.tar,
a.mt = ((double.Parse(a.qr) < quartpos) ? a.mt : 0),
a.qr })
.Select(g => new MyClass {
RG = g.Select(a => a.rg).First(),
tar = g.Select(a => a.tar).First(),
yr = g.Select(a => a.yr).First(),
qr = g.Select(a => a.qr).First(),
mt = g.Select(a => a.mt).First(),
pp = g.Average(a => double.Parse(a.pp)),
pi = g.Sum(a => double.Parse(a.pi)),
cp = g.Average(a => double.Parse(a.cp)),
ci = g.Sum(a => double.Parse(a.ci)),
it = g.Sum(a => double.Parse(a.it)),
to = g.Sum(a => double.Parse(a.to)),
cnt = g.Select(a => a.dt).Distinct().Count(),
pdvol = g.Sum(a => (double.Parse(a.pp) <= 1)
? 0
: (double.Parse(a.pi) / double.Parse(a.pp))) })
.ToList();
This throws two errors:
String Int type conversion - since mt is string but : 0 is int
Invalid anonymous type member declaration.
I m fully aware that I would have to use the same syntax in the select to make sure that quarters are selected if grouped by quarters and months if grouped by mounths also.
Definition of the class:
public class Myclass
{
public string yr;
public string qr;
public string mt;
public string cw;
public string tar;
public string RG;
public double pp;
public double pi;
public double cp;
public double ci;
public double it;
public double to;
public double pd;
public double cd;
public double fp;
public double fi;
public double fd;
public int cnt;
public double pdmw;
public double pdvol;
}
classlist is a class where all variables are strings - straight from csv import.
My question is how can I group month (mt) conditionally?
a.mt = ((double.Parse(a.qr) < quartpos) ? a.mt : 0)
You can't create anonymous types like this. Take a look at the syntax in the documentation.
Either you want:
a.mt
or
PropertyName = ((double.Parse(a.qr) < quartpos) ? a.mt : 0)
in this line.
One not very elegant solution I can think of is to split classlist into two new lists with a simple .select(a => double.Parse(a.qr) < quartpos) and then keep the code that does all the other stuff from above and group list1 by months and list2 by quarters. This would remove the need for conditional grouping.
But I thought there must be a better way.
public static IEnumerable<EVFile> SmallerQ(this IEnumerable<EVFile> #this, double quarterpos)
{
return from ev in #this
where (double.Parse(ev.qr)<quarterpos)
select ev;
}
public static IEnumerable<EVFile> LargerEqQ(this IEnumerable<EVFile> #this, double quarterpos)
{
return from ev in #this
where (double.Parse(ev.qr) >= quarterpos)
select ev;
}
newlist = classlist.SmallerQ(quarterpos).ToList();
With these two extension methods I can split the original list in two and then proceed without the conditional group join. So it does the job, but as said not very nicely.
I have the following unsorted list:
List<string> myUnsortedList = New List<string>();
myUnsortedList.Add("Alpha");
myUnsortedList.Add("(avg) Alpha");
myUnsortedList.Add("Zeta");
myUnsortedList.Add("Beta");
myUnsortedList.Add("(avg) Beta");
myUnsortedList.Add("(avg) Zeta");
I want to sort the list descending alphabetical order, then have the value with (avg) right after the normal value:
Final Result: Zeta, (avg) Zeta, Beta, (avg) Beta, Alpha, (avg) Alpha
My application is written in C# and I want to use LINQ to accomplish the sorting
This should work ok for what you need, assuming "(avg)" is the only special prefix
This will order all the stings descending not including the "(avg) " then it will order by the strings length this way the string with the "(avg)" prefix will come after the one without
var result = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x => x.Length);
Final Result:
Zeta
(avg) Zeta
Beta
(avg) Beta
Alpha
(avg) Alpha
Here are a couple of ways to pull this off with LINQ, while also correctly sorting the values should they occur in an order other than the one you've presented. For example, if "(avg) Zeta" occurs before "Zeta" then the latter should still come first once sorted.
Here's the sample list, reordered to match what I described above:
var myUnsortedList = new List<string>
{
"Alpha",
"(avg) Alpha",
"(avg) Zeta",
"Zeta",
"Beta",
"(avg) Beta"
};
Lambda syntax
string prefix = "(avg)";
var result = myUnsortedList.Select(s => new
{
Value = s,
Modified = s.Replace(prefix, "").TrimStart(),
HasPrefix = s.StartsWith(prefix)
})
.OrderByDescending(o => o.Modified)
.ThenBy(o => o.HasPrefix)
.Select(o => o.Value);
Zip / Aggregate
string prefix = "(avg)";
var avg = myUnsortedList.Where(o => o.StartsWith(prefix))
.OrderByDescending(o => o);
var regular = myUnsortedList.Where(o => !o.StartsWith(prefix))
.OrderByDescending(o => o);
var result = regular.Zip(avg, (f, s) => new { First = f, Second = s })
.Aggregate(new List<string>(), (list, o) =>
new List<string>(list) { o.First, o.Second });
Query syntax and string splitting
This one is similar to the lambda syntax, except I'm not using the prefix to determine which string has a prefix. Instead, I am splitting on a space, and if the split result has more than one item then I'm assuming that it has a prefix. Next, I order based on the value and the prefix's availability.
var result = from s in myUnsortedList
let split = s.Split(' ')
let hasPrefix = split.Length > 1
let value = hasPrefix ? split[1] : s
orderby value descending, hasPrefix
select s;
Split the lists into two lists, one normal, one average.
Sort them both.
Then, do a manual "Zipper Merge".
You should probably create your own custom IComparer<T>:
class MyCustomComparer : IComparer<string>
{
private readonly StringComparison StringComparer;
public static readonly MyCustomComparer Ordinal =
new MyCustomComparer(StringComparison.Ordinal);
public static readonly MyCustomComparer OrdinalIgnoreCase =
new MyCustomComparer(StringComparison.OrdinalIgnoreCase);
// etc.
private MyCustomComparer(StringComparison stringComparer)
{
StringComparer = stringComparer;
}
public int Compare(string x, string y)
{
bool isMatchedX = IsMatchedPattern(x);
bool isMatchedY = IsMatchedPattern(y);
if (isMatchedX&& !isMatchedY ) // x matches the pattern.
{
return String.Compare(Strip(x), y, StringComparer);
}
if (isMatchedY && !isMatchedX) // y matches the pattern.
{
return String.Compare(Strip(y), x, StringComparer);
}
return String.Compare(x, y, StringComparison.Ordinal);
}
private static bool isMatchedPattern(string str)
{
// Use some way to return if it matches your pattern.
// StartsWith, Contains, Regex, etc.
}
private static string Strip(string str)
{
// Use some way to return the stripped string.
// Substring, Replace, Regex, etc.
}
}
Check to see if x and y match your pattern. If neither or both do, then use a standard comparison operation. Basically, you only need the custom comparison operation if one (and only one) matches the pattern.
If x matches the pattern and y doesn't, then strip x and check the stripped version of x against y using the String.Compare(...) operation.
If y matches the pattern and x doesn't, then strip y and check the stripped version of y against x using the String.Compare(...) operation.
I updated my answer to show how you can copy the way StringComparison works by exposing static readonly instances of the custom comparer for case/culture options.
Finally, use LINQ with your custom comparer: myList.OrderBy(x => x, MyCustomComparer.Ordinal);
One final note... feel free to optimize this if necessary. This is untested code just off the whim of my mind. The logic is there, I hope. But, typos might have occurred.
Hope that helps.
Another way is to implement an some comparer say MyComparer that implements IComparer<string> and then:
var result = myUnsortedList.OrderBy(x => x, new MyComparer());
I feel like you're using the wrong data structure for this. Why don't you use a SortedDictionary and make it be "name => avg"
untested, probably working code:
SortedDictionary<string, int> dict = new SortedDictionary<string, int>();
dict.Add("Alpha", 10);
dict.Add("Beta", 20);
dict.Add("Zeta", 30);
foreach(string key in dict.Keys.Reverse())
{
int avg = dict[key];
}
To use Your own logic in linq ordering You should implement Your own Comparer and use it's instance as second parameter in OrderBy or OrderByDescending linq method like below:
namespace ConsoleApplication71
{
public class AVGComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// Null checkings are necessary to prevent null refernce exceptions
if((x == null) && (y == null)) return 0;
if(x == null) return -1;
if(y == null) return 1;
const string avg = #"(avg) ";
if(x.StartsWith(avg) || y.StartsWith(avg))
{
return x.Replace(avg, string.Empty).CompareTo(y.Replace(avg, string.Empty));
}
return x.CompareTo(y);
}
}
class Program
{
static void Main(string[] args)
{
List<string> myUnsortedList = new List<string>();
myUnsortedList.Add("Alpha");
myUnsortedList.Add("(avg) Alpha");
myUnsortedList.Add("Zeta");
myUnsortedList.Add("Beta");
myUnsortedList.Add("(avg) Beta");
myUnsortedList.Add("(avg) Zeta");
var mySortedList = myUnsortedList.OrderByDescending(s => s, new AVGComparer());
foreach (string s in mySortedList)
{
Console.WriteLine(s);
}
}
}
}
The output is:
Zeta
(avg) Zeta
Beta
(avg) Beta
Alpha
(avg) Alpha
In a line:
var sorted = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x=> x.Contains("(avg)")).ToList();
Here is a passing test (nunit):
[Test]
public void CustomSort()
{
var myUnsortedList = new List<string> { "Zeta", "Alpha", "(avg) Alpha", "Beta", "(avg) Beta", "(avg) Zeta" };
var EXPECTED_RESULT = new List<string> { "Zeta", "(avg) Zeta", "Beta", "(avg) Beta", "Alpha", "(avg) Alpha" };
var sorted = myUnsortedList.OrderByDescending(x => x.Replace("(avg) ", "")).ThenBy(x=> x.Contains("(avg)")).ToList();
for (int i = 0; i < myUnsortedList.Count; i++)
{
Assert.That(sorted[i], Is.EqualTo(EXPECTED_RESULT[i]));
}
}
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();
Is there some way to use IComparer with ArrayList.Sort() to sort a group of strings as ints?
If they are all strings, why are you using an ArrayList? If you're on .Net 2.0 or later, List<string> is a much better choice.
If you're on .Net 3.5 or later:
var result = MyList.OrderBy(o => int.Parse(o.ToString() ) ).ToList();
Sure. Just create the appropriate comparer that does the conversion.
public class StringAsIntComparer : IComparer {
public int Compare(object l, object r) {
int left = Int32.Parse((string)l);
int right = Int32.Parse((string)r);
return left.CompareTo(right);
}
A slight variation based on Joel's solution
string[] strNums = {"111","32","33","545","1","" ,"23",null};
var nums = strNums.Where( s =>
{
int result;
return !string.IsNullOrEmpty(s) && int.TryParse(s,out result);
}
)
.Select(s => int.Parse(s))
.OrderBy(n => n);
foreach(int num in nums)
{
Console.WriteLine(num);
}