Parsing String Into List of Integers with Ranges - c#

I have a bunch of strings I would like to parse that all look like this:
"1001, 1003, 1005-1010"
"1015"
"900-903"
"200, 202-209, 211-220"
Sometimes these strings will be just one integer, sometimes multiple separated by commas, and sometimes a range, and the latter two can appear simultaneously in a single string in any order.
What I would like to do is create a function that takes in the string and returns a collection of integers by parsing the string. So for example the first string should return:
[1001, 1003, 1005, 1006, 1007, 1008, 1009, 1010]
What are some smart ways to do this in .NET 4.0?

.NET 4.0 means you got LINQ available, so you should probably use it:
var input = "1001, 1003, 1005-1010";
var results = (from x in input.Split(',')
let y = x.Split('-')
select y.Length == 1
? new[] { int.Parse(y[0]) }
: Enumerable.Range(int.Parse(y[0]), int.Parse(y[1]) - int.Parse(y[0]) + 1)
).SelectMany(x => x).ToList();

Traditional loop that might be easier to read:
string input = "1001, 1003, 1005-1010";
List<int> result = new List<int>();
foreach (string part in input.Split(','))
{
int i = part.IndexOf('-');
if (i == -1)
{
result.Add(int.Parse(part));
}
else
{
int min = int.Parse(part.Substring(0, i));
int max = int.Parse(part.Substring(i + 1));
result.AddRange(Enumerable.Range(min, max - min + 1));
}
}

Related

How can I return a string from an Ienumerable?

I am very new to programming and am taking an Object Oriented Programming class. However, the professor didn't explain how to take an Ienumerable and make it into a string in order to accomplish this question of the assignment:
TODO:
Write a public static C# method named NumSquare that takes a one-dimentional array as input
and creates a LINQ statement that queries the numbers that have a square number graeter than 20 and orders them ascending.
The LINQ query retrieves anonymous objects in which each object contains the number (Num) and its square number (SqrNum).
The method returns the LINQ query as an IEnumerable object.
The anonymous object contains two instance variables named Num and SqrNum.
Input: a one-dimentional integer array.
Output: a LINQ query of type IEnumerable.
Example: Given array A = [3, 4, 10, 5], invoking NumSquare(A) return a LINQ query that once executed will contain:
{Num=5, SqrNum=25},
{Num=10, SqrNum=25}
Here's what I have so far, but I've tried several things over the last 2 1/2 weeks.
public static IEnumerable<object> NumSquare(int[] A)
{
//write your code here
var num = from Number in A
select Number;
var sqrnum = from Number in A
let squarenum = Number * Number
select squarenum;
return (IEnumerable<object>)sqrnum;
}
I know that this return won't get me the whole result that I need, but that's as far as I can get with no errors. I also don't know how to test anything because he didn't show us how to call an IEnumerable. Help?
I think what you are looking for is not a string as output but as the exercise says an anonymous object. An anonymous object can be something like this:
var o = new { Num = 4, SqrNum = 16 };
Its just an object that basically has no explicit type and some read-only variables.
So what you want to do is to convert your array into a IEnumerable<{int Num, int SqrNum}> which you would have to declare as IEnumerable<object> and not a string.
You could do something like this:
static IEnumerable<object> NumSqr(int[] a)
{
return a
.Where(x => x * x > 20)
.OrderBy(x => x)
.Select(x => new { Num = x, SqrNum= x * x });
}
Alternatively:
static IEnumerable<object> NumSqr(int[] a)
{
return from number in a
where number * number > 20
orderby number
select new { Num = number, SqrNum = number * number };
}
In order to print out the result of the function you could do this:
var a = new int[] { 3, 4, 10, 5 };
var result = NumSqr(a);
foreach (var obj in result)
{
Console.WriteLine(obj);
}
The output should look like this:
{ Num = 5, SqrNum = 25 }
{ Num = 10, SqrNum = 100 }

Filter string array with substring

