Int.Parse and Sorting - c#

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

Related

Converting IEnumerable<IEnumerable<T>> to List<string>

I’m trying to convert from this answer the code:
static IEnumerable<IEnumerable<T>> GetKCombs<T>(IEnumerable<T> list, int length) where T : IComparable
{
if (length == 1) return list.Select(t => new T[] { t });
return GetKCombs(list, length - 1)
.SelectMany(t => list.Where(o => o.CompareTo(t.Last()) > 0),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
Into a list of strings. For example I want this output {1,2} {1,3} to convert it to "1,2","1,3" (this is 2 seperate string) but I cant get it. I cant even understand how I can read the results of the above function. this is my code:
int[] numbers = ListEditText.Text.Split(',').Select(x => int.Parse(x)).ToArray();
var combinations = GetKCombs(numbers, 2);
stringCombinations = combinations.Select(j => j.ToString()).Aggregate((x, y) => x + "," + y);
In the end all the results i will add them on a List with all the possible unique combinations
For example for the numbers {1,2,3} i want this List:
'1','2','3','1,2','1,3','2,3','1,2,3'
This is my code right now:
List<string> stringCombinations = new List<string>();
for (int i = 0; i < numbers.Count(); i++)
{
combinations = GetKCombs(numbers, i + 1).Select(c => string.Join(",", c));
stringCombinations.AddRange(combinations);
}
You can try first joining the results of the inner IEnumerables
var combinations = GetKCombs(numbers, 2).Select(c => string.Join(",", c));
and then concatenating them into a single string
var combinationString = string.Join("; ", combinations); // "1,2; 1,3"
Based on your edits -- if I got you right -- you can try doing
var combinationStrings =
numbers
.SelectMany((_, i) =>
GetKCombs(numbers, i + 1) // get combinations for each 'length'
.Select(c => string.Join(",", c))) // join them to a string
.ToList();
Try
var stringCombinations = string.Join(",", combinations.Select(j => $#"""{string.Join(",", j)}"""));
It prints exactly the output you want.

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!

Convert comma separated string to int list and validate

I've converting comma separated string to List.
Problem while a list contain empty value or invalid value, how to skip invalid value?
List<int> list = model.Categories.Split(',').Select(int.Parse).ToList();
string will be "10,12,3.0,4,1k,5,0.0";
If you wanna validate that only valid ints will reach the Select use int.TryParse:
int num;
var result = model.Categories.Split(',')
.Where(i => int.TryParse(i, out num))
.Select(int.Parse).ToList();
If using C#7.0 then can just be:
var result = model.Categories.Split(',')
.Where(i => int.TryParse(i, out int num))
.Select(int.Parse).ToList();
To not have to parse twice you can try something like this too:
Func<string, int?> ParseOrDefault = (input) =>
int.TryParse(input, out int num) ? (int?)num : null;
var result = data.Split(',').Select(ParseOrDefault).Where(i => i != null).ToList();
Or better:
var result = data.Split(',')
.Select(i => int.TryParse(i, out int num) ? (int?)num : null)
.Where(i => i != null).ToList();
You can try using TryParse instead of Parse:
List<int> list = model
.Categories
.Split(',')
.Select(item => {
int value;
bool parsed = int.TryParse(item, out value);
return new {
parsed = parsed,
value = value;
};
})
.Where(item => item.parsed)
.Select(item => item.value)
.ToList();

LINQ - Select min count

I have a list of strings which contain X in them. I want to select list(s) with the minimum count of X in them. For example:
CountMin("AXBXX", "AAX") will return AAX.
How can I write this qith LINQ in a concise way ?
public static string CountMin(IList<string> inputList)
{
if (inputList == null || !inputList.Any()) return null;
var result = inputList.Select(s => new
{
Item = s,
Count => s.Count(ch => ch == 'X')
})
.OrderBy(item => item.Count).First().Item;
}
Snippet assumes that all elements on list are different to null. If you need it, it could be easily improved.
You can also omit temporary class:
inputList.OrderBy(s => s.Count(c => c == 'X')).First();
string[] list = {"AXBXX", "AAX", "AXX"};
string result = (from word in list
select new { word, wordLen = (word.Length - (word.Replace("X", "")).Length) })
.OrderBy(x => x.wordLen).First().word;
MessageBox.Show(result);
Here's an answer that will get you all of the minimum X strings from the list.
var listOfStrings = new List<string>()
{
"AXB",
"ABXXC",
"ABX",
};
var minimumXs =
listOfStrings
.GroupBy(x => x.Count(y => y == 'X'))
.OrderBy(x => x.Key)
.Take(1)
.SelectMany(x => x);
That gives me:
AXB
ABX

Linq - Order by number then letters

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

Categories

Resources