Programming Taskbook


E-mail:

Password:

User registration   Restore password

Russian

SFedU SMBU

Electronic problem book on programming

©  M. E. Abramyan (Southern Federal University, Shenzhen MSU-BIT University), 1998–2026

 

PT for LINQ | Sample solutions | LINQ queries

PrevNext


Element-wise Operations: LinqBegin4

Creating a Project Template and Getting Acquainted with the Task

The tasks in the LinqBegin group are designed to master various methods of sequence transformation. All these methods are extension methods of the System.Linq.Enumerable class and form the LINQ To Objects interface (extension methods appeared in version 3.0 of the C# language).

The tasks cover most LINQ To Objects methods; they are divided into 4 subgroups, each dedicated to specific types of transformations: from the simplest (element-wise operations) to the most complex (joining and grouping).

Remark. Methods belonging to the LINQ to Objects interface are called query operators, lambda operators (to emphasize that lambda expressions are usually specified as their parameters) or q-operators (from the English word query). We will call them LINQ methods.

Let's consider one of the tasks related to element-wise operations.

LinqBegin4°. A character C and a string sequence A are given. If A contains a single element ending with the character C, then output this element; if there are no required strings in A, output an empty string; if there are more than one required strings, output the string "Error".

Note. Use a try block to catch a possible exception.

Performing a task using the Programming Taskbook usually begins with creating a project template for the selected task. The PT4Load software module, included in the taskbook, is designed to create the template. This module can be launched using the Load.lnk shortcut, which is automatically created in the student's working directory (by default, the working directory is located on drive C and is named PT4Work; using the PT4Setup program, also included in the taskbook and available from Start Menu | Programs | Programming Taskbook 4, any number of other working directories can be defined; starting from version 4.15, new working directories can also be defined directly from the PT4Load module by clicking the button ).

Starting from version 4.22, the PT4Load module can be launched using the auxiliary PT4Panel module, designed for quick launching of other modules and various versions of the help system, as well as for quickly switching between different working directories.

The task groups related to LINQ technology are not included in the base set of the Programming Taskbook problem book. For these groups to be available, it is necessary after installing the Programming Taskbook to install its extension Programming Taskbook for LINQ.

After launching the PT4Load module, its window will appear on the screen, listing all available task groups:

If scroll buttons are displayed to the right of the group list, then the list of groups can be scrolled using these buttons or the [Up] and [Down] keys.

The window title indicates the name of the current programming environment and its version number. The provided figure corresponds to a configuration where the current environment is Microsoft Visual Studio 2022 for the C# language.

When performing tasks dedicated to LINQ technology, it is necessary to use the C#, Visual Basic .NET, or F# languages. If an environment other than the required one is specified, you should call the context menu of the PT4Load module (by right-clicking in its window) and select the required environment from the list that appears.

The LinqBegin group should be present in the group list. Its absence can be explained by two reasons: either the current language is a language other than C#, Visual Basic .NET, or F#, or the Programming Taskbook for LINQ extension is not installed on the computer.

After entering the task name (in our case LinqBegin4), the "Load" button in the PT4Load window will become available and, by clicking it (or pressing the [Enter] key), we will create a project template for the specified task, which will be immediately loaded into the Visual Studio environment. We will assume that the C# language is selected as the programming language. The created project will consist of several files, however, to solve the task, we will only need the file named LinqBegin4.cs. This file will be loaded into the Visual Studio editor.

Let's present the contents of the LinqBegin4.cs file:

// File: "LinqBegin4"
using PT4;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PT4Tasks
{
    public class MyTask : PT
    {
        // When solving tasks of the LinqBegin group, the following
        // additional methods defined in the taskbook are available:
        // (*) GetEnumerableInt() - input of a numeric sequence;
        // (*) GetEnumerableString() - input of a string sequence;
        // (*) Put() (extension method) - output of a sequence;
        // (*) Show() and Show(cmt) (extension methods) - debug output
        //       of a sequence, cmt - string comment;
        // (*) Show(e => r) and Show(cmt, e => r) (extension methods) -
        //       debug output of r values, obtained from elements e
        //       of a sequence, cmt - string comment.

        public static void Solve()
        {
            Task("LinqBegin4");

        }
    }
}

The LinqBegin4.cs file begins with using directives, connecting the main namespaces, including System.Collections.Generic for generic collections, System.Linq for the Enumerable class, containing LINQ to Objects extension methods, System.Text for classes related to text processing, and PT4 for the PT class, providing access to the core functions of the Programming Taskbook. Then follows the description of the MyTask class - a descendant of the PT class. The Solve method, where the task solution needs to be programmed, contains a call to the Task method, initializing the task with the specified name.

When performing tasks related to LINQ technology, additional taskbook methods may be useful. A brief description of these methods is provided in the comments located before the description of the Solve method.

To run the created program, just press the [F5] key. This will bring up the taskbook window. The window view shown in the figure corresponds to the dynamic layout mode of sections, which appeared in version 4.11 of the Programming Taskbook.

Since the program does not perform any data input and output actions, the program run is considered introductory. During an introductory run, the taskbook window displays three sections: the task description, the initial data, and an example of the correct solution.

All initial data are highlighted in a special color to distinguish them from comments: if the window is set to the "on black background" color mode (as in the figure above), then the data is highlighted in yellow; in the "on white background" mode the data is highlighted in blue. To switch the color mode, just press the [F3] key or click on the "Color (F3)" label in the upper right corner of the taskbook window.

Running the program allows you to familiarize yourself with a sample of the initial data and the corresponding example of the correct solution for these initial data. In our case, the set of initial data includes a character C and a string sequence A. In all tasks of the LinqBegin group, when defining a sequence, first the number of its elements is specified, and then - the values of the elements themselves (on the screen, the number of elements is separated from the list of values by a colon), and the values may occupy several screen lines. Character data is enclosed in single quotes, and string data - in double quotes, which corresponds to the representation of character and string constants adopted in the C# language. The use of quotes allows, in particular, to "see" empty strings included in the sets of initial or resulting data (as in the example of initial data provided in the previous figure).

To exit the program, press the "Exit" button, the [Esc] key, or the [F5] key, i.e., the same key that runs the program from the Visual Studio environment (if using another environment, you can press the key intended for running the program from that environment).

To get more acquainted with the task, you can use two special modes of the taskbook: the demo mode and the mode for displaying tasks in html format.

To run the program in demo mode, just add the "?" symbol to the parameter of the Task method (in our case, the call to the method will look like Task("LinqBegin4?");). The taskbook window in demo mode has additional buttons that allow you to go to the previous or next task, and also display various sets of initial data and the associated samples of the correct solution on the screen (see the figure). The figure shows a situation where the initial sequence contains several elements ending with the character C, equal to "e" (these are the elements "je" and "Je"), so the correct solution requires outputting the string "Error".

To display the task in html format, just click on the "Mode (F4)" label in the taskbook window (or simply press the [F4] key). This will launch the html browser used in the system by default, and it will load an html page containing the task description and the text of the preamble to the corresponding task group:

In this case, besides the description of the LinqBegin4 task, the screen displays the preamble for the entire LinqBegin group and the preamble for the subgroup "Element-wise Operations, Aggregation, and Sequence Generation", which includes the LinqBegin4 task.

The task can be displayed in html format immediately after launching the program (without displaying the taskbook window), if you add the "#" symbol to the parameter of the Task method (for example, Task("LinqBegin4#");).

The html page mode is convenient in several respects. First, only in this mode can you familiarize yourself with the preambles to the task group and its subgroups and thereby obtain additional information that may be useful when performing the tasks. Secondly, the browser window with the html page does not need to be closed to continue working on the task; thus, using this window, you can at any time familiarize yourself with the task description, even if the current state of the program does not allow compiling it and running it.

It is possible to display all tasks in html mode belonging to a certain group. To do this, in the parameter of the Task method, you should delete the task number, leaving only the group name and the "#" symbol (for example, Task("LinqBegin#");).

Having finished this overview of the taskbook capabilities intended for creating a project template and getting acquainted with the selected task, let's move on to describing the solution process itself.

Performing the Task

The first stage in performing any task is inputting the initial data. If the task is performed using an electronic taskbook, then it is the taskbook that forms the set of initial data and provides it to the student's program. Obviously, to obtain the initial data prepared by the taskbook, the program must use special input methods. These static methods are defined in the PT class, which is an ancestor of the MyTask class. Therefore, when calling input methods in the Solve function, you do not need to specify the name of the class in which they are defined.

In accordance with the input organization approach adopted in the standard .NET library, input uses functions without parameters, each of which returns one element of the initial data of a specific type. The Programming Taskbook provides functions for inputting data of all basic types:

  • GetBool() - for inputting logical data (type bool);
  • GetInt() - for inputting integer data (type int);
  • GetDouble() - for inputting real data (type double);
  • GetChar() - for inputting character data (type char);
  • GetString() - for inputting string data (type string).

These functions are sufficient to organize the input of any data included in the tasks of the LinqBegin, LinqObj, and LinqXml groups. For example, to input data from the LinqBegin4 task, you can use the following fragment (here and below we will only provide the description of the Solve function, since the rest of the project does not require changes):

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  string[] a = new string[GetInt()];
  for (int i = 0; i < a.Length; i++)
    a[i] = GetString();
}

