-2

So, today I was trying to make an app in which the user enters the coordinates of their car and the app calculates the easiest way to reach a gas station before the fuel tank runs out. But when I wanted to test if the app displays the data which is stored in a .txt file, I get a System.FormatException error which I cannot solve. I have used this method many many times before and I am not sure what is the problem here.

The error occurs at the line:

int xcoor = int.Parse(s.Substring(k + 1));

Code:

int n = 10;

string[] cities = new string[n];
int[] x = new int[n];
int[] y = new int[n];
System.IO.StreamReader sr = new System.IO.StreamReader(
    @"..\..\CitiesGPS.txt", Encoding.GetEncoding(1250));
for (int i = 0; i < n; i++)
{
    string s = sr.ReadLine();
    int k = s.IndexOf(";");
    string city = s.Substring(0, k);
    cities[i] = city;
    int xcoor = int.Parse(s.Substring(k + 1));
    x[i] = xcoor;
    int k2 = s.IndexOf(";");
    int ycoor = int.Parse(s.Substring(k2 + 1));
    y[i] = ycoor;
}

sr.Close();
Console.WriteLine("Data in the file:");
for (int i = 0; i < n; i++)
{
    Console.WriteLine(cities[i] + ";" + x[i] + ";" + y[i]);
}

Data:

Nagyváros;150;30
Kisváros;250;300
TanyaBenzunkúttal;290;310
Szépváros;500;10
Alsóváros;250;0
Felsőváros;560;560
Középváros;300;300
FolyópartiVáros;380;400
HáromBenzinkutasváros;10;400
Nagyvárosbvezetőútja;380;230

Thanks in advance,

Blaise

Bal500
  • 5
  • 6
  • In what line is the error an how is your `.txt` file? – J.F. Nov 13 '20 at 11:37
  • 1
    It could be one of the `int.Parse` calls; hard to say as it stands. Use a debugger or wrap the code in a try/catch-block and output the exception (looking at the line number where it was thrown). Also, unrelated, it would possible by easier to use `string.Split()` (or even a CSV library) to parse the lines. – Christian.K Nov 13 '20 at 11:37
  • The error comes up at line: int xcoor = int.Parse(s.Substring(k + 1)); – Bal500 Nov 13 '20 at 11:41
  • Removed Parse and replaced them with "Convert.ToInt32" but it still does not work. – Bal500 Nov 13 '20 at 11:43
  • https://learn.microsoft.com/en-us/dotnet/api/system.int32.parse?view=net-5.0 says a FormatException can occur when the input string is not in the correct format. So presumably, whatever `s.Substring(k + 1)` returns is not a valid integer. Use your debugger to find out what that expression actually produces. We can't tell you that because we can't see any of the data you're using. – ADyson Nov 13 '20 at 11:43
  • Added the data to the question. – Bal500 Nov 13 '20 at 11:45
  • P.S. Why aren't you just using a CSV-parsing library though? (Despite the "Comma" in the name CSV, in fact all of the popular libraries allow you to substitute any delimiter, e.g. `;` in your case, and still read the file.) – ADyson Nov 13 '20 at 11:45
  • Thanks for the data. Now, using that, a simple test allows you to see what the issue is going to be: https://dotnetfiddle.net/zIPLnQ . – ADyson Nov 13 '20 at 11:48
  • Also `int k2 = s.IndexOf(";");` is just going to get you the same index of `;` that you found for `k`. To make that work you'd have to look only inside the substring starting from the occurrence of the first semicolon. – ADyson Nov 13 '20 at 11:50

3 Answers3

2

You don't have any length on the middle Substring call, so it takes the rest of the line.

int xcoor = int.Parse(s.Substring(k + 1, /* missing length*/));

But, please use a CSV-parser. There is no reason to re-invent the wheel every time you need it.

EDIT I just noticed you're getting the same index of ";" into both k and k2, so it won't work at all. Again, use a CSV-parser.

EDIT 2 If you won't use a CSV-parser, here is a solution with Split as @ADyson suggests:

