Programming Taskbook

Russian

E-mail:

Password:

User registration   Restore password

1000 training tasks on programming

©  M. E. Abramyan, 1998–2010

 

Examples | C# and VB.NET | File processing

PrevNext


File processing

Solutions of tasks connected with processing of binary files with numeric data and also string files and text files are described on this page.

Binary file with numeric data: File48

This section contains description of solving the following task:

File48°. Three files of integers called SA, SB, SC and a string SD are given. All given files are of the same size. Create a new file called SD; this file must contain triples of components of the given files as follows: A1B1C1, A2B2C2, ... .

Creating a template and acquaintance with the task

To create a template of the required task one should use PT4Load tool. The solution of the task should be entered in the Solve procedure, which is defined in the File48 file (this file is created by PT4Load tool, it has .cs extension for C# and .vb extension for VB.NET):

[C#]

public static void Solve()
{
    Task("File48");
}

[VB.NET]

Sub Solve()
    Task("File48")
End Sub

When the program is launched you will see the Programming Taskbook window. Here is the screenshot of the window for the C# language:

The first line of the input data panel contains four file names: SA, SB, and SC for input files, SD for an output file. The next three lines of the input data panel displays components of the input files. File components are light-cyan colored to distinguish them from standard input-output data and comments. With mouse or keyboard you can scroll the file components .

All input files are created with new name and data for each test running of the program. When the program finishes all files are removed automatically from the working directory.

The running of our program is considered as acquaintance running because the program does not perform input-output operations. In the panel of results the "Example of right solution" tab is active; this tab displays components that should be saved in the output file.

Initial data input

All .NET Framework classes connected with file processing are defined in the System.IO namespace. For using these classes without having to specify the namespace you should insert the following directive at the beginning of the program:

[C#]

using System.IO;

[VB.NET]

Imports System.IO

Before solving the task it is necessary to input file names and open corresponding files using the FileStream class constructor:

[C#]

public static void Solve()
{
    Task("File48");
    FileStream[] f = new FileStream[4];
    for (int i = 0; i < 3; i++)
        f[i] = new FileStream(GetString(), FileMode.Open);
}

[VB.NET]

Sub Solve()
    Task("File48")
    Dim f(3) As FileStream
    For i As Integer = 0 To 2
        f(i) = New FileStream(GetString(), FileMode.Open)
    Next
End Sub

Because we input only three file names, the next program running will lead to the error message "Some required data are not input.".

Run-time errors in the program

To correct the last error it is enough to increase the amount of iterations in the for statement:

[C#]

    for (int i = 0; i < 4; i++)

[VB.NET]

    For i = 0 To 3

When the program will be started we shall see the following error message: "Error System.IO.FileNotFoundException: Could not find file "C:\PT4Work\d8kf.tst" (of course the another name of file will be pointed out). This message means that the run-time error occurs. Any run-time error in .NET Framework throws an exception; the message contain the name of exception (System.IO.FileNotFoundException) and its brief description.

Creating an empty output file

To avoid the run-time error you should create a non-existing file using the FileMode.Create parameter in the FileStream constructor. Let's restore the previous amount of iterations and add the following statement that creates the output file:

[C#]

    f[3] = new FileStream(GetString(), FileMode.Create);

[VB.NET]

    f(3) = New FileStream(GetString(), FileMode.Create)

When the program will be started we shall see the following error message: "Output file is not closed". To correct this error you should call the Close method for each open file stream at the end of the program:

[C#]

public static void Solve()
{
    Task("File48");
    FileStream[] f = new FileStream[4];
    for (int i = 0; i < 3; i++)
        f[i] = new FileStream(GetString(), FileMode.Open);
    f[3] = new FileStream(GetString(), FileMode.Create);
    //
    for (int i = 0; i < 4; i++)
        f[i].Close();
}

[VB.NET]

Sub Solve()
    Task("File48")
    Dim f(3) As FileStream
    For i As Integer = 0 To 2
        f(i) = New FileStream(GetString(), FileMode.Open)
    Next
    f(3) = New FileStream(GetString(), FileMode.Create)
    REM
    For i As Integer = 0 To 3
        f(i).Close()
    Next
End Sub

The comment (// for C# or REM for VB.NET) marks the program position, where we can execute input-output file operations: files are already opened by the FileStream constructors and are not closed by the Close method.

Running of this program provides creation of an output file. But this file will be empty, that is, will contain no elements. Therefore we shall see the error message "Wrong solution", and the output data panel will contain the following text:

EOF:

This text (EOF—End Of File) means that the output file exists but contains no elements.

Using of incorrect types for file elements

We did not use file input-output operations so far. Therefore we did not specify a type for file components.

But if the program contains file input-output operations then it is important to specify the type of file components correctly, otherwise "strange" errors will occurs during the program running. Let's model this situation in our program. For this purpose we replace the // or REM comment by the following statements:

[C#]

    BinaryReader[] r = new BinaryReader[3];
    for (int i = 0; i < 3; i++)
        r[i] = new BinaryReader(f[i]);
    BinaryWriter w = new BinaryWriter(f[3]);
    for (int i = 0; i < 3; i++)
        w.Write(r[i].ReadDouble());

[VB.NET]

    Dim r(2) As BinaryReader
    For i As Integer = 0 To 2
        r(i) = New BinaryReader(f(i))
    Next
    Dim w As New BinaryWriter(f(3))
    For i As Integer = 0 To 2
        w.Write(r(i).ReadDouble())
    Next

These statements provide creation of additional streams, reading one component of the double type from each input file, and writing these components to the output file (in the required order). Note that we have specified the component type incorrectly (as double), but the program will be compiled successfully, and it will execute without run-time errors.

The result of the program execution will be unexpected: the output file will contain six components, that is, two components of each input file. It can be explained as follows. In our program the file elements are assumed to be real numbers (of size 8 bytes). But in fact the input files contains integer components (of size 4 bytes). Therefore reading and writing of one "real-valued" component leads to reading/writing of two integer components.

Thus, we see that the errors connected with the incorrect file types are not detected by compiler and do not usually lead to the run-time errors, but often lead to "strange" results.

After replacing the ReadDouble method by the ReadInt32 one our program will work quite "clearly": the output file will contain three components, that is, one initial component of each input file.

Correct solution, its testing and browsing results

At last, let's present a correct algorithm for the File48 task:

[C#]

public static void Solve()
{
    Task("File48");
    FileStream[] f = new FileStream[4];
    for (int i = 0; i < 3; i++)
        f[i] = new FileStream(GetString(), FileMode.Open);
    f[3] = new FileStream(GetString(), FileMode.Create);
    BinaryReader[] r = new BinaryReader[3];
    for (int i = 0; i < 3; i++)
        r[i] = new BinaryReader(f[i]);
    BinaryWriter w = new BinaryWriter(f[3]);
    while (f[0].Position < f[0].Length)
        for (int i = 0; i < 3; i++)
            w.Write(r[i].ReadInt32());
    for (int i = 0; i < 3; i++)
        r[i].Close();
    w.Close();
}

[VB.NET]

Sub Solve()
    Task("File48")
    Dim f(3) As FileStream
    For i As Integer = 0 To 2
        f(i) = New FileStream(GetString(), FileMode.Open)
    Next
    f(3) = New FileStream(GetString(), FileMode.Create)
    Dim r(2) As BinaryReader
    For i As Integer = 0 To 2
        r(i) = New BinaryReader(f(i))
    Next
    Dim w As New BinaryWriter(f(3))
    Do While f(0).Position < f(0).Length
        For i As Integer = 0 To 2
            w.Write(r(i).ReadInt32())
        Next
    Loop
    For i As Integer = 0 To 2
        r(i).Close()
    Next
    w.Close()
End Sub

This algorithm contains the "while" loop that provides reading all components from the input files (recall that all input files are of the same size). In the condition of the "while" loop the current position of the first input file (f(0).Position) compares with its last position, that is, the length of this file (f(0).Length).

Also the final part of the program is changed: instead of the Close method of the FileStream objects the Close methods of the BinaryReader and BinaryWriter objects are called. This changing allows to close all streams because the Close method of the BinaryReader and BinareWriter stream closes not only these streams but also their base stream (of the FileStream type).

After running the program we shall see the message "Right solution. The test 1 of 5", and after 5 test runnings we shall receive the message "The task is solved!".

Using the PT4Results tool we can browse information about all test runnings of our program. Here is the list of results for the C# language:

File48     c03/05 11:43 Acquaintance with the task.
File48     c03/05 11:46 Some required data are not input.
File48     c03/05 11:49 Output file is not found.
File48     S03/05 11:52 Error FileNotFoundException.
File48     c03/05 11:56 Wrong solution.--3
File48     c03/05 11:59 The task is solved!

Remark. It is not necessary to use an array of the FileStream objects, because these objects may be passed to the BinaryReader and BinaryWriter constructors directly. Further, we may use methods of the File class to create FileStream objects (instead of the FileStream constructors):

[C#]

public static void Solve()
{
    Task("File48");
    BinaryReader[] r = new BinaryReader[3];
    for (int i = 0; i < r.Length; i++)
        r[i] = new BinaryReader(File.OpenRead(GetString()));
    BinaryWriter
        w = new BinaryWriter(File.Create(GetString()));
    while (r[0].BaseStream.Position < r[0].BaseStream.Length)
        for (int i = 0; i < r.Length; i++)
            w.Write(r[i].ReadInt32());
    foreach (BinaryReader a in r)
        a.Close();
    w.Close();
}

[VB.NET]

Sub Solve()
    Task("File48")
    Dim r(2) As BinaryReader
    For i As Integer = 0 To r.Length - 1
        r(i) = New BinaryReader(File.OpenRead(GetString()))
    Next
    Dim w As New BinaryWriter(File.Create(GetString()))
    Do While r(0).BaseStream.Position < r(0).BaseStream.Length
        For i As Integer = 0 To r.Length - 1
            w.Write(r(i).ReadInt32())
        Next
    Loop
    For Each a As BinaryReader In r
        a.Close()
    Next
    w.Close()
End Sub

Remark. In this program we use the Length property of the r array in the for loop instead 3. To access the Position and Length properties of the base stream we use the BaseStream property of the BinaryReader class. Further, the for each loop (For Each for VB.NET) was used for the Close methods calls. As a result our program became more brief and clear.


String binary files and text files: File67, Text21

In this section we shall discuss some features of processing of string files (i.e., binary files whose components are strings) and text files.

String files

Let's consider the File67 task as an example of task with string file processing:

File67°. A file of strings is given. The file contains dates in the "day/month/year" format, the "day" and "month" fields contain two digits, the "year" field contains four digits (for example, "16/04/2001"). Create two new files and write integer values of days and months for each date from the given file to the first and second resulting file respectively (in the same order).

If Programming taskbook is used for task solution in C# and VB.NET then binary string files are assumed to contain strings of the 80 characters length (strings of the smaller length are padded by blank characters). Therefore you should use the TrimEnd() method call for removing right blank characters from the string (after reading it from a string file), and the PadRight(80) method call for padding string by blank characters (before writing it to a string file). A string file stores not only all characters of each string but also the string length. If a string length is less than 128 bytes then 1 byte is used to store the string length, therefore the size of each component of a string file used in the tasks equals 81 bytes.

One should emphasize that string components of binary files may be of any length, and the above mentioned restriction on the length of string is imposed by Programming Taskbook only.

Taking into account all features mentioned above we can solve the File67 task as follows:

[C#]

public static void Solve()
{
    Task("File67");
    BinaryReader
        f = new BinaryReader(File.OpenRead(GetString()));
    BinaryWriter
        f1 = new BinaryWriter(File.Create(GetString())),
        f2 = new BinaryWriter(File.Create(GetString()));
    for (long i = 1; i <= f.BaseStream.Length / 81; i++)
    {
        string s = f.ReadString();
        f1.Write(int.Parse(s.Substring(0, 2)));
        f2.Write(int.Parse(s.Substring(3, 2)));
    }
    f.Close();
    f1.Close();
    f2.Close();
}

[VB.NET]

Sub Solve()
    Task("File67")
    Dim f As New BinaryReader(File.OpenRead(GetString())), _
        f1 As New BinaryWriter(File.Create(GetString())), _
        f2 As New BinaryWriter(File.Create(GetString()))
    For i As Long = 1 To f.BaseStream.Length \ 81
        Dim s As String = f.ReadString()
        f1.Write(Integer.Parse(s.Substring(0, 2)))
        f2.Write(Integer.Parse(s.Substring(3, 2)))
    Next
    f.Close()
    f1.Close()
    f2.Close()
End Sub

Recall that you should insert the using (Imports) directive at the beginning of the program. In the File67 task it is not necessary to remove trailing spaces from string that are read from input files. In this program the for loop is used for reading and writing file data. The amount of iterations is determined by the amount of the input file components and is equal to file size (in bytes) divided by the size of the file component (81 bytes).

Text files

Let's consider the Text21 task as an example of task with text file processing:

Text21°. Given a text file that contains more than three lines, remove its last three lines.

As text files unlike binary cannot be open for reading and writing simultaneously, to change the text file use an temporary file. Neccessary resulting data are written to this file, then the initial file is removed and the temporary file renamed by the initial file name.

Text files, unlike binary string files, cannot be open for reading and writing simultaneously, therefore it is necessary to use an temporary file for text file changing: the required output data should be written to the temporary file, after that the initial file should be removed and the temporary file name should be changed to the initial file name. Lines of the text file are of various length, therefore it is necessary to read all lines to determine the amount of lines in the text file.

For text file processing one should use special stream classes: the StreamReader class for data reading and the StreamWriter for data writing.

Considering all features mentioned above we can solve the Text21 task as follows (recall that you should insert the using (Imports) directive at the beginning of the program):

[C#]

public static void Solve()
{
    Task("Text21");
    string s1 = GetString(), s2 = "$T21$.tmp";
    int n = 0;
    StreamReader f1 = new StreamReader(s1);
    StreamWriter f2 = new StreamWriter(s2);
    while (f1.Peek() != -1)
    {
        f1.ReadLine();
        n++;
    }
    f1.BaseStream.Position = 0;
    for (int i=0; i < n - 3; i++)
        f2.WriteLine(f1.ReadLine());
    f1.Close();
    f2.Close();
    File.Delete(s1);
    File.Move(s2, s1);
}

[VB.NET]

Sub Solve()
    Task("Text21")
    Dim s1 As String = GetString(), _
        s2 As String = "$T21$.tmp", n As Integer = 0, _
        f1 As New StreamReader(s1), _
        f2 As New StreamWriter(s2)
    Do While f1.Peek() <> -1
        f1.ReadLine()
        n += 1
    Loop
    f1.BaseStream.Position = 0
    For i As Integer = 1 To n - 3
        f2.WriteLine(f1.ReadLine())
    Next
    f1.Close()
    f2.Close()
    File.Delete(s1)
    File.Move(s2, s1)
End Sub

This algorithm is inefficient because it requires to read the initial file twice: the first time to determine the amount of file lines, the second time to create an temporary file that will contain all lines of the initial file except three last ones.

The Text21 task can be solved using only one reading of the initial file. In this solution we'll take into account the following consideration: a text line should be written to an temporary file if at least three lines follow this line in the file. This way of solution does not requires to determine the amount of the text lines. To store strings, which have been read from the input file but still have not been written to the temporary file, we shall use an array of three elements of the string type.

We obtain the second, one-pass algorithm of the Text23 solution:

[C#]

public static void Solve()
{
    Task("Text21");
    string[] s = new String[3];
    string s1 = GetString(), s2 = "$T21$.tmp";
    int n = 0;
    StreamReader f1 = new StreamReader(s1, Encoding.Default);
    StreamWriter f2 = new StreamWriter(s2, false,
        Encoding.Default);
    for (int i= 0; i < 3; i++)
        s[i] = f1.ReadLine();
    while (f1.Peek() != -1)
    {
        f2.WriteLine(s[n]);
        s[n] = f1.ReadLine();
        n = (n + 1) % 3;
    }
    f1.Close();
    f2.Close();
    File.Delete(s1);
    File.Move(s2, s1);
}

[VB.NET]

Sub Solve()
    Task("Text21")
    Dim s(2) As String, s1 As String = GetString(), _
        s2 As String = "$T21$.tmp", n As Integer = 0, _
        f1 As New StreamReader(s1, Encoding.Default), _
        f2 As New StreamWriter(s2, False, Encoding.Default)
    For i As Integer = 0 To 2
        s(i) = f1.ReadLine()
    Next
    Do While f1.Peek() <> -1
        f2.WriteLine(s(n))
        s(n) = f1.ReadLine()
        n = (n + 1) Mod 3
    Loop
    f1.Close()
    f2.Close()
    File.Delete(s1)
    File.Move(s2, s1)
End Sub

PrevNext

 

 

Designed by
M. E. Abramyan and V. N. Braguilevsky

Last revised:
02.05.2010