Frayt
Frayt

Reputation: 1233

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.

Upvotes: 0

Views: 1240

Answers (4)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726809

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.

Upvotes: 1

Matthew Watson
Matthew Watson

Reputation: 109732

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

Upvotes: 1

Tim Schmelter
Tim Schmelter

Reputation: 460208

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

Upvotes: 0

ercyon
ercyon

Reputation: 111

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.

Upvotes: 0

Related Questions