In this fragment, first the character c is described and input, then the string array a is described and created (while reading the size of the initial sequence, which is specified in the array constructor), after which a loop is organized to input the values of the array elements themselves.

When running the program, the taskbook window will look similar to the one shown in the figure. This run is no longer considered introductory, since the program performs data input-output actions.

Note the additional panel (indicators panel), which contains information about the number of input and output data, as well as the number of successfully passed test runs of the program.

In our case, the program has input all the initial data (as evidenced by the first indicator and the text associated with it), but the resulting data has not been output. We can say that we have successfully passed the first stage of the solution, related to inputting the initial data. Therefore, the information panel contains the text "Correct data input", and its background has changed to light blue.

Note that when various errors are detected, the background of the information panel also changes, and in this case various shades of red are used (for example, for errors related to input or output of an insufficient number of initial or resulting data, orange is used). Furthermore, the same color highlights the title of the section related to the error (for example, when outputting an insufficient number of resulting data, the title "Results obtained" is highlighted in orange). In case of an erroneous solution, the taskbook window displays not only the section with the obtained results but also the section with an example of the correct solution. All these additional window elements are designed to simplify the search and correction of errors detected by the taskbook during program execution.

Before proceeding to discuss the solution of the task, note one additional feature available when performing tasks of the LinqBegin group, namely the presence of auxiliary input functions GetEnumerableInt() and GetEnumerableString(), described in the MyTask class and providing quick input of integer and string sequences. Thanks to these functions, the actions for inputting initial data are simplified, and the obtained solutions become more concise and clear.

