Split string into List<int> ignore none int values - c#

Am using the following code to split a string into a List<int>, however occasionally the string includes non integer values, which are handled differently.
An example string might be like: 1,2,3,4,x
code looks like:
List<int> arrCMs = new List<int>(strMyList.Split(',').Select(x => int.Parse(x)));
The problem is as soon as it hits the 'x' it throws an error because 'x' can't be parsed as an integer.
How can I make it ignore non integer values? I'm sure I should be able to do something with int.TryParse but can't quite figure it out.
Thanks

List<int> arrCMs = strMyList.Split(',')
.Select(possibleIntegerAsString => {
int parsedInteger = 0;
bool isInteger = int.TryParse(possibleIntegerAsString , out parsedInteger);
return new {isInteger, parsedInteger};
})
.Where(tryParseResult => tryParseResult.isInteger)
.Select(tryParseResult => tryParseResult.parsedInteger)
.ToList();
The first Select in the above example returns an anonymous type that describes the result of int.TryParse - that is, whether it was a valid integer, and if so, what the value was.
The Where clause filters out those that weren't valid.
The second Select then retrieves the parsed values from the strings that were able to be parsed.

Short and sweet, using int.TryParse:
List<int> nums = list
.Split(',')
.Select(i =>
{
int val;
return int.TryParse(i, out val) ? (int?)val : null;
})
.Where(i => i.HasValue)
.Cast<int>()
.ToList()
Working Example: http://dotnetfiddle.net/4wyoAM

Change this
int result;
List<int> arrCMs =
new List<int>(strMyList.Split(',')
.Where(x => int.TryParse(x, out result))
.Select(int.Parse));

another one, using Array.ForEach
List<int> ints = new List<int>();
Array.ForEach(strMyList.Split(','), s =>
{
int i;
if (int.TryParse(s, out i)){ ints.Add(i);}
});

Plot twist: use an old-school foreach loop.
List<int> arrCMs = new List<int>();
foreach (string str in strMyList.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
int res;
if (int.TryParse(str, out res))
{
arrCMs.Add(res);
}
}
You could also create a method for it and make use of an iterator block by using yield return:
public static IEnumerable<int> ParseIntegers(string val, char seperator = ',')
{
foreach (string str in val.Split(new [] { seperator }, StringSplitOptions.RemoveEmptyEntries))
{
int res;
if (int.TryParse(str, out res))
{
yield return res;
}
}
}
This is probably over-engineering if it's only for one time use.

Parse once only for each value, but a bit tricky.
int result = 0;
List<int> arrCMs = strMyList.Split(',')
.Where(x => int.TryParse(x, out result))
.Select(x => result)
.ToList();

Related

Sort a List in which each element contains 2 Values

