Diego
Diego

Reputation: 25

Read matrix from file with C#

Please can you help me?

I have to read from file these int:

22 13 17 11 0

8 2 23 4 24

21 9 14 16 7

6 10 3 18 5

1 12 20 15 19



3 15 0 2 22

9 18 13 17 5

19 8 7 25 23

20 11 10 24 4

14 21 16 12 6



14 21 17 24 4

10 16 15 9 19

18 8 23 26 20

22 11 13 6 5

2 0 12 3 7

The code I'm trying to use is this:

String input = File.ReadAllText( @"c:\myfile.txt" );

int i = 0, j = 0;
int[,] result = new int[5, 5];
foreach (var row in input.Split('\n'))
{
    j = 0;
    foreach (var col in row.Trim().Split(' '))
    {
        result[i, j] = int.Parse(col.Trim());
        j++;
    }
    i++;
}

The problem is that in the input file there are too much spaces and the code get an exception. System.FormatException

How can I read matrix without these spaces?

Upvotes: 0

Views: 1144

Answers (4)

Funk
Funk

Reputation: 11201

Props @CaiusJard for the many variations. The following pipeline first regroups the input data (file) into a more sensible format, eg the text for only a single matrix. And then pipes that format through to the compute intensive method that does the actual parsing and array creation.

class Program
{
    static void Main(string[] args)
    {
        var matrices =
            File.ReadAllLines(...)
                .Where(line => !string.IsNullOrWhiteSpace(line))
                .Select((line, i) => new { MatrixNumber = i/5, Line = line })
                .GroupBy(x => x.MatrixNumber, x => x.Line)
                .Select(group => ParseMatrix(group.ToList()))
                .ToList();
        ...
    }

    static int[,] ParseMatrix(List<string> txtRows) 
    {
        if (txtRows.Count != 5) throw new FormatException("Invalid dimension");
        var matrix = new int[5,5];
        for (var i=0; i<5; i++) 
        {
            var columns = txtRows[i].Split(' ', StringSplitOptions.RemoveEmptyEntries);
            if (columns.Length != 5) throw new FormatException("Invalid dimension");
            for (var j=0; j<5; j++) 
            {
                matrix[i,j] = int.Parse(columns[j]);
            }
        }
        return matrix;
    }
}

The benifit in doing so is that, for very large files, we can easily add .AsParallel() to make optimal use of computer resources. I also added AsOrdered() assuming the order of the matrices in the file should be preserved.

var matrices =
    File.ReadAllLines(...)
        .Where(line => !string.IsNullOrWhiteSpace(line))
        .Select((line, i) => new { MatrixNumber = i/5, Line = line })
        .GroupBy(x => x.MatrixNumber, x => x.Line)
        .AsParallel()
        .AsOrdered()
        .Select(group => ParseMatrix(group.ToList()))
        .ToList();

Upvotes: 0

Caius Jard
Caius Jard

Reputation: 74605

If you want to persist with your existing code, it needs some fixup predominantly because it can only store one 5x5 matrix, and there are three such matrices in the file. The rest of my commentary is in the code as comments:

string[] lines = File.ReadAllLines(@"c:\myfile.txt" );


List<int[,]> matrices = new();                    //to store three matrices in
int[,] result = new int[5, 5];

for(int i = 0; i < lines.Length; i++)             //for loop is tidier than having i and j variables dumped all around the place in a foreach
{

    //make the columns array now, so we can check it for length and skip if it's not 5
    var cols = lines[i].Split(' ', StringSplitOptions.RemoveEmptyEntries);

    if(cols.Length != 5) continue;                //skip if it's wonky/blank line

    //write the cols
    for(int j = 0; j < cols.Length; j++)
        result[i%5, j] = int.Parse(cols[j]);      //note that it's i%5 because i will increase forever, but this matrix needs it to stay in the range 0..4

    //if we processed our last row for this matrix i.e. i%5 is 4, save the matrix and renew it
    if(i % 5 == 4){ 
      matrices.Add(result);
      result = new int[5,5];
    }
}

Upvotes: 0

Caius Jard
Caius Jard

Reputation: 74605

If you want to keep using the existing int[,] you can make life a bit easier by leveraging the fact that they're stored sequentially, so you could read your file into one array of ints, and then write chunks of it to a matrix

You appear to have three 5x5 matrix in the file but only capacity to store one in your code. This means your code will blow up after the first matrix is filled. Instead let's have a list for all our matriceS:

var allNums = x.Split(Array.Empty<string>(), StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();

var matrices = new List<int[,]>();

for(int i = 0; i*25 < allNums.Length; i++){

    var matrix = new int[5,5];
    
    Buffer.BlockCopy(allNums, i*25*sizeof(int), matrix, 0, 25 * sizeof(int));
    
    matrices.Add(matrix);
}
  • The file is read into allNums using a similar strategy as mentioned in the other answer; RemoveEmptyEntries in combination with an empty delimiter list means that any numbers of whitespace in a row serve as one delimiter. This time we run Select(int.Parse) to convert every string number in the stream of numbers from the file, into a stream of ints and store them in a single array
  • For as many blocks of 25 numbers as there are in the stream
    • Make a new matrix to receive them
    • Use BlockCopy to copy the bytes from portion X of the source array to 0, for length of (however many bytes 25 ints occupies). In practive this means that the bytes representing the first 25 numbers are copied to a matrix, then on the next loop the the next 25 are copied to another matrix..
    • Add the matrix to a list of matrices

At the end of the operation, your list of matrices has three 5x5 inside

Upvotes: 1

Caius Jard
Caius Jard

Reputation: 74605

If you want to re-jig your code to using a jagged array:

You could, I suppose, read it as a stream of ints that you repartition into 5 columns based on the index of the int

int[][] matrix = File.ReadAllText(...)
  .Split(Array.Empty<string>(), StringSplitOptions.RemoveEmptyEntries)
  .Select((s, i) => new { N = int.Parse(s), I = i})
  .GroupBy(at => at.I/5, at => at.N, (k, g) => g.ToArray())
  .ToArray();

So, what's going on here?

  • File.ReadAllText we know
  • Split on an empty delimiter array causes Split to split on whitespace, so all your spaces, tabs, returns etc are just folded into being "a single delimiter" by RemoveEmptyEntries which effectively delivers the file contents as a list of numbers, having removed all the interim whitespace
  • Select in this guise takes a lambda that accepts two parameters; one the number itself (as a string) that I've termed s and the other, i the index from 0 that it occurs at. These two bits of info are captured into an anonymous type as N (the result of parsing s to an int) and I
  • GroupBy here has 3 parameters, all lambdas
    • The first is what to group by - in this case we're going to group by the I divided by 5. This chops the number stream up into blocks of 5 ints
    • The second is what value goes into the output group - in this case there is no longer any need for I, so we just put N in the output. This means that the GroupBy will effectively make a list of a list of 5 ints as its output
    • The third is what transform to apply to the resulting list of 5 ints and I ToArray it, to make it an int[5]
  • Because we now have a list of int[5] all we need to do is call ToArray on it to turn it into an int[n][5] where n is the number of items in the list

In its current form, this is a 15x5, but if you wanted it as three 5x5 you could chop it again using a similar strategy, or perhaps a simpler Skip/Take 5 set inside a loop

Note that here I use the word "list" in its English sense, not in its List<T> C# sense, as a contraction of "enumerable collection"

Upvotes: 2

Related Questions