for (int i = 0; i < n; i++)
{
    var parts = sr.ReadLine().Split(";");
    string city = parts[0];
    cities[i] = city;
    int xcoor = int.Parse(parts[1]);
    x[i] = xcoor;
    int k2 = s.IndexOf(";");
    int ycoor = int.Parse(parts[2]);
    y[i] = ycoor;
}
Palle Due
  • 5,929
  • 4
  • 17
  • 32
  • When I have added the lenght - used 0 so everything will be displayed - the problem still occurred but when I changed 0 to 1 it started working but it messed up the data :/ – Bal500 Nov 13 '20 at 11:50
  • `Substring(x, y)` means: "Take y characters from string starting at x". Of course neither 0 or 1 works. – Palle Due Nov 13 '20 at 11:53
  • 1
    @FábiánBalázs why would you change the length to 1? That makes no sense. None of your numbers are 1 character long. You'd need to make the length the distance between the first ; and the second ;. Or just do the sane thing and use string.Split!! https://learn.microsoft.com/en-us/dotnet/api/system.string.split?view=net-5.0 – ADyson Nov 13 '20 at 11:53
2

Apart from the general advice to use a ready-made CSV / delmimited-file parser for this task, your own code is massively over-complicating the problem, and suffers from various logical flaws when trying to detect the location of the delimiters and calculate the correct lengths of the strings in between them.

It's much simpler to just use the String.Split method.

Also your onward data structure (consisting of separate arrays for cities, x coords and y coords) doesn't make much sense if you're intending to then match the co-ordinates to the location names - if you want to do that, keep the data together. A simple object structure would make it easier to work with later on. And lists are generally easier to work with than arrays, too.

Also for simplicity you should just read to the end of the file, not a specific number of lines.

Lastly, best practice with a disposable item like a StreamReader is to initialise it in conjunction with a using block to ensure it's properly disposed afterwards (see StreamReader documentation examples.

e.g.

public class LocationData
{
  public string Name {get; set;}
  public int X {get; set;}
  public int Y {get; set;}
}

and then

using (System.IO.StreamReader sr = new new System.IO.StreamReader(@"..\..\CitiesGPS.txt", Encoding.GetEncoding(1250)))
{
  List<LocationData> locationList = new List<LocationData>();

  while ((s = sr.ReadLine()) != null)
  {
    string[] data = s.Split(';');
    LocationData loc = new LocationData()
    {
      Name = data[0],
      X = int.Parse(data[1]),
      Y = int.Parse(data[2])
    };
    locationList.Add(loc);
  }
}

foreach (LocationData loc in locationList)
{
    Console.WriteLine(loc.Name + ";" + loc.X + ";" + loc.Y);
}

P.S. Super-simple demo of processing a single row using string.split: https://dotnetfiddle.net/vGXu2O

ADyson
  • 57,178
  • 14
  • 51
  • 63
0

The fail is with the substring in the line

int xcoor = int.Parse(s.Substring(k + 1));

You are trying to parse to int the value 150;30. And obviously it can't be parsed as a number.

I recommend split every line by ; to get the values for each position.

You can do this inside for loop:

string[] line = s.Split(";");
var city = line[0];
var xcoor = int.Parse(line[1]);
var ycoor = int.Parse(line[2]);
cities[i] = city;
x[i] = xcoor;
y[i] = ycoor;

Edit to add how it works using substring().

Substring method takes two arguments:

1st: Is the position where start.
2nd: Is how many positions more do you want to get.

So, the solution is get the ondex of all ; (I've used a lambda expression) and then use substring in a correct way.

That is: Saying the program to start where a semicolon has been found and end as many position as there exists until the next semicolon.

var indexes = Regex.Matches(s, ";").Cast<Match>().Select(m => m.Index).ToList();
var city = s.Substring(0,indexes[0]);
var xcoor = s.Substring(indexes[0]+1,(indexes[1]-indexes[0])-1);
var ycoor = s.Substring(indexes[1]+1, (s.Length-indexes[1])-1);
cities[i] = city;
x[i] = int.Parse(xcoor);
y[i] = int.Parse(ycoor);

With this code, you can see the position where to start and 'cut' the substring.

J.F.
  • 13,927
  • 9
  • 27
  • 65