I have a text file that contains Values in this Format: Time|ID:
180|1
60 |2
120|3
Now I want to sort them by Time. The Output also should be:
60 |2
120|3
180|1
How can I solve this problem? With this:
var path = #"C:\Users\admin\Desktop\test.txt";
List<string> list = File.ReadAllLines(path).ToList();
list.Sort();
for (var i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
I got no success ...
3 steps are necessary to do the job:
1) split by the separator
2) convert to int because in a string comparison a 6 comes after a 1 or 10
3) use OrderBy to sort your collection
Here is a linq solution in one line doing all 3 steps:
list = list.OrderBy(x => Convert.ToInt32(x.Split('|')[0])).ToList();
Explanation
x => lambda expression, x denotes a single element in your list
x.Split('|')[0] splits each string and takes only the first part of it (time)
Convert.ToInt32(.. converts the time into a number so that the ordering will be done in the way you desire
list.OrderBy( sorts your collection
EDIT:
Just to understand why you got the result in the first place here is an example of comparison of numbers in string representation using the CompareTo method:
int res = "6".CompareTo("10");
res will have the value of 1 (meaning that 6 is larger than 10 or 6 follows 10)
According to the documentation->remarks:
The CompareTo method was designed primarily for use in sorting or alphabetizing operations.
You should parse each line of the file content and get values as numbers.
string[] lines = File.ReadAllLines("path");
// ID, time
var dict = new Dictionary<int, int>();
// Processing each line of the file content
foreach (var line in lines)
{
string[] splitted = line.Split('|');
int time = Convert.ToInt32(splitted[0]);
int ID = Convert.ToInt32(splitted[1]);
// Key = ID, Value = Time
dict.Add(ID, time);
}
var orderedListByID = dict.OrderBy(x => x.Key).ToList();
var orderedListByTime = dict.OrderBy(x => x.Value).ToList();
Note that I use your ID reference as Key of dictionary assuming that ID should be unique.
Short code version
// Key = ID Value = Time
var orderedListByID = lines.Select(x => x.Split('|')).ToDictionary(x => Convert.ToInt32(x[1]), x => Convert.ToInt32(x[0])).OrderBy(x => x.Key).ToList();
var orderedListByTime = lines.Select(x => x.Split('|')).ToDictionary(x => Convert.ToInt32(x[1]), x => Convert.ToInt32(x[0])).OrderBy(x => x.Value).ToList();
You need to convert them to numbers first. Sorting by string won't give you meaningful results.
times = list.Select(l => l.Split('|')[0]).Select(Int32.Parse);
ids = list.Select(l => l.Split('|')[1]).Select(Int32.Parse);
pairs = times.Zip(ids, (t, id) => new{Time = t, Id = id})
.OrderBy(x => x.Time)
.ToList();
Thank you all, this is my Solution:
var path = #"C:\Users\admin\Desktop\test.txt";
List<string> list = File.ReadAllLines(path).ToList();
list = list.OrderBy(x => Convert.ToInt32(x.Split('|')[0])).ToList();
for(var i = 0; i < list.Count; i++)
{
Console.WriteLine(list[i]);
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestClass {
public static void main(String[] args) {
List <LineItem> myList = new ArrayList<LineItem>();
myList.add(LineItem.getLineItem(500, 30));
myList.add(LineItem.getLineItem(300, 20));
myList.add(LineItem.getLineItem(900, 100));
System.out.println(myList);
Collections.sort(myList);
System.out.println("list after sort");
System.out.println(myList);
}
}
class LineItem implements Comparable<LineItem>{
int time;
int id ;
#Override
public String toString() {
return ""+ time + "|"+ id + " ";
}
#Override
public int compareTo(LineItem o) {
return this.time-o.time;
}
public static LineItem getLineItem( int time, int id ){
LineItem l = new LineItem();
l.time=time;
l.id=id;
return l;
}
}

Use Linq to break a list by special values?

I'm trying to use Linq to convert IEnumerable<int> to IEnumerable<List<int>> - the input stream will be separated by special value 0.
IEnumerable<List<int>> Parse(IEnumerable<int> l)
{
l.Select(x => {
.....; //?
return new List<int>();
});
}
var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
Parse(l) // returns {{1,3,5}, {3, 4}, {1,4}}
How to implement it using Linq instead of imperative looping?
Or is Linq not good for this requirement because the logic depends on the order of the input stream?
Simple loop would be good option.
Alternatives:
Enumerable.Aggregate and start new list on 0
Write own extension similar to Create batches in linq or Use LINQ to group a sequence of numbers with no gaps
Aggregate sample
var result = list.Aggregate(new List<List<int>>(),
(sum,current) => {
if(current == 0)
sum.Add(new List<int>());
else
sum.Last().Add(current);
return sum;
});
Note: this is only sample of the approach working for given very friendly input like {0,1,2,0,3,4}.
One can even make aggregation into immutable lists but that will look insane with basic .Net types.
Here's an answer that lazily enumerates the source enumerable, but eagerly enumerates the contents of each returned list between zeroes. It properly throws upon null input or upon being given a list that does not start with a zero (though allowing an empty list through--that's really an implementation detail you have to decide on). It does not return an extra and empty list at the end like at least one other answer's possible suggestions does.
public static IEnumerable<List<int>> Parse(this IEnumerable<int> source, int splitValue = 0) {
if (source == null) {
throw new ArgumentNullException(nameof (source));
}
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext()) {
return Enumerable.Empty<List<int>>();
}
if (enumerator.Current != splitValue) {
throw new ArgumentException(nameof (source), $"Source enumerable must begin with a {splitValue}.");
}
return ParseImpl(enumerator, splitValue);
}
}
private static IEnumerable<List<int>> ParseImpl(IEnumerator<int> enumerator, int splitValue) {
var list = new List<int>();
while (enumerator.MoveNext()) {
if (enumerator.Current == splitValue) {
yield return list;
list = new List<int>();
}
else {
list.Add(enumerator.Current);
}
}
if (list.Any()) {
yield return list;
}
}
This could easily be adapted to be generic instead of int, just change Parse to Parse<T>, change int to T everywhere, and use a.Equals(b) or !a.Equals(b) instead of a == b or a != b.
You could create an extension method like this:
public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, T value)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { };
//In case the source doesn't start with 0
if (!e.Current.Equals(value))
{
list.Add(e.Current);
}
while (e.MoveNext())
{
if ( !e.Current.Equals(value))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { };
}
}
//In case the source doesn't end with 0
if (list.Count>0)
{
yield return list;
}
}
}
}
Then, you can do the following:
var l = new List<int> { 0, 1, 3, 5, 0, 3, 4, 0, 1, 4, 0 };
var result = l.SplitBy(0);
You could use GroupBy with a counter.
var list = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
int counter = 0;
var result = list.GroupBy(x => x==0 ? counter++ : counter)
.Select(g => g.TakeWhile(x => x!=0).ToList())
.Where(l => l.Any());
Edited to fix possibility of zeroes within numbers
Here is a semi-LINQ solution:
var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
string
.Join(",", l.Select(x => x == 0 ? "|" : x.ToString()))
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
This is probably not preferable to using a loop due to performance and other reasons, but it should work.

