Convert comma separated string to int list and validate - c#

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

Related

Convert a string in a List<int> using LINQ (cleaner way)

I have this string:
string input = "1,2,3,4,s,6";
Pay attention to the s character.
I just want to convert this string in a List<int> using LINQ. I initially tried in this way:
var myList = new List<int>();
input.Split(',').ToList().ForEach(n =>
myList.Add(int.TryParse(n, out int num) ? num : -1)
);
lista.RemoveAll(e => e == -1);
But I prefer not have any -1 instead of a no-number characters.
So now I try with this:
var myList = new List<int>();
input.Split(',').ToList()
.FindAll(n => int.TryParse(n, out int _))
.ForEach(num => myList.Add(int.Parse(num)));
I prefer this, but is really a shame that the parsing happening two times (TryParse at first and then Parse). But, from what I understand, the out variable in TryParse is useless (or not?).
Have you others suggests (using LINQ)?
public class ParsesStringsToIntsWithLinq
{
public IEnumerable<int> Parse(string input)
{
var i = 0;
return (from segment in input.Split(',')
where int.TryParse(segment, out i)
select i);
}
}
[TestClass]
public class Tests
{
[TestMethod]
public void IgnoresNonIntegers()
{
var input = "1,2,3,4,s,6";
var output = new ParsesStringsToIntsWithLinq().Parse(input);
Assert.IsTrue(output.SequenceEqual(new []{1,2,3,4,6}));
}
}
It doesn't return a List<int> but I have to draw the line somewhere. You can make a list out of it.
Using a nice extension method
public static IEnumerable<T> AsSingleton<T>(this T source) {
yield return source;
}
(which you can replace with new[] { n } if preferred)
input.Split(',').SelectMany(s => Int32.TryParse(s, out var n) ? n.AsSingleton() : Enumerable.Empty<int>()).ToList()
I prefer to make a nice helper function:
Func<string, int?> tryParse = s => int.TryParse(s, out int n) ? (int?)n : null;
Then it's a simple matter to parse:
string input = "1,2,3,4,s,6";
List<int> myList =
input
.Split(',')
.Select(s => tryParse(s))
.Where(n => n.HasValue)
.Select(n => n.Value)
.ToList();
That gives:
1
2
3
4
6
int i = 0;
var myList = (from s in input.Split(',') where int.TryParse(s, out i) select i).ToList();
If the numbers are always single ASCII digits:
var myList = "1,2,3,4,s,6".Select(c => c ^ 48).Where(i => i < 10).ToList();
Few slower RegEx alternatives for fun:
var myList2 = Regex.Split("1,2,3,4,s,6", "[^0-9]+").Select(int.Parse).ToList(); // if the string starts and ends with digits
var myList3 = Regex.Replace("1,2,3,4,s,6", "[^0-9]+", " ").Trim().Split(' ').Select(int.Parse).ToList();
var myList4 = Regex.Matches("1,2,3,4,s,6", "[0-9]+").Cast<Match>().Select(m => int.Parse(m.Value)).ToList();
Why does it have to be LINQ?
Try:
//Come up a better name...
public static List<int> ConvertToIntListNoLinq(string input)
{
List<int> output = new List<int>();
foreach(string s in input.Split(','))
{
if(int.TryParse(s, out int result))
{
output.Add(result);
}
}
return output;
}
Fiddle
Here's a generic LINQ extension, which utilizes a delegate. This will allow you to pass in a function returning a bool, while "retaining" the result of the out variable (like int.TryParse).
Usage:
string input = "1,2,3,4,s,6";
List<int> myList = input.Split(',').SelectTry<string, int>(int.TryParse).ToList();
Code:
using System.Collections.Generic;
public static class LINQExtensions
{
public delegate bool TryFunc<TSource, TResult>(TSource source, out TResult result);
public static IEnumerable<TResult> SelectTry<TSource, TResult>(
this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
{
foreach (TSource item in source)
{
TResult result;
if (selector(item, out result))
{
yield return result;
}
}
}
}
I think this is a clean way too. Even though it uses that extra variable, the benefit we get is it is clean and understandable.
string ids = "2,4,2,4,5,s"
const int inValidInt = -99;
var ids = ids.Split(',')
.Select(id =>
{
int parsedId = int.TryParse(id, out parsedId) ? parsedId : inValidInt;
return parsedId;
})
.Where(x => x != inValidInt).ToList();
You can do it like this:
List<int> numbers = input
.Split(',')
.Where(t => int.TryParse(t, out int a))
.Select(int.Parse)
.ToList();
You don't need to call .Split(...).ToList() as String[] is already enumerable.
You can use multiple statements in a lambda with braces.
The FindAll, ForEach and RemoveAll methods are not Linq methods, they're members of List<T>. Their Linq equivalent is Where.
Like so:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => { Int32 val; return Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out val ) ? val : -1 } )
.Where( n => n != -1 )
.ToList();
You can make it more concise with a helper method:
static Int32 Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return -1;
}
Becomes:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => Parse( s ) )
.Where( n => n != -1 )
.ToList();
If you don't want to reserve -1 then you can use nullable ints:
static Int32? Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return null;
}
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',') // String to String[]
.Select( s => Parse( s ) ) // String[] to IEnumerable<Int32?>
.Where( n => n != null ) // filter out nulls
.Select( n => n.Value ) // IEnumerable<Int32?> to IEnumerable<Int32>
.ToList(); // IEnumerable<Int32> to List<Int32>

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

Receiving “Input string was not in a correct format” error

I have problem when I try to debug and encounter this error:
Input string was not in a correct format
Code:
List<Int32> ListintNo = DT_ExcludeNo.AsEnumerable()
.Select(x => Convert.ToInt32(x[0].ToString())).ToList();
Try this:
int i = 0;
List ListintNo = DT_ExcludeNo.AsEnumerable().Where(x => Int32.TryParse(x[0].ToString(), out i)).Select(x => Convert.ToInt32(x[0].ToString())).ToList();
Almost the same as Ricardos, with a simplification (using num in the Select statement rather than calling Convert, since we've already converted the string with the call to TryParse():
var num = 0;
var ListintNo = DT_ExcludeNo.AsEnumerable()
.Where(x => int.TryParse(x[0], out num))
.Select(x => num)
.ToList();

Find comma in a Textbox full of integers

I got a textbox that should be filled with int of 4 number, something like this [0000, 4444, 5555, 6666]. I need to get where is the comma and then put the 4 numbers in a var.
Can you help me?
Have you tried String.Split?
string[] allTokens = textBox1.Text.Split(new []{ ','}, StringSplitOptions.RemoveEmptyEntries);
int[] allInts = Array.ConvertAll<string, int>(allTokens, int.Parse);
If the format can be invalid you can use int.TryParse:
int num = 0;
int[] allInts = allTokens
.Where(s => int.TryParse(s, out num))
.Select(s => num)
.ToArray();
You will get int list
var numbers = TextBox1.Text.Split(',').Select(str => {
int value;
bool success = int.TryParse(str, out value);
return new { value, success };
})
.Where(pair => pair.success)
.Select(pair => pair.value).ToList();
Reference
You could try
var resultArr = tb.split(",");
foreach (elem in resultArr)
{
int i;
if (int.tryparse(elem, out i))
// do something with i
else
// that was not an int
}

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

Categories

Resources