- Settings:
Page
Normal
Plain layout without fancy styles
Font
Normal
Large fonts
Very large fonts
Colour
Normal
Restful colour scheme
Light text on a dark background

Note: user account creation on this site has been disabled.

Programming, speculative fiction, science, technology
Powered by Drupal, an open source content management system
Scope
Include Children
If you select a term with children (sub-terms), do you want those child terms automatically included in the search? This requires that "Items containing" be "any."
Categories

Everything in the Events vocabulary

"Events" is used for: Convention Post, Page, Story.

People mentioned in the articles

"People" is used for: Page, Convention Post, Story.

Themes mentioned in the article

"Themes" is used for: Page, Convention Post, Story.
Skip to top of page

LINQ -- replacing loops with a couple lines of code

Recently I've been taking a closer look at LINQ, or Language-Integrated Query -- a set of C# and Visual Basic features that let you write SQL-like queries against data structures such as arrays or hashtables. I like how it lets you replace loops with just a line or two of code. I will illustrate it here with two simple exercises.

1. Reading lines from a file, and splitting them into arrays

Let's say we need to read lines from a file, split it along certain delimiters, and store all the resulting fields in an array of arrays. The fields from each line will be in a one dimensional array, each of which in turn will be an element of a two-dimensional array.


namespace FieldReorderLINQDemo
{
    class LINQDemoReorderFields
    {
        static void Main(string[] args)
        {
            string inputFilePath = args[0];
            string[] inputLines;
            try
            {
                inputLines = File.ReadAllLines(inputFilePath);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error occurred opening the input file {0}:", inputFilePath);
                Console.WriteLine(e.Message);
                return;
            }

            FieldParser fs = new FieldParser();
            string[][] fields = fs.readAndParseFields(inputLines);
        }
    }

    public class FieldParser
    {
        public string[][] readAndParseFields(string[] inputLines)
        {
            string[][] fields = new string[inputLines.Length][];
            int lineNum = 0;
            foreach (string inputLine in inputLines)
            {
                Console.WriteLine(inputLine);
                fields[lineNum] = Regex.Split(inputLine, @"\t+");
                lineNum++;
            }
            return fields;
        }
    }
}

Now let's rewrite readAndParseFields() to use LINQ:


        public string[][] readAndParseFields(string[] inputLines)
        {
            IEnumerable splitQuery = from inputLine in inputLines
                                                select Regex.Split(inputLine, @"\t+");
            return splitQuery.ToArray();
	}

Regex.Split(inputLine, @"\t+") splits every line in inputLines using as a delimiter the string matched by the regular expression; in this case, it splits the lines by one or more tabs, and returns a string[]. This query from each inputLine selects a string[]. The first statement just defines the query, but doesn't execute it. Calling splitQuery.ToArray() executes it.

2. Reading the lines and reordering the fields

Now let's do something a little more complex with those fields. Suppose we don't just want to split the strings and populate an array, but we also want to rearrange the fields in this two-dimensional matrix. Maybe this file is a text file exported from Excel, and you want some columns to switch places. Maybe column 1 should become column 4, and column 2 should become column 1, and so on. Let's say, we have 4 columns, numbered 0, 1, 2, 3, and we want to arrange them in a permutation 3, 1, 0, 2.

The "traditional" way, wwith loops, we could do it as follows. (We are assuming, for the sake of simplicity, that the number of fields in each line is the same, and equal to the length of fieldOrderNumbers -- the array of field order numbers in the new permutation, such as {3, 1, 0, 2}. In reality, we would need to add error checking.)



string[][] parseAndReorderFields(string[] inputLines, int[] fieldOrderNumbers)
{
	// The fields resulting from parsed strings, in the original order
	string[][] inputFields = new string[inputLines.Length][];
	// The fields after reordering
	string[][] fields = new string[inputLines.Length][];
	int lineNum = 0;
	foreach (string inputLine in inputLines)
	{
		inputFields[lineNum] = Regex.Split(inputLine, @"\t+");
                fields[lineNum] = new string[inputFields[lineNum].Length];
                for (int j = 0; j < inputFields[lineNum].Length; j++)
                    fields[lineNum][j] = inputFields[lineNum][fieldOrderNumbers[j]];
                lineNum++;
	}
	return fields;
}

With LINQ, we could do it in far fewer lines of code, and clearer, too:


        public virtual string[][] parseAndReshuffleFields(string[] inputLines, int[] fieldOrderNumbers)
        {
            IEnumerable reorderQuery = from inputLine in inputLines
                                                 let rowFields = Regex.Split(inputLine, @"\t+")
                                                 let dict = fieldOrderNumbers.ToDictionary(k => k, k => rowFields[k])
                                                 select dict.Values.ToArray()
            string[][] fields = reorderQuery.ToArray();
            return fields;
        }

As before, Regex.Split(inputLine, @"\t+") splits every line in inputLines using the string matched by the regular expression as a delimiter -- in this case, by one or more tabs. The resulting string[] is assigned to rowFields.

Our query needs to select an array in which the elements of rowFields will be arranged in the order of fieldOrderNumbers: for each number k in fieldOrderNumbers, the corresponding element in the new array would be rowFields[k]. We do that through an intermediary step of creating a Dictionary whose keys will be fieldOrderNumbers, and the value for each key k will be rowFields[k]. We create this Dictionary in the line

let dict = fieldOrderNumbers.ToDictionary(k => k, k => rowFields[k])

The k in the parentheses designates each element of fieldOrderNumbers. k => k -- the key selector function -- maps it to itself; k => rowFields[k] -- the element selector function -- maps it to a rowFields[k] element. This MSDN article has more about Enumerable.ToDictionary.

The last line, select dict.Values.ToArray(), selects the values of this dictionary -- a string[] array whose elements of rowFields[k] in the right order. Then the statement

string[][] fields = reorderQuery.ToArray();

actually executes the query, and gets the two dimensional array we wanted.