How to omit a value in a select lambda? - c#

I want to make a simple CSV parser. It should go through a list of comma separated values and put them in a IList<int>. The values are expected to be integer numbers. In case a value is not parseable, I just want to omit it.
This is the code I have so far:
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return parsed;
}
continue; //is not allowed here
}).ToList();
However, the use of continue is (of course) not allowed here. How to omit a value in my select implementation?
Note: Of course could I use a foreach or a LINQ expression, but I wonder how to do it with a lambda.

How about:
public static IEnumerable<int> ExtractInt32(this IEnumerable<string> values) {
foreach(var s in values) {
int i;
if(int.TryParse(s, out i)) yield return i;
}
}
then:
var vals = csv.Split(',').ExtractInt32().ToList();
The nice things here:
avoids magic "sentinal" numbers (like int.MinValue)
avoids a separate and disconnected "it is valid" / "parse" step (so no duplication)

Select transforms a value. It doesn't filter. Where is doing that:
csv.Split(',')
.Select(item =>
{
int parsed;
return new { IsNumber = int.TryParse(item, out parsed),
Value = parsed };
})
.Where(x => x.IsNumber)
.Select(x => x.Value);
Additionally, please see this answer for a clever, short way of doing it. Please note that the meaning of "clever" isn't entirely positive here.

One way is to return some default value and then skip it.
errorInt = int.MinValue;
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return parsed;
}
else
{
return errorInt;
}
}).Where(val => val != errorInt).ToList();

I think you have three options:
Use SelectMany instead which will allow you to return as empty enumerable for elements you wish to omit (and an enumerable of length 1 otherwise).
Use an int value you are sure won't be in the set (e.g. -1) to represent 'omitted' and filter them out afterwards. This approach is fragile as you may pick a value that subsequently appears in the set which will result in a subtle bug. (You could mitigate this by using a larger data type, e.g. long and picking a value outside the range of int but then you will need to convert back to int subsequently.)
Use Nullable<int> (int?) instead and filter out the null values afterwards.
1:
csv.Split(',').SelectMany(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return new[] {parsed};
}
return Enumerable.Empty<int>();
}
3:
csv.Split(',').Select(item =>
{
int parsed;
if (int.TryParse(item, out parsed))
{
return (int?) parsed;
}
return (int?) null;
}
.Where(item => item.HasValue)
.Select(item => item.Value);

try this:
int dummy;
sv.Split(',').Where(c => int.TryParse(c,out dummy)).Select(c => int.Parse(c));
The int.TryParse(..) just checks if it is a valid string to be translated into an int. The out parameter is just ignored - we cont need it.
We know that only those string values that "makes-it" to the Select() are values that can be safetly parsed as int's.

Why not to use Where on array and only then select proper ints
csv.Split(',')
.Where(item =>
{
int parsed;
return int.TryParse(item, out parsed);
})
.Select(item => Convert.ToInt32(item));

I would probably just use:
csv.Split(',').Where(item => isValid(item)).Select(item => TransformationExpression(item));
or,
csv.Split(',').Select(item => ReturnsDummyValueIfInvalid(item)).Where(item => item != DummyValue);

int TempInt;
List<int> StuffIWant = csv.Split(',').Where(item => int.TryParse(item, TempInt)).ToList();

Related

Query IEnumerable as IEnumerable<Type>

I have a problem I need to solve efficiently.
I require the index of an element in an IEnumerable source, one way I could do this is with the following
var items = source.Cast<ObjectType>().Where(obj => obj.Start == forDate);
This would give me an IEnumerable of all the items that match the predicate.
if(items != null && items.Any()){
// I now need the ordinal from the original list
return source.IndexOf(items[0]);
}
However, the list could be vast and the operation will be carried out many times. I believe this is inefficient and there must be a better way to do this.
I would be grateful if anyone can point me in the correct direction.
Sometimes, it's good to forget about Linq and go back to basics:
int index = 0;
foeach (ObjectType element in source)
{
if (element.Start == forDate)
{
return index;
}
index++;
}
// No element found
Using Linq, you can take the index of each object before filtering them:
source
.Cast<ObjectType>()
.Select((obj, i) => new { Obj = obj, I = i })
.Where(x => x.Obj.Start == forDate)
.Select(x => x.I)
.FirstOrDefault();
However, this is not really efficient, the following will do the same without allocations:
int i = 0;
foreach (ObjectType obj in source)
{
if (obj.Start == forDate)
{
return i;
}
i++;
}
Your second code sample was invalid: since items is an IEnumerable, you cannot call items[0]. You can use First(). Anyway:
var items = source.Cast<ObjectType>()
.Select((item, index) => new KeyValuePair<int, ObjectType>(index, item))
.Where(obj => obj.Value.Start == forDate);
and then:
if (items != null && items.Any()) {
return items.First().Key;
}
If you need to do this multiple times I would create a lookup for the indices.
ILookup<DateTime, int> lookup =
source
.Cast<ObjectType>()
.Select((e, i) => new { e, i })
.ToLookup(x => x.e.Start, x => x.i);
Now given a forDate you can do this:
IEnumerable<int> indices = lookup[forDate];
Since the lookup is basically like a dictionary that returns multiple values you get the results instantly. So repeating this for multiple values is super fast.
And since this returns IEnumerable<int> you know when there are duplicate values within the source list. If you only need the first one then just do a .First().

Parsing invalid integers as null within orderby

var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x => int.Parse(x)).ToArray();
Can anybody advise how to amend the above code so that a null value is returned for every value that fails to parse as an integer?
I think I need to replace Parse with TryParse somehow?
Clarification:
The program accepts 3 integers from 3 different textboxes, sorts them and inserts the sequence into a database. If a non-integer is entered, I wanted to treat it as a null value.
For example, if TextBox1.Text = "", TextBox2.Text = "45" and TextBox3.Text = "8", the sequence inserted would be: 0,8,45.
However, I now think it might be better to exclude non-integers from the sort so for the same example, the resulting sequence would be something like: 8,45,N/A.
Apologies for not being able to explain my requirements clearly.
If you're really using LINQ to Objects, I'd write a separate method:
public static int? ParseOrNull(string text)
{
int result;
return int.TryParse(text, out result) ? (int?) result : null;
}
Then:
list = list.OrderBy(x => ParseOrNull(x)).ToArray();
This will cope with text values which are either genuine string references to non-numbers, or null references. You might want to overload ParseOrNull to accept an IFormatProvider.
This is just ordering by a nullable int, however. If you want values which invalid replaced with null, but other values to remain as strings (ordered by the numeric value) I suspect you want something more like:
var result = list.Select(x => new { Text = x, Numeric = ParseOrNull(x) })
.OrderBy(pair => pair.Numeric)
.Select(pair => pair.Numeric.HasValue ? pair.Text : null)
.ToArray();
If neither of these does what you want, please clarify your requirements.
Note that none of this will work with something like LINQ to SQL or Entity Framework, which wouldn't be able to translate your method into SQL.
Try this:
var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x =>
{
int val;
return int.TryParse(x, out val) ? (int?)val : null;
}).ToArray();
As I understand the requirements and reading your code (which assigns the result to the same array), you still want strings as output, but ordered by their numeric value, and the strings that aren't parseable you want in the resulting array as null;
var result =
list
.Select(x => { int tmp; return Int32.TryParse(x, out tmp) ? x : null; })
.OrderBy(x => x);
Try this:
var list = new string[] { TextBox1.Text, TextBox2.Text, TextBox3.Text };
list = list.OrderBy(x => ParseStringToInt(x)).ToArray();
private int ParseStringToInt(string value)
{
int result;
int.TryParse(value, out result);
return result;
}