So I have these values in an Array:
1_642-name.xml
1_642-name2.xml
1_678-name.xml
1_678-name2.xml
I always only want the values with the highest number to be in my array. But i cannot seem to figure out how?
The string consists of these factors:
1 is a static number - And will always only be 1
642 or numbers between _ and - is an identity and can always get larger
name.xml is always the same
I want to filter by the largest identity (678) in this case.
Ive tried something like this without luck:
string[] filter = lines.FindAll(lines, x => x.Substring(3, 3));
Result:
1_678-name.xml
1_678-name2.xml
Because the number of characters in your format can vary easily, this is a great job for Regular Expressions. For example:
var input = "1_642-name2.xml";
var pattern = #"^\d+_(\d+)-.+$";
var match = Regex.Match(input, pattern);
match.Groups[1].Value; // "642" (as a string)
An explanation of the regex string can be found here.
We can use that to extract various parts of each element of your array.
The first thing to do is find the max value, which, if we have this format:
#_###-wordswords
Then we want the number between the _ and the -.
var list = new string[]
{
"1_642-name.xml",
"1_642-name2.xml",
"1_678-name.xml",
"1_678-name2.xml"
};
var pattern = new Regex(#"^\d+_(\d+)-.+$");
var maxValue = list.Max(x => int.Parse(pattern.Match(x).Groups[1].Value));
This finds "678" as the max value. Now we just need to filter the list to only show entries that have "678" in that format slot.
var matchingEntries = list
.Where(x => pattern.Match(x).Groups[1].Value == maxValue.ToString());
foreach (var entry in matchingEntries)
{
Console.WriteLine(entry);
}
The Where filters the list with your max value.
There are a good number of inefficiencies with this code. I'm regex parsing each value twice, and calculating the string equivalent of maxValue on each element. I'll leave fixing those as an exercise to the reader.
Just to provide an alternate to regular expressions, you can also simply parse each line, examine the number, and if it's the largest we've found so far, add the line to a list. Clear the list any time a larger number is found, and then return the list at the end.
A bonus is that we only loop through the list once instead of twice:
public static List<string> GetHighestNumberedLines(List<string> input)
{
if (input == null || !input.Any()) return input;
var result = new List<string>();
var highNum = int.MinValue;
foreach (var line in input)
{
var parts = line.Split('_', '-');
int number;
// Making sure we atually have a number where we expect it
if (parts.Length > 1 && int.TryParse(parts[1], out number))
{
// If this is the highest number we've found, update
// our variable and reset the list to contain this line
if (number > highNum)
{
highNum = number;
result = new List<string> {line};
}
// If this matches our high number, add this line to our list
else if (number == highNum)
{
result.Add(line);
}
}
}
return result;
}

How to treat integers from a string as multi-digit numbers and not individual digits?

My input is a string of integers, which I have to check whether they are even and display them on the console, if they are. The problem is that what I wrote checks only the individual digits and not the numbers.
string even = "";
while (true)
{
string inputData = Console.ReadLine();
if (inputData.Equals("x", StringComparison.OrdinalIgnoreCase))
{
break;
}
for (int i = 0; i < inputData.Length; i++)
{
if (inputData[i] % 2 == 0)
{
even +=inputData[i];
}
}
}
foreach (var e in even)
Console.WriteLine(e);
bool something = string.IsNullOrEmpty(even);
if( something == true)
{
Console.WriteLine("N/A");
}
For example, if the input is:
12
34
56
my output is going to be
2
4
6 (every number needs to be displayed on a new line).
What am I doing wrong? Any help is appreciated.
Use string.Split to get the independent sections and then int.TryParse to check if it is a number (check Parse v. TryParse). Then take only even numbers:
var evenNumbers = new List<int>();
foreach(var s in inputData.Split(" "))
{
if(int.TryParse(s, out var num) && num % 2 == 0)
evenNumbers.Add(num); // If can't use collections: Console.WriteLine(num);
}
(notice the use of out vars introduced in C# 7.0)
If you can use linq then similar to this answer:
var evenNumbers = inputData.Split(" ")
.Select(s => (int.TryParse(s, out var value), value))
.Where(pair => pair.Item1)
.Select(pair => pair.value);
I think you do too many things here at once. Instead of already checking if the number is even, it is better to solve one problem at a time.
First we can make substrings by splitting the string into "words". Net we convert every substring to an int, and finally we filter on even numbers, like:
var words = inputData.Split(' '); # split the words by a space
var intwords = words.Select(int.Parse); # convert these to ints
var evenwords = intwords.Where(x => x % 2 == 0); # check if these are even
foreach(var even in evenwords) { # print the even numbers
Console.WriteLine(even);
}
Here it can still happen that some "words" are not integers, for example "12 foo 34". So you will need to implement some extra filtering between splitting and converting.

Why Is My String Not Converting To An Integer?

I have a list of items, each with numbers, followed by a space, and then a word. Think scrabble.
36 adore
36 adore
27 amigo
31 amino
28 amiss
I am trying to use the 2 digit number as an organizational item to which I can rank the words by order of value.
My list, ComJoined is shown above.
My Code is:
for (int i = 0; i < ComJoined.Count; i++)
{
if (i + 1 <= ComJoined.Count)
{
int one = (Convert.ToInt32(ComJoined[i].Substring(0, 2)));
int two = Convert.ToInt32(ComJoined[i + 1].Substring(0, 2));
if (one <= two)
{
string Stuff = ComJoined[i];
ComJoined.Insert(i + 1, Stuff);
ComJoined.RemoveAt(i);
}
}
}
For some reason it says that "Input string was not in a correct format." I read that this meant the string didn't have a int value, but the part being converted, the first two digits, obviously do. Why is this occurring?
So you want to sort the list descending, according to the 2 digit code, and all items start with the two digit code?
This would just be a linq one-liner: var sortedList = ComJoined.OrderByDescending().ToList()
This might be less complex of a solution to your problem:
var words = new SortedDictionary<int, string>();
foreach (var com in ComJoined)
{
string[] splitCom = com.Split(' ');
// Assuming your data is in the correct format. You could use TryParse to avoid an exception.
words.Add(int.Parse(splitCom[0]), splitCom[1]);
}
// Do something with the sorted dictionary...
foreach (KeyValuePair<int, string> word in words)
Console.WriteLine("{0} {1}", word.Key, word.Value);

Int.Parse(String.Split()) returns "Input string was not in a correct format" error

I am trying to perform a LINQ query on an array to filter out results based on a user's query. I am having a problem parsing two int's from a single string.
In my database, TimeLevels are stored as strings in the format [mintime]-[maxtime] minutes for example 0-5 Minutes. My user's have a slider which they can select a min and max time range, and this is stored as an int array, with two values. I'm trying to compare the [mintime] with the first value, and the [maxtime] with the second, to find database entries which fit the user's time range.
Here is my C# code from the controller which is supposed to perform that filtering:
RefinedResults = InitialResults.Where(
x => int.Parse(x.TimeLevel.Split('-')[0]) >= data.TimeRange[0] &&
int.Parse(x.TimeLevel.Split('-')[1]) <= data.TimeRange[1] &&).ToArray();
My thinking was that it would firstly split the 0-5 Minutes string at the - resulting in two strings, 0 and 5 Minutes, then parse the ints from those, resulting in just 0 and 5.
But as soon as it gets to Int.Parse, it throws the error in the title.
some of the x.TimeLevel database records are stored as "30-40+ Minutes". Is there any method just to extract the int?
You could use regular expressions to match the integer parts of the string for you, like this:
RefinedResults = InitialResults
.Where(x => {
var m = Regex.Match(x, #"^(\d+)-(\d+)");
return m.Success
&& int.Parse(m.Groups[1]) >= data.TimeRange[0]
&& int.Parse(m.Groups[2]) <= data.TimeRange[1];
}).ToArray();
This approach requires the string to start in a pair of dash-separated decimal numbers. It would ignore anything after the second number, ensuring that only sequences of digits are passed to int.Parse.
The reason your code doesn't work is because string.Split("-", "0-5 Minutes") will return [0] = "0" and [1] = "5 Minutes", and the latter is not parseable as an int.
You can use the regular expression "\d+" to split up groups of digits and ignore non-digits. This should work:
var refinedResults =
(
from result in InitialResults
let numbers = Regex.Matches(result.TimeLevel, #"\d+")
where ((int.Parse(numbers[0].Value) >= data.TimeRange[0]) && (int.Parse(numbers[1].Value) <= data.TimeRange[1]))
select result
).ToArray();
Here's a complete compilable console app which demonstrates it working. I've used dummy classes to represent your actual classes.
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace ConsoleApplication2
{
public class SampleTime
{
public SampleTime(string timeLevel)
{
TimeLevel = timeLevel;
}
public readonly string TimeLevel;
}
public class Data
{
public int[] TimeRange = new int[2];
}
class Program
{
private static void Main(string[] args)
{
var initialResults = new []
{
new SampleTime("0-5 Minutes"),
new SampleTime("4-5 Minutes"), // Should be selected below.
new SampleTime("1-8 Minutes"),
new SampleTime("4-6 Minutes"), // Should be selected below.
new SampleTime("4-7 Minutes"),
new SampleTime("5-6 Minutes"), // Should be selected below.
new SampleTime("20-30 Minutes")
};
// Find all ranges between 4 and 6 inclusive.
Data data = new Data();
data.TimeRange[0] = 4;
data.TimeRange[1] = 6;
// The output of this should be (as commented in the array initialisation above):
//
// 4-5 Minutes
// 4-6 Minutes
// 5-6 Minutes
// Here's the significant code:
var refinedResults =
(
from result in initialResults
let numbers = Regex.Matches(result.TimeLevel, #"\d+")
where ((int.Parse(numbers[0].Value) >= data.TimeRange[0]) && (int.Parse(numbers[1].Value) <= data.TimeRange[1]))
select result
).ToArray();
foreach (var result in refinedResults)
{
Console.WriteLine(result.TimeLevel);
}
}
}
}
Error happens because of the " Minutes" part of the string.
You can truncate the " Minutes" part before splitting, like;
x.TimeLevel.Remove(x.IndexOf(" "))
then you can split.
The problem is that you are splitting by - and not also by space which is the separator of the minutes part. So you could use Split(' ', '-') instead:
InitialResults
.Where(x => int.Parse(x.TimeLevel.Split('-')[0]) >= data.TimeRange[0]
&& int.Parse(x.TimeLevel.Split(' ','-')[1]) <= data.TimeRange[1])
.ToArray();
As an aside, don't store three informations in one column in the database. That's just a source of nasty errors and bad performance. It's also more difficult to filter in the database which should be the preferred way or to maintain datatabase consistency.
Regarding your comment that the format can be 0-40+ Minutes. Then you could use...
InitialResults
.Select(x => new {
TimeLevel = x.TimeLevel,
MinMaxPart = x.TimeLevel.Split(' ')[0]
})
.Select(x => new {
TimeLevel = x.TimeLevel,
Min = int.Parse(x.MinMaxPart.Split('-')[0].Trim('+')),
Max = int.Parse(x.MinMaxPart.Split('-')[1].Trim('+'))
})
.Where(x => x.Min >= data.TimeRange[0] && x.Max <= data.TimeRange[1])
.Select(x => x.TimeLevel)
.ToArray();

Categories

Resources