C# array for finding all the 2's

Basically what I have to do is find a certain number, which in this case is 2, and see how many times I have that number in my program, I assumed that I would have to use a .GetValue(42) but it's not doing it right, the code I am using is
static int count2(int[] input)
{
return input.GetValue(2);
}
input is from a separate method, but it contains the values that I'm working with which is
int [] input = {1,2,3,4,5};
Not sure if you count specifically the number 2, or any number that contains the number 2.
For the later here's the easy way:
public int count2(int[] input) {
int counter = 0;
foreach(var i in input) {
if (i.ToString().Contains("2"))
{
++counter;
}
}
return counter;
}
You can do it with LINQ
input.Count(x=>x==2);
Array.GetValue() "gets the value at the specified position in the one-dimensional Array" which is not what you want. (in your example it will return 3 because that's the value at index 2 of your array).
You want to count the number of times a specific item is in the array. That's a matter of looping and checking each item:
var counter = 0;
foreach(var item in input)
{
if(item == 2)
{
counter++;
}
}
return counter;
to get a count do this
int [] inputDupes = {1,2,3,4,5,2};
var duplicates = inputDupes
.Select(w => inputDupes.Contains(2))
.GroupBy(q => q)
.Where(gb => gb.Count() > 1)
.Select(gb => gb.Key).Count();//returns an Int32 value
to see if there are duplicates of the number 2 then do the following
int [] inputDupes = {1,2,3,4,5,2};
var duplicates = inputDupes
.Select(w => inputDupes.Contains(2))
.GroupBy(q => q)
.Where(gb => gb.Count() > 1)
.Select(gb => gb.Key)
.ToList(); //returns true | false
if you want to do this based on any number then create a method and pass a param in where .Contains() extension method is being called
if you want to capture user input from Console you can do it this way as well
int [] inputDupes = {1,2,3,4,5,2};
Console.WriteLine("Enter a number to check for duplicates: ");
string input = Console.ReadLine();
int number;
Int32.TryParse(input, out number);
var dupeCount = inputDupes.Count(x => x == number);
Console.WriteLine(dupeCount);
Console.Read();
Yields 2 for the duplicate Count
static int count2(int[] input)
{
return input.Count(i => i == 2);
}
You could use a Func like this:
public Func<int[], int, int> GetNumberCount =
(numbers, numberToSearchFor) =>
numbers.Count(num => num.Equals(numberToSearchFor));
...
var count = GetNumberCount(input, 2);
Gotta' love a Func :)

How to perform word search using LINQ?

I have a list which contains the name of suppliers. Say
SuppId Supplier Name
----------------------------------
1 Aardema & Whitelaw
2 Aafedt Forde Gray
3 Whitelaw & Sears-Ewald
using following LINQ query
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey));
I can return records correctly in the following conditions,
1) If i am using search string as "Whitelaw & Sears-Ewald" it will return 3rd record.
2) If i am using "Whitelaw" or "Sears-Ewald" it will return 3rd record.
But how can i return 3rd record if i am giving search string as "Whitelaw Sears-Ewald". It always returns 0 records.
Can i use ALL to get this result, but i dont know how to use it for this particular need.
What I usually do in this situation is split the words into a collection, then perform the following:
var searchopts = SearchKey.Split(' ').ToList();
supplierListQuery = supplierListQuery
.Where(x => searchopts.Any(y=> x.SupplierName.Contains(y)));
This works for me:
IEnumerable<string> keyWords = SearchKey.Split('');
supplierListQuery = supplierListQuery
.AsParallel()
.Where
(
x => keyWords.All
(
keyword => x.SupplierName.ContainsIgnoreCase(keyword)
)
);
Thank you all for your quick responses. But the one which worked or a easy fix to handle this was timothyclifford's note on this. Like he said i alterd my answer to this
string[] filters = SearchKey.ToLower().Split(new[] { ' ' });
objSuppliersList = (from x in objSuppliersList
where filters.All(f => x.SupplierName.ToLower().Contains(f))
select x).ToList();
Now it returns the result for all my serach conditions.
Because "Whitelaw" appears in both you will get both records. Otherwise there is no dynamic way to determine you only want the last one. If you know you only have these 3 then append .Last() to get the final record.
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey.Split(' ')[0]));
You need to use some sort of string comparer to create your own simple Search Engine and then you can find strings that are most likely to be included in your result :
public static class SearchEngine
{
public static double CompareStrings(string val1, string val2)
{
if ((val1.Length == 0) || (val2.Length == 0)) return 0;
if (val1 == val2) return 100;
double maxLength = Math.Max(val1.Length, val2.Length);
double minLength = Math.Min(val1.Length, val2.Length);
int charIndex = 0;
for (int i = 0; i < minLength; i++) { if (val1.Contains(val2[i])) charIndex++; }
return Math.Round(charIndex / maxLength * 100);
}
public static List<string> Search(this string[] values, string searchKey, double threshold)
{
List<string> result = new List<string>();
for (int i = 0; i < values.Length; i++) if (CompareStrings(values[i], searchKey) > threshold) result.Add(values[i]);
return result;
}
}
Example of usage :
string[] array = { "Aardema & Whitelaw", "Aafedt Forde Gray", "Whitelaw & Sears-Ewald" };
var result = array.Search("WhitelawSears-Ewald", 80);
// Results that matches this string with 80% or more
foreach (var item in result)
{
Console.WriteLine(item);
}
Output: Whitelaw & Sears-Ewald
If you want an easy (not very handy) solution,
var result = supplierListQuery
.Select(x => normalize(x.SupplierName))
.Where(x => x.Contains(normalize(SearchKey)));
string normalize(string inputStr)
{
string retVal = inputStr.Replace("&", "");
while (retVal.IndexOf(" ") >= 0)
{
retVal = retVal.Replace(" ", " ");
}
return retVal;
}