linq parsing ints from ArrayList

I have an ArrayList that comes from a json deserializer. This array should only contain numbers but as always, bad things can happen and I'm looking to avoid having to handle an exception. This is what I have:
var TheListOfLongs = (from object s in TheArrayList
select Convert.ToInt64(s)).ToList<long>();
This works fine as long as TheArrayList only contains numbers. How can I change the Convert.ToInt64 statement to a TryParse?
Thanks.
long outValue;
//will work, but double conversion
var result = TheArrayList.Cast<object>()
.Where(m => Int64.TryParse(m.ToString(), out outValue))
.Select(m => Convert.ToInt64(m)).ToList();
//should avoid double Parse, but untested, see Daniel Hilgarth's answer and warnings.
var result = TheArrayList.Cast<object>()
.Where(m => Int64.TryParse(m.ToString(), out outValue))
.Select(m => outValue).ToList();
or good old foreach, which is probably the best choice.
var list = new List<long>();
long outValue;
foreach (object value in the ArayList) {
if (Int64.TryParse(value.ToString(), out outValue))
list.Add(outValue);
}
You could
Int64 value;
var TheListOfLongs = TheArrayList.
Where(entry => Int64.TryParse(entry, out value)).
Select(entry=> Convert.ToInt64(entry)).ToList<long>()
I'd just loop through, no need for linq
bool allParsed = true;
List<long> longList = new List<long>();
foreach(object s in TheArrayList)
{
long val;
if(Int64.TryParse(s, out val))
longList.Add(val);
else
{
allParsed = false;
break;
}
}
if(!allParsed)
// handle the error
You can make use of the deferred nature of LINQ to Objects:
long value = 0;
var result = TheArrayList.Where(m => long.TryParse(m, out value))
.Select(x => value);
This works, but it is problematic in other ways:
It will break if you want to convert that code to PLINQ.
It will break if TheArrayList is actually some IQueryable going to a database.
It looks like it's buggy

Using LINQ to extract ints from a list of strings