Using the GetEnumerableString function, the solution fragment responsible for inputting the initial data will consist of only two lines:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  var a = GetEnumerableString();
}

In the provided program variant, a feature of the C# language is used, which appeared simultaneously with LINQ technology and is closely related to it: any variable can be described using the keyword var, if this variable is immediately initialized with some value. In such a situation, the variable type is automatically determined by the type of the initializing expression (it is also said that the variable type is inferred from the type of the initializing expression). In our case, this capability allows for a more concise notation - the word var instead of the type IEnumerable<string>, which is the return value type of the GetEnumerableString function. Note that in some cases (related to the use of so-called anonymous types, which also appeared in the C# language simultaneously with LINQ technology) it is impossible to do without the var keyword when describing variables.

When running the new solution variant, the same message ("Correct data input") will be displayed.

Let's proceed to process the input data, based on the application of LINQ technology. When choosing a suitable LINQ method, it is useful to refer to the list of methods specified in the preamble to the subgroup to which the task belongs, since it is these methods that should be applied when solving tasks in this subgroup. However, when performing tasks from subsequent subgroups, it may be necessary to use not only the methods that first appear in these subgroups but also any of the methods considered in the preceding subgroups.

In our case, obviously, it is necessary to use one of the methods related to selecting a single element: Single or SingleOrDefault (element-wise operations also include the methods First and FirstOrDefault, related to selecting the initial element of a sequence, and Last and LastOrDefault, related to selecting its final element). It should be noted that of all methods related to element-wise operations, the methods Single and SingleOrDefault are characterized by the most complex behavior (which is precisely why we chose the LinqBegin4 task for analysis).

For any element-wise operation, a predicate parameter can be specified, which allows selecting the elements for which the corresponding operation should be performed. Such a parameter must be formatted as a lambda expression, accepting an element of the sequence and returning a logical value (true if the element should be considered during the operation, false otherwise).

Remark. Everywhere further, delegate parameters, used in the methods of the LINQ to Objects and LINQ to XML interfaces, are called lambda expressions. Of course, such parameters can be specified as delegates defined in various ways (for example, formatted as an auxiliary method of a class or described as an anonymous method according to C# version 2.0 rules), however, representing such parameters as lambda expressions is the most natural and clear.

According to the LinqBegin4 task condition, it is required to analyze the elements of the initial sequence ending with the character C. As a predicate in this case, the following lambda expression can be used (before the lambda expression symbol => its parameter is specified, and after it - the return value):

e => e.Length != 0 && e[e.Length - 1] == c

Note that without the first condition, the specified logical expression would lead to an exception IndexOutOfRangeException when processing empty strings (which may be included in the initial sequence). To combine the two conditions, it is necessary to use the && operation (logical AND with short-circuit evaluation), in which, if the first operand is false, the second operand is not analyzed (the & operation with full evaluation cannot be used in this case, nor can the order of these conditions be changed). Instead of comparing string lengths in the first condition, you can compare the strings themselves (e != ""), however, this comparison will be slower than comparing lengths.

The specified variant of predicate implementation is not the only possible one. If you use the EndsWith method of the string class, then the predicate can be represented as a single condition:

e => e.EndsWith(c.ToString())

In this case, however, it is required to explicitly convert the character c to the string type by calling the ToString method for it. Furthermore, it should be considered that the EndsWith method requires comparing strings, which is performed slower than comparing characters.

Let's choose one of the described predicates and use it in the Single method, which returns the single element of the sequence satisfying the specified predicate. The obtained element must be passed to the taskbook to check the correctness of the solution; for this, another static method of the PT class should be used - the Put method, which can accept an arbitrary number of parameters of basic types (logical, integer, real, character, string). As a result, the next variant of the Solve function with the solution of the task will take the form:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  var a = GetEnumerableString();
  Put(a.Single(e => e.Length != 0 && e[e.Length - 1] == c));
}