Split string, convert ToList<int>() in one line

I have a string that has numbers
string sNumbers = "1,2,3,4,5";
I can split it then convert it to List<int>
sNumbers.Split( new[] { ',' } ).ToList<int>();
How can I convert string array to integer list?
So that I'll be able to convert string[] to IEnumerable
var numbers = sNumbers?.Split(',')?.Select(Int32.Parse)?.ToList();
Recent versions of C# (v6+) allow you to do null checks in-line using the null-conditional operator
You can also do it this way without the need of Linq:
List<int> numbers = new List<int>( Array.ConvertAll(sNumbers.Split(','), int.Parse) );
// Uses Linq
var numbers = Array.ConvertAll(sNumbers.Split(','), int.Parse).ToList();
Better use int.TryParse to avoid exceptions;
var numbers = sNumbers
.Split(',')
.Where(x => int.TryParse(x, out _))
.Select(int.Parse)
.ToList();
Joze's way also need LINQ, ToList() is in System.Linq namespace.
You can convert Array to List without Linq by passing the array to List constructor:
List<int> numbers = new List<int>( Array.ConvertAll(sNumbers.Split(','), int.Parse) );
It is also possible to int array to direct assign value.
like this
int[] numbers = sNumbers.Split(',').Select(Int32.Parse).ToArray();
You can use new C# 6.0 Language Features:
replace delegate (s) => { return Convert.ToInt32(s); } with
corresponding method group Convert.ToInt32
replace redundant constructor call: new Converter<string, int>(Convert.ToInt32) with: Convert.ToInt32
The result will be:
var intList = new List<int>(Array.ConvertAll(sNumbers.Split(','), Convert.ToInt32));
also you can use this Extension method
public static List<int> SplitToIntList(this string list, char separator = ',')
{
return list.Split(separator).Select(Int32.Parse).ToList();
}
usage:
var numberListString = "1, 2, 3, 4";
List<int> numberList = numberListString.SplitToIntList(',');
On Unity3d, int.Parse doesn't work well. So I use like bellow.
List<int> intList = new List<int>( Array.ConvertAll(sNumbers.Split(','),
new Converter<string, int>((s)=>{return Convert.ToInt32(s);}) ) );
Hope this help for Unity3d Users.
My problem was similar but with the inconvenience that sometimes the string contains letters (sometimes empty).
string sNumbers = "1,2,hh,3,4,x,5";
Trying to follow Pcode Xonos Extension Method:
public static List<int> SplitToIntList(this string list, char separator = ',')
{
int result = 0;
return (from s in list.Split(',')
let isint = int.TryParse(s, out result)
let val = result
where isint
select val).ToList();
}
Why stick with just int when we have generics?
What about an extension method like :
public static List<T> Split<T>(this string #this, char separator, out bool AllConverted)
{
List<T> returnVals = new List<T>();
AllConverted = true;
var itens = #this.Split(separator);
foreach (var item in itens)
{
try
{
returnVals.Add((T)Convert.ChangeType(item, typeof(T)));
}
catch { AllConverted = false; }
}
return returnVals;
}
and then
string testString = "1, 2, 3, XP, *, 6";
List<int> splited = testString.Split<int>(',', out _);
You can use this:
List<Int32> sNumberslst = sNumbers.Split(',').ConvertIntoIntList();

Categories