I have a List<string>, and some of these strings are numbers. I want to extract this subset into a List<int>.
I have done this in quite a verbose looking way - as below - but I get the feeling there must be a neater LINQ way to structure this. Any ideas?
List<string> myStrs = someListFromSomewhere;
List<int> myInts = new List<int>();
foreach (string myStr in myStrs)
{
int outInt;
if (int.TryParse(myStr, out outInt))
{
myInts.Add(outInt);
}
}
Obviously I don't need a solution to this - it's mainly for my LINQ education.
You can do it like this:
int parsed = 0;
myInts = myStrs.Where(x => int.TryParse(x, out parsed)).Select(x => parsed);
This works because the execution of LINQ operators is deferred, meaning:
For each item in myStrs first the code in Where is executed and the result written into parsed. And if TryParse returned true the code in Select is executed. This whole code for one item runs before this whole code is run for the next item.
LINQ and out parameters don't mix well. You could do this:
var myInts = myStrs
.Select(s =>
{
int outInt;
return int.TryParse(s, out outInt) ? (int?)outInt : null;
})
.Where(i => i.HasValue)
.Select(i => i.Value)
.ToList();
Since this is for LINQ education... If you really are looking for how this can be done with only LINQ syntax, Another option is to move the parsing logic to a class that encapsulates the result and the value.
var myInts = from val in myStrs
let parserVal = new Parser(val)
where parserVal.IsInt
select parserVal.Val;
where Parser is something like this...
class Parser
{
public bool IsInt { get; set; }
public int Val { get; set; }
public Parser(string val)
{
int outVal;
IsInt = int.TryParse(val, out outVal);
if (IsInt)
{
Val = outVal;
}
}
}
Select distinct then parse each selected item
List<int> myInts = new List<int>();
myInts = myStrs.Distinct().Select(
x =>
{
int parsed;
int.TryParse(x, out parsed);
return parsed;
})
.ToList();

Max return value if empty query

I have this query:
int maxShoeSize = Workers
.Where(x => x.CompanyId == 8)
.Max(x => x.ShoeSize);
What will be in maxShoeSize if company 8 has no workers at all?
UPDATE:
How can I change the query in order to get 0 and not an exception?
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
.Select(x => x.ShoeSize)
.DefaultIfEmpty(0)
.Max();
The zero in DefaultIfEmpty is not necessary.
I know this is an old question and the accepted answer works, but this question answered my question about whether such an empty set would result in an exception or a default(int) result.
The accepted answer however, while it does work, isn't the ideal solution IMHO, which isn't given here. Thus I am providing it in my own answer for the benefit of anyone who is looking for it.
The OP's original code was:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);
This is how I would write it to prevent exceptions and provide a default result:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;
This causes the return type of the Max function to be int?, which allows the null result and then the ?? replaces the null result with 0.
EDIT
Just to clarify something from the comments, Entity Framework doesn't currently support the as keyword, so the way to write it when working with EF would be:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;
Since the [TypeOfWorkers] could be a long class name and is tedious to write, I've added an extension method to help out.
public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
return source.Max(selector) ?? nullValue;
}
This only handles int, but the same could be done for long, double, or any other value type you need. Using this extension method is very simple, you just pass in your selector function and optionally include a value to be used for null, which defaults to 0. So the above could be rewritten like so:
int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);
Hopefully that helps people out even more.
Max() won't return anything in that case.
It will raise InvalidOperationException since the source contains no elements.
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
.Select(x => x.ShoeSize)
.DefaultIfEmpty()
.Max();
If this is Linq to SQL, I don't like to use Any() because it results in multiple queries to SQL server.
If ShoeSize is not a nullable field, then using just the .Max(..) ?? 0 will not work but the following will:
int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;
It absolutely does not change the emitted SQL, but it does return 0 if the sequence is empty because it changes the Max() to return an int? instead of an int.
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
.Max(x=>(int?)x.ShoeSize).GetValueOrDefault();
(assuming that ShoeSize is of type int)
If Workers is a DbSet or ObjectSet from Entity Framework your initial query would throw an InvalidOperationException, but not complaining about an empty sequence but complaining that the materialized value NULL can't be converted into an int.
Max will throw System.InvalidOperationException "Sequence contains no elements"
class Program
{
static void Main(string[] args)
{
List<MyClass> list = new List<MyClass>();
list.Add(new MyClass() { Value = 2 });
IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.
int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
}
}
class MyClass
{
public int Value;
}
NB: the query with DefaultIfEmpty() may be significantly slower.
In my case that was a simple query with .DefaultIfEmpty(DateTime.Now.Date).
I was too lazy to profile it but obviously EF tried to obtain all the rows and then take the Max() value.
Conclusion: sometimes handling InvalidOperationException might be the better choice.
You can use a ternary within .Max() to handle the predicate and set its value;
// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);
You would need to handle the Workers collection being null/empty if that's a possibility, but it would depend on your implementation.
You can try this:
int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;
You could check if there are any workers before doing the Max().
private int FindMaxShoeSize(IList<MyClass> workers) {
var workersInCompany = workers.Where(x => x.CompanyId == 8);
if(!workersInCompany.Any()) { return 0; }
return workersInCompany.Max(x => x.ShoeSize);
}

Categories

Resources