linq parsing ints from ArrayList - c#

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

Related

Change variable in linq

I have a query something like this
function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
if(listDataPoints.Count == 0)
return;
var startPoint = new CustomObject();
startPoint = listDataPoint.First();
List<CustomObject2> cObjList = from r in listDataPoints
where r != null && r.GetDistance(startPoint) > 100
select new CustomObject2
{
Var1 = r.Var1
}.ToList()
}
The problem here is that, in the beginning the startPoint is set to the first object in listDataPoint. However, after the comparison in the query (GetDistance) I want to reassign startPoint to the value of "r" if the Distance is greater than 100.
Is there any way to do so?
Thanks in advance
No, there is no clean way to do that.
LINQ is essentially a piece of functional programming that has been brought into C#. In functional programming values are immutable (they cannot be changed). Thanks to being functional and using immutality, LINQ queries can be lazily evaluated. It is not uncommon for a LINQ query to be only partly run, or for some parts of the sequence to be evaluated several times. That is safe to do thanks to immutability.
As soon as you want to change a value, you are working against LINQ. In this case you are much better off with a for loop.
Of course there are ways to solve this in a functional manner, as it is possible to solve this in a purely functional language. But in C# it is much cleaner to use a for loop.
You can use a fold:
var cObjList = listDataPoints.Where(r => r != null)
.Aggregate(Tuple.Create(startPoint, new List<CustomObject2>()), (acc, r) => {
if(r.GetDistance(acc.Item1)) {
acc.Item2.Add(new CustomObject2 { Var1 = r.Var1 });
return Tuple.Create(r, acc.Item2);
}
else return acc;
}).Item2;
Since you were not-null checking the elements from listDataPoints, so I assume it may contain null objects. In this case, your code may be vulnerable when the First() element from the list is empty.
//there is no function or procedure in c#;
//function List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
List<CustomObject2> GetDataPoint(List<CustomObject> listDataPoints)
{
var dataPoints = listDataPoints.Where(r => r != null);
if (dataPoints.Empty())
//return; you cant not return anything in a function
return null; //or return an empty list
//return new List<CustomObject2>();
var cObjList = dataPoints.Aggregate(
new Stack<CustomObject>(),
(results, r) =>
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(r);
return results;
})
.Select(r => new CustomObject2(){ Var1 = r.Var1 })
.ToList();
//return directly the line above or do more work with cObjList...
}
Yet, this is still messy and not easily maintained. Like Anders Abel suggests, you are best to go with the for loop for this case :
var cObjList= new List<CustomObject2>();
foreach(var r in dataPoints)
{
if (r.GetDistance(results.Peek()) > 100)
results.Add(new CustomObject2(){ Var1 = r.Var1 });
}
//...
return cObjList;

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;
}

How to omit a value in a select lambda?

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

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

What is the shortest way to compare if two IEnumerable<T> have the same items in C#? [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
Test whether two IEnumerable<T> have the same values with the same frequencies
I wrote
UPDATED - correction:
static bool HaveSameItems<T>(this IEnumerable<T> self, IEnumerable<T> other)
{
return !
(
other.Except(this).Any() ||
this.Except(other).Any()
);
}
Isn't there a shorter way?
I know there is SequenceEqual but the order doesn't matter for me.
Even if the order doesn't matter to you, it doesn't rule out SequenceEqual as a viable option.
var lst1 = new [] { 2,2,2,2 };
var lst2 = new [] { 2,3,4,5 };
var lst3 = new [] { 5,4,3,2 };
//your current function which will return true
//when you compare lst1 and lst2, even though
//lst1 is just a subset of lst2 and is not actually equal
//as mentioned by Wim Coenen
(lst1.Count() == lst2.Count() &&
!lst1.Except(lst2).Any()); //incorrectly returns true
//this also only checks to see if one list is a subset of another
//also mentioned by Wim Coenen
lst1.Intersect(lst2).Any(); //incorrectly returns true
//So even if order doesn't matter, you can make it matter just for
//the equality check like so:
lst1.OrderBy(x => x).SequenceEqual(lst2.OrderBy(x => x)); //correctly returns false
lst3.OrderBy(x => x).SequenceEqual(lst2.OrderBy(x => x)); // correctly returns true
Here's an O(n) solution that only walks each sequence once (in fact, it might not even completely walk the second sequence, it has early termination possibilities):
public static bool HaveSameItems<T>(IEnumerable<T> a, IEnumerable<T> b) {
var dictionary = a.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
foreach(var item in b) {
int value;
if (!dictionary.TryGetValue(item, out value)) {
return false;
}
if (value == 0) {
return false;
}
dictionary[item] -= 1;
}
return dictionary.All(x => x.Value == 0);
}
One downside to this solution is that it's not going to interop with LINQ to SQL, EF, NHiberate etc. nicely.

Categories

Resources