The obtained program will correctly process sequences containing a single element ending with the character c, however, if the sequence does not contain such elements or there is more than one, then an exception InvalidOperationException will be thrown:

Two types of messages will be associated with the exception: "Sequence contains more than one matching element" (as in the figure, in Russian) and "Sequence contains no matching element".

To catch and handle exceptions, the try–catch construct should be used. To access the exception object, it must be described in the header of the catch block. Having this object, you can access its Message field, containing a textual description of the exception, determine the reason for throwing the exception by this field, and output the required string (recall that according to the task condition, in case of absence of suitable elements, an empty string should be output, and in case of two or more suitable elements - the string "Error").

We get the first variant of the correct solution:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  var a = GetEnumerableString();
  try
  {
    Put(a.Single(e => e.Length != 0 && e[e.Length - 1] == c));
  }
  catch (InvalidOperationException ex)
  {
    if (ex.Message.Contains("более"))
      Put("Error");
    else
      Put("");
  }
}

The solution can be simplified by using a variant of the Single method - SingleOrDefault, which does not lead to throwing an exception in case the sequence lacks the required elements (note that other element-wise operations containing the text "OrDefault" behave similarly: FirstOrDefault and LastOrDefault). In such a situation, the SingleOrDefault method returns the default value (for numeric sequences this is 0, for string sequences, as for any sequences with reference data, - the value null). Thus, when using the SingleOrDefault method, an exception will be thrown only if the initial sequence contains more than one required element.

In the second solution variant, the catch section became shorter, and the try section increased:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  var a = GetEnumerableString();
  try
  {
    string res = a.SingleOrDefault(e => e.Length != 0 &&
      e[e.Length - 1] == c);
    Put(res != null ? res : "");
  }
  catch
  {
    Put("Error");
  }
}

Instead of the ternary operation ?:, you can use the ?? operation which appeared in C# version 2.0 and is often used in programs applying LINQ technology. The expression a ?? b returns the value a if it is not null, and the value b otherwise. Thus the Put operator from the try section can be rewritten as

    Put(res ?? "");

In such a situation, the need for the variable res disappears, and the try section is reduced to a single statement:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  var a = GetEnumerableString();
  try
  {
    Put(a.SingleOrDefault(e => e.Length != 0 &&
      e[e.Length - 1] == c) ?? "");
  }
  catch
  {
    Put("Error");
  }
}

Since the variable a is used in only one place in the program, it is also unnecessary. This allows for an even more shortened solution:

public static void Solve()
{
  Task("LinqBegin4");
  char c = GetChar();
  try
  {
    Put(GetEnumerableString().SingleOrDefault(e =>
      e.Length != 0 && e[e.Length - 1] == c) ?? "");
  }
  catch
  {
    Put("Error");
  }
}

It should be noted that when using LINQ technology, very often the substantive part of the program is a chain of sequential method calls; in our case, the chain consists of only two calls (GetEnumerableString and SingleOrDefault), however, it additionally includes the ?? operation. Also note that when using the FirstOrDefault and LastOrDefault methods, there is no need for explicit exception handling (although the ?? operation for them may also be useful).

After passing seven successful test runs of the program, we will get a message that the task is completed (see the figure). During a successful test run, the window lacks the section with an example of the correct solution, because this section would coincide with the section of obtained results.

By pressing the [F2] key, you can display a window with the protocol of performing this task (this protocol is stored in encrypted form in the results file results.dat). In our case, information about the progress of the task may look as follows:

LinqBegin4  S16/04 11:43 Acquaintance with the task.
LinqBegin4  S16/04 11:55 Correct data input.
LinqBegin4  S16/04 12:03 Error InvalidOperationException.--3
LinqBegin4  S16/04 12:08 The task is solved!

The character specified before the information about the date and time of the test run determines the programming language in which the task was performed (the character S corresponds to the C# language (C Sharp); for the VB.NET language, the character B is used, for the F# language - the character F). The numerical value specified at the end of the line indicates the number of test runs performed consecutively and completed with the same result.


PrevNext

 

  Рейтинг@Mail.ru

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

Last revised:
01.01.2026