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();
Related
I put the following code segment in .NET Fiddle but it printed out System.Linq.Enumerable+WhereArrayIterator1[System.String] I'd like to print out each content in result, in order to understand how Select works. Can someone please help to point out what the problem is? Many thanks!
string[] sequ1 = { "abcde", "fghi", "jkl", "mnop", "qrs" };
string[] sequ2 = { "abc", "defgh", "ijklm", "nop" };
var result =sequ1.Select( n1 => sequ2.Where(n2 => n1.Length < n2.Length) );
foreach( var y in result)
{
Console.WriteLine(y);
}
You are actually returning a collection of collections.
sequ1.Select( n1 => sequ2.Where(n2 => n1.Length < n2.Length) );
For each element in sequ1, this statement filters sequ2 to find all of the elements from the second sequence where the current value in the first sequence is shorter than it and then maps to a new collection containing each of those results.
To describe what Select is actually doing:
You start with a collection of things. In your case: sequ1 which has type IEnumerable<string>
You supply it with a function, this function takes an argument of the type of thing you supplied it with a collection of and has a return type of some other thing, in your case:
fun n1 => sequ2.Where(n2 => n1.Length < n2.Length)
Your function takes a string and returns an IEnumerable<string>
Finally, it returns a result containing a collection of each element in the original collection transformed to some new element by the function you supplied it with.
So you started with IEnumerable<string> and ended up with IEnumerable<IEnumerable<string>>.
That means you have a collection for each value that appears in sequ1.
As such, you would expect the result to be:
{{}, {"defgh", "ijklm"}, {"defgh", "ijklm"}, {"defgh", "ijklm"}, {"defgh", "ijklm"}}
You can inspect the results by adding another loop.
foreach(var y in result)
{
foreach(var z in result)
{
Console.WriteLine(z);
}
}
Change your Select to SelectMany:
var result = sequ1.SelectMany(n1 => sequ2.Where(n2 => n1.Length < n2.Length));
I may be wrong, but I think the OP wants to compare both arrays, and for each element, print the longest one.
If that's the case, I would do it as follows:
var result = sequ1.Take(sequ2.Length)
.Select((n1, i) =>
(n1.Length > sequ2.ElementAt(i).Length)
? n1
: sequ2.ElementAt(i));
Explanation:
Use Take to only go as long as the length of the second array, and avoid nullreference exceptions later on.
Use Select, with two arguments, the first is the string, the second is the index.
Use ElementAt to find the corresponding element in sequ2
I don't know about this example is about to help you to understand how select work. A more simple exmaple what i think is this.
public class Person {
public string Name { get; set; }
public string LastName { get; set; }
}
public class Test {
public Test() {
List<Person> persons = new List<Person>();
persons.Add(new Person() { Name = "Person1",LastName = "LastName1" });
persons.Add(new Person() { Name = "Person2",LastName = "LastName2" });
var getNamesFromPersons = persons.Select(p => p.Name);
}
}
If you are beginning c#, you need to sideline the keyword "var" from your code.
Force yourself to write out what the variables really are:
If you forego the use of var, you would have seen why your code was Console.Writing what it did.
string[] sequ1 = { "abcde", "fghi", "jkl", "mnop", "qrs", };
string[] sequ2 = { "abc", "defgh", "ijklm", "nop", };
IEnumerable<IEnumerable<string>> result = sequ1.Select(n1 => sequ2.Where(n2 => n1.Length < n2.Length));
foreach (IEnumerable<string> y in result)
{
foreach (string z in y)
{
Console.WriteLine(z);
}
}
I have two lists:
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
I want do to something like
var newData = data1.intersect(data2, lambda expression);
The lambda expression should return true if data1[index].ToString() == data2[index]
You need to first transform data1, in your case by calling ToString() on each element.
Use this if you want to return strings.
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
var newData = data1.Select(i => i.ToString()).Intersect(data2);
Use this if you want to return integers.
List<int> data1 = new List<int> {1,2,3,4,5};
List<string> data2 = new List<string>{"6","3"};
var newData = data1.Intersect(data2.Select(s => int.Parse(s));
Note that this will throw an exception if not all strings are numbers. So you could do the following first to check:
int temp;
if(data2.All(s => int.TryParse(s, out temp)))
{
// All data2 strings are int's
}
If you have objects, not structs (or strings), then you'll have to intersect their keys first, and then select objects by those keys:
var ids = list1.Select(x => x.Id).Intersect(list2.Select(x => x.Id));
var result = list1.Where(x => ids.Contains(x.Id));
From performance point of view if two lists contain number of elements that differ significantly, you can try such approach (using conditional operator ?:):
1.First you need to declare a converter:
Converter<string, int> del = delegate(string s) { return Int32.Parse(s); };
2.Then you use a conditional operator:
var r = data1.Count > data2.Count ?
data2.ConvertAll<int>(del).Intersect(data1) :
data1.Select(v => v.ToString()).Intersect(data2).ToList<string>().ConvertAll<int>(del);
You convert elements of shorter list to match the type of longer list. Imagine an execution speed if your first set contains 1000 elements and second only 10 (or opposite as it doesn't matter) ;-)
As you want to have a result as List, in a last line you convert the result (only result) back to int.
public static List<T> ListCompare<T>(List<T> List1 , List<T> List2 , string key )
{
return List1.Select(t => t.GetType().GetProperty(key).GetValue(t))
.Intersect(List2.Select(t => t.GetType().GetProperty(key).GetValue(t))).ToList();
}
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;
}
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();
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