Build a Windows process tree in C#

[process tree]

A process tree is a tree that shows the processes running on the computer arranged hierarchically so you can see which processes started other processes. My first attempts at this used techniques that I found scattered around the internet. Unfortunately those techniques generally worked by finding the parent process for individual processes. Repeating those techniques for each of the hundreds of processes on my system took a couple of minutes.

This example uses a different approach that only takes about half a second. It gets information on all of the process at once and then uses the information to build the tree.

ProcessInfo

The program stores information about a process in the following ProcessInfo class.

class ProcessInfo : IComparable<ProcessInfo>
{
    public Process TheProcess;
    public ProcessInfo Parent;
    public List<ProcessInfo> Children = new List<ProcessInfo>();

    public ProcessInfo(Process the_process)
    {
        TheProcess = the_process;
    }

    public override string ToString()
    {
        return string.Format("{0} [{1}]",
            TheProcess.ProcessName, TheProcess.Id);
    }

    public int CompareTo(ProcessInfo other)
    {
        return TheProcess.ProcessName.CompareTo(
            other.TheProcess.ProcessName);
    }
}

The class stores a reference to the process that it represents in the variable TheProcess. Note that the class does not keep itself up to date so, for example, if the process ends, the ProcessInfo object does not know that.

As you can probably guess, the class stores the process’s parent process in variable Parent. The Children list holds references to this process’s child processes.

The class’s constructor simply saves a reference to the process. The ToString method returns the process’s name and ID.

Finally the CompareTo method compares this object’s ProcessName to another object’s ProcessName. That method implements the IComparable interface, which allows the program to sort ProcessInfo objects alphabetically.

Form1_Load

When the program starts, the following Load event handler does most of the interesting work.

private int NumProcesses, NumThreads;

private void Form1_Load(object sender, EventArgs e)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    Dictionary<int, ProcessInfo> process_dict =
        new Dictionary<int, ProcessInfo>();

    // Get the processes.
    foreach (Process process in Process.GetProcesses())
    {
        process_dict.Add(process.Id, new ProcessInfo(process));
    }

    // Get the parent/child info.
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(
       "SELECT ProcessId, ParentProcessId FROM Win32_Process");
    ManagementObjectCollection collection = searcher.Get();

    // Create the child lists.
    foreach (var item in collection)
    {
        // Find the parent and child in the dictionary.
        int child_id = Convert.ToInt32(item["ProcessId"]);
        int parent_id = Convert.ToInt32(item["ParentProcessId"]);

        ProcessInfo child_info = null;
        ProcessInfo parent_info = null;
        if (process_dict.ContainsKey(child_id))
            child_info = process_dict[child_id];
        if (process_dict.ContainsKey(parent_id))
            parent_info = process_dict[parent_id];

        if (child_info == null)
            Console.WriteLine(
                "Cannot find child " + child_id.ToString() +
                " for parent " + parent_id.ToString());

        if (parent_info == null)
            Console.WriteLine(
                "Cannot find parent " + parent_id.ToString() +
                " for child " + child_id.ToString());

        if ((child_info != null) && (parent_info != null))
        {
            parent_info.Children.Add(child_info);
            child_info.Parent = parent_info;
        }
    }

    // Convert the dictionary into a list.
    List<ProcessInfo> infos = process_dict.Values.ToList();

    // Sort the list.
    infos.Sort();

    // Populate the TreeView.
    NumProcesses = 0;
    NumThreads = 0;
    foreach (ProcessInfo info in infos)
    {
        // Start with root processes.
        if (info.Parent != null) continue;

        // Add this process to the TreeView.
        AddInfoToTree(trvProcesses.Nodes, info);
    }
    lblCounts.Text =
        "# Processes: " + 
        NumProcesses.ToString() + ", " +
        "# Threads : " +
        NumThreads.ToString();

    watch.Stop();
    Console.WriteLine(string.Format("{0:0.00} seconds",
        watch.Elapsed.TotalSeconds));
}

After starting a stopwatch, the code creates a dictionary that uses an integer (process ID) for the keys and ProcessInfo objects for the values.

Next the code loops through the processes returned by the Process.GetProcesses. For each returned process, the program creates a new ProcessInfo object and adds that object to the dictionary.

After this step, the program has a list of the system’s processes, but it doesn’t have any parent/child information. To get that information, the program uses a ManagementObjectSearcher. (To use that class, add a reference to the System.Management library and add the statement using System.Management at the top of the file.)

The program initializes the searcher with the query SELECT ProcessId, ParentProcessId FROM Win32_Process. For every process that is running, that fetches the process’s ID and its parent ID. The program calls the object’s Get method to execute the query and return a collection holding the results.

Now the program loops through the results. For each result, the program gets the child and parent process IDs and looks them up in the dictionary to find the corresponding ProcessInfo objects. If it finds both ProcessInfo objects, the code sets the child’s Parent field and adds the child to the parent’s Children list.

After the program has finished looping through the searcher’s results, the objects in the dictionary have links to their parents and children. To make using the objects easier, the program gets the dictionary’s value collection, converts it into a list, and sorts the result. (Remember that the ProcessInfo class implements IComparable so the list can sort the objects.)

Next the program uses the list to populate the program’s TreeView control. To do that, it loops through the list. If a process has a parent, then it will be placed in the tree when its parent is placed in the tree. If a process does not have a parent, then the code adds it as a top-level node in the tree by calling the AddInfoToTree method described shortly.

After it finishes building the tree, the program displays the number of processes and threads that it found, and the elapsed time.

AddInfoToTree

The following AddInfoToTree method adds a ProcessInfo to the TreeView.

// Add a ProcessInfo, its children, and its threads to the tree.
private void AddInfoToTree(TreeNodeCollection nodes, ProcessInfo info)
{
    // Add the process's node.
    TreeNode process_node = nodes.Add(info.ToString());
    process_node.Tag = info;
    NumProcesses++;

    // Add the node's threads.
    if (info.TheProcess.Threads.Count > 0)
    {
        TreeNode thread_node = process_node.Nodes.Add("Threads");
        foreach (ProcessThread thread in info.TheProcess.Threads)
        {
            thread_node.Nodes.Add(string.Format(
                "Thread {0}", thread.Id));
            NumThreads++;
        }
    }

    // Sort the children.
    info.Children.Sort();

    // Add child processes.
    foreach (ProcessInfo child_info in info.Children)
    {
        AddInfoToTree(process_node.Nodes, child_info);
    }

    // Expand the main process node.
    if (info.Children.Count > 0)
        process_node.Expand();
}

The method takes as its first parameter the TreeNodeCollection where the process should be added.

The method adds a new node to the nodes collection and saves the ProcessInfo object in the new node’s Tag property. This example does not use that value, but another program might want to be able to find the ProcessInfo object for a tree node. It could then use that object to get the process object and all of the properties that it contains.

The code increments NumProcesses to indicate that it added another process to the tree.

Next, if the process has threads, the method adds them to the tree. It creates a child node labeled Threads and then loops through the threads, adding them to the thread node.

Before the method adds the process’s children to the tree, it sorts the children. (Again, this is possible because the ProcessInfo class implements IComparable.) The code then loops through the children and recursively calls the AddInfoToTree method to add the child’s information below the current process’s node.

The method finishes by expanding the process’s node if the node has children. The result is that TreeView nodes that have children are expanded so you can see all of the processes, but Threads nodes are not expanded. (There are so many of them that it make the tree hard to read.)

Conclusion

Download the example to experiment with it. If you like, you can modify the example to manipulate the processes. For example, you could click on tree node and then call the corresponding process’s Kill method. (There’s some commented out code in the example that does this.)


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, system | Tagged , , , , , , , , , | 5 Comments

Draw lines with gaps to show they are passing under other lines in C#

[draw lines]

This example shows how to draw lines with gaps in them to show that they are passing under other lines. It seems like there must be a name for this, but I couldn’t find it so I’m calling them “cut lines.” (If you know the “official” name for this, please post a comment below.)

You could calculate where two lines intersect and then break the line on the bottom into two pieces, shortening them a bit so they leave a gap where the upper line is supposed to be. If you had many lines, you might then need to break the pieces into smaller pieces and so on until you finish examining every pair of lines for intersections. That should all work, but it sounds like a lot of trouble.

Fortunately there’s a much simpler way to draw lines with gaps like this. Loop through the lines in bottom-to-top order. For each line, first draw it with a slightly thicker pen that uses the background color. That creates a channel for the line through the lines below it. Then draws the line with its usual pen.

To make this slightly easier, I created the following method to draw lines.

// Draw a line segment in the indicated color.
private void DrawLine(Graphics gr, Pen bg_pen, Pen fg_pen,
    Point start_point, Point end_point)
{
    if (bg_pen != null)
        gr.DrawLine(bg_pen, start_point, end_point);
    gr.DrawLine(fg_pen, start_point, end_point);
}

If the background pen bg_pen is not null, the method draws the line using that pen. It then draws the line again using the foreground pen fg_pen.

The following code shows how the program’s Paint event handler uses this method to draw lines.

private void picLines_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the previously saved lines.
    Pen bg_pen = null;
    if (chkUseCuts.Checked) bg_pen = new Pen(picLines.BackColor, 8);
    using (Pen fg_pen = new Pen(Color.Blue, 4))
    {
        for (int i = 0; i < StartPoints.Count; i++)
        {
            DrawLine(e.Graphics, bg_pen, fg_pen,
                StartPoints[i], EndPoints[i]);
        }

        // Draw the new line if there is one.
        if (Drawing)
        {
            fg_pen.Color = Color.Red;
            DrawLine(e.Graphics, bg_pen, fg_pen,
                NewStartPoint, NewEndPoint);
        }
    }
    if (bg_pen != null) bg_pen.Dispose();
}

This code defines the bg_pen variable to hold the background pen and sets it to null. Then if you have checked the program’s Use Cuts box, the method sets the pen equal to a new eight-pixel-wide pen using the drawing’s background color.

The code then creates a four-pixel-wide blue pen.

Next the method loops through the line segments whose end points are stored in the StartPoints and EndPoints lists. For each segment, the code simply calls the earlier DrawLine method to draw the line above the previous lines.

If you are in the middle of drawing a new line, the program changes the foreground pen’s color to red and draws the new line so far.

Finally, if the background pen is not null, the code calls its Dispose method. It is important to call the Dispose method of any object that has such a method so it can free up resources that are limited on the system. Pens and brushes are two common items that have a Dispose method.

If you define an object inside a using statement, as the code does with the foreground brush, then the program automatically calls its Dispose method when the using block ends.

[draw lines]

Unfortunately this technique is harder to apply to more complicated objects such as self-overlapping polygons (like a star) or mutually overlapping objects (like interlocked circles). In those cases, you will probably need to draw all of the objects and then go back and draw just their points of intersections using the new technique. For an example that does something like this, see the post Draw interlocked circles in C#.

Download the example program to experiment with it and to see other details such as how the program lets you click and drag to select new lines.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, Uncategorized | Tagged , , , , , , , , | Leave a comment

Recommended Book on CD: “The Serpent of Venice” by Christopher Moore

This book continues the adventures of Pocket, the fool who is the title character in the previous Christopher Moore book Fool. The book begins with Pocket’s murder, or at least attempted murder. Hilarity then ensures as Pocket spends the rest of the book avenging his own death.

If you read my earlier post about Moore’s book Fool, you may remember how much I enjoyed the performance of reader Euan Morton. Morton brings the same amazing set of skills to this book, giving each character a separate, recognizable, and somehow wholly appropriate voice. His performances are so good that I may seek out books based on his reading them. (I notice that he is also the reader for Moore’s book Sacre Bleu. I’ve read it but now may need to find it on CD.)

I also notice that Moore has a new book coming out, Shakespeare for Squirrels and Morton is also the reader for that book. At a whopping $31 for the paperback and $40 for the CD version, I may need to wait on that one for a while or try the $15 Kindle edition.

As with all of Moore’s books, skip this book if you don’t like bawdy humor. But if you like general silliness in a Shakespearean setting, this is another great listen.


Follow me on Twitter   RSS feed   Donate




Posted in books | Tagged , , , , | Leave a comment

Animate maze solving, version 3

[animate maze solving]

The previous maze-solving examples Animate maze solving, version 1 and Animate maze solving, version 2 used a recursive method named Solve that returns an IEnumerable containing the paths that the program was searching.

Removing Recursion

To remove the recursion, think about what happens during recursion or any method call for that matter. Here’s the structure of the Solve method.

private IEnumerable<List<MazeNode>> Solve(
    MazeNode end_node, List<MazeNode> path)
{
    ...

            foreach (List<MazeNode> test_path in Solve(end_node, path))
                yield return test_path;
    ...
}

The method does a bunch of stuff and then uses a foreach loop to iterate over the paths returned by a recursive call to the Solve method. When the program makes that call, it saves the current call’s position and local variables. It then calls the method again passing it the desired end node and the path that we need to examine with a new node added at the end.

To remove the recursion, we can imitate those steps. In order to animate the solution, we also need to save that kind of state information and then later restore it so we can continue execution at the animation’s next step.

The Solve Method

Here’s the new version of the Solve method.

private List<MazeNode> Path = null;
private List<int> LastUsedNeighbor = null;

// The final node in the Path list is the node we are visiting.
// If LastUsedNeighbor has an entry for that node, then
// we have visited some of its children.
// See if we have a solution.
// If not, try the last node's next neighbor.
private void Solve()
{
    // See if we have reached the end node.
    int last_node_index = Path.Count - 1;
    MazeNode last_node = Path[last_node_index];
    if (last_node == EndNode)
    {
        FoundSolution = true;
        tmrStep.Enabled = false;
        return;
    }

    // See if the last node has any more neighbors we can visit.
    bool found_neighbor = false;
    int neighbor_index = LastUsedNeighbor[last_node_index];
    MazeNode neighbor = null;
    for (; ; )
    {
        // Try the next neighbor.
        neighbor_index++;

        // If we have checked all neighbors, break.
        if (neighbor_index >= last_node.Neighbors.Count) break;

        // See if this neighbor is not already in the current path.
        neighbor = last_node.Neighbors[neighbor_index];
        if (!neighbor.InPath)
        {
            // Visit this neighbor.
            found_neighbor = true;
            LastUsedNeighbor[last_node_index] = neighbor_index;
            break;
        }
    }

    // See if we found a neighbor to visit.
    if (found_neighbor)
    {
        // Visit this neighbor.
        Path.Add(neighbor);
        LastUsedNeighbor.Add(-1);
        neighbor.InPath = true;
    }
    else
    {
        // We have visited all of the last node's neighbors.
        // Remove the last node from the Path list.
        last_node.InPath = false;
        Path.RemoveAt(last_node_index);
        LastUsedNeighbor.RemoveAt(last_node_index);
    }
}

As before, the Path list stores the path we are currently exploring. The LastUsedNeighbor list holds the index of the last neighbor that we have considered for the last node in the path. When a “recursive” call returns, we use LastUsedNeighbor to decide which neighbor to explore next. If the current node has no more neighbors, then the “recursion” ends and control moves to the “calling method.”

The Solve method first checks to see if we have reached the maze’s destination node. If we have reached that node, the method sets FoundSolution to true, disables the tmrStep Timer, and returns.

If we have not yet found a solution, the method gets the index of the last neighbor visited. It then enters a loop to consider other neighbors of the current node. If the next possible neighbor index is greater than the current node’s neighbor count, then the code breaks out of the loop having found no new neighbor to use.

If the next neighbor considered is not currently in the test path, the code sets found_neighbor to true to indicate that we should use that neighbor. It adds the neighbor index to the LastUsedNeighbor list, and breaks out of the loop.

When the loop ends, the method checks whether it found a new neighbor to try. If it did find a neighbor, the code adds it to the Path list. It also adds -1 to the LastUsedNeighbor list to indicate that the last neighbor of this node is -1. That makes the Solve method’s loop starting looking for a new neighbor at index 0, which is this node’s first neighbor.

When the loop ends, if the method did not find a new neighbor to explore, then it must “move up” the “recursive call stack.” In that case, it removes the last node from the Path list because that node did not lead to a solution. It also removes the last entry from the LastUsedNeighbor list because we are done considering that node’s neighbors.

Now each time this method is called, it performs one step in the search for a complete path.

Searching for a Solution

When the program needs to start finding a path through the maze, it executes the following StartSolving method.

// Start solving the maze.
private void StartSolving()
{
    // Remove any previous results.
    Path = new List<MazeNode>();
    LastUsedNeighbor = new List<int>();
    foreach (MazeNode node in Nodes)
        node.InPath = false;

    // Make the nodes define their neighbors.
    foreach (MazeNode node in Nodes)
        node.DefineNeighbors();

    // Start at the start node.
    Path.Add(StartNode);
    LastUsedNeighbor.Add(-1);
    StartNode.InPath = true;

    // Solve.
    FoundSolution = false;
    tmrStep.Enabled = true;
}

This method creates new, empty Path and LastUsedNeighbor lists and then defines the nodes’ neighbors as usual. It then adds the start node to the Path list and adds -1 to the LastUsedNeighbor list. It sets the start node’s InPath value to true, sets FoundSolution to false, and starts the tmrStep Timer.

When the timer’s Tick event fires, the following event handler executes.

// Take a step in solving the maze.
private void tmrStep_Tick(object sender, EventArgs e)
{
    Solve();
    picMaze.Refresh();
}

This code calls the Solve method to take the next step searching for the solution. It then refreshes the picMaze PictureBox so its Paint event handler can display the current path.

Conclusion

This example uses a timer to animate maze solving. Instead of using recursion, it uses the Path and LastUsedNeighbor lists to keep track of where it is in the process of finding a path through the maze. You can always remove recursion from any program if you try hard enough. In the worst case, you can imitate the recursion as this example does.

The previous version of the program used an enumerator created by a recursive method. The process of returning an enumerator is a bit strange and recursion can also be confusing, but personally I like that solution. It’s weird, but understandable once you figure out how to return enumerations.

Download the examples to see additional details and to compare their code.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics | Tagged , , , , , , , , , | Leave a comment

Animate maze solving, version 2

[animate maze solving]

My earlier post Animate maze solving, version 1 uses a method that returns an enumeration to show the steps used to find a path through a maze. That method works but it uses a loop that includes calls to Thread.Sleep and Application.DoEvents.

That technique works, but it’s inelegant and can sometimes interfere with the normal operation of the event loop. For instance, the previous example needed to provide a special way to break out of the loop if the user tried to close the form. If the code did not do that, then the program could remain in a sort of zombie mode where the form has disappeared but the code was still running.

This post describes an improved solution.

[Essential Algorithms]
Note that this algorithm is similar to those described in my book Essential Algorithms, Second Edition. That book explains many other algorithms for finding paths through networks such as the mazes used here.

Approach

Making a method return an enumeration is a clever way to return results from inside a sequence of recursive calls and then resume those calls later, and its still worth using that technique. The problem with the previous example was that it looped through the enumeration in a foreach loop. That made it awkward to process events within the loop and to leave the loop early.

This example’s approach takes advantage of a useful but often overlooked fact about enumerations: they have enumerators. An enumerator is an object that keeps track of the position within an enumeration. A foreach loop uses an enumerator to loop through the values.

The new example keeps its own reference to an enumerator and uses it to move through the values when a timer fires. Because the program uses a timer, it doesn’t need to sleep and use Application.DoEvents to process pending events. That’s basically what the normal event loop does while the program is waiting for the timer to fire.

Code

The new example’s Solve method is the same as before. It still recursively searches for a path through the maze, uses yield return to return paths, and uses yield break to break out of the recursion when it has found a solution.

The following code shows the new version of the StartSolving method.

IEnumerator<List<MazeNode>> PathEnumerator = null;

// Start solving the maze.
private void StartSolving()
{
    // Remove any previous results.
    Path = new List<MazeNode>();
    foreach (MazeNode node in Nodes)
        node.InPath = false;

    // Make the nodes define their neighbors.
    foreach (MazeNode node in Nodes)
        node.DefineNeighbors();

    // Start at the start node.
    Path.Add(StartNode);
    StartNode.InPath = true;

    // Create the IEnumerable to generate paths.
    FoundSolution = false;
    IEnumerable<List<MazeNode>> enumerable = Solve(EndNode, Path);

    // Get the enumerable's enumerator.
    PathEnumerator = enumerable.GetEnumerator();

    // Start the timer.
    tmrStep.Enabled = true;
}

This new version builds kits maze network and adds the start node to the Path list as before. It then calls the Solve method and saves its IEnumerable result in variable enumerable.

Note that the Solve method does not actually do anything at this point. In fact the Solve method is not even invoked.

After setting the enumerable variable, the program calls its GetEnumerator method to get an enumerator object. Again, the Solve method doesn’t do anything at this point. The enumerator has not requested a value so Solve has not produced one yet.

The StartSolving method enables its timer and ends.

When the timer fires, the following Tick event handler executes.

// Get and display the next path.
private void tmrStep_Tick(object sender, EventArgs e)
{
    PathEnumerator.MoveNext();
    Path = PathEnumerator.Current;
    picMaze.Refresh();
}

This code calls the enumerator’s MoveNext method to generate the next value in the enumeration. This is where the Solve method springs into action. It executes until it produces a result and returns it with the yield return statement. At that point, the Solve method is suspended and control returns to the Tick event handler.

The event handler uses the enumerator’s Current property to get the enumeration’s current value and refreshes the program’s PictureBox to show the current path.

Each time the Tick event occurs, the program uses the enumerator to get the next path in the enumeration and displays it.

The new example doesn’t need a clever way to break out of a foreach loop because it does not use such a loop. It still needs the FoundSolution variable so it can climb out of the recursive calls to the Solve method.

If you create a new maze or click the maze to define the start or end point, the program disables its timer so it stops the previous search. If you close the form, the normal event loop is smart enough to stop waiting for the timer, so the program ends normally.

If you define new start and end points to begin a new search, the new call to StartSolving creates a new enumerator and the whole things starts over.

Summary

Using an enumeration with yield return and yield break in this way is still confusing, but it’s a lot less confusing than the previous version. (At least in my humble opinion.)

The remaining complexity comes from the recursion. The yield statements provide a sneaky trick for temporarily leaving the recursion, but that’s still the most complicated part. In my final post on this topic, I’ll show how you can eliminate the recursion entirely so you don’t need to use yield. I’m not sure it’s less confusing or in any way better than this version, but it does emphasize that you can remove recursion if you really want to.

Meanwhile, download the new example and experiment with it to see how it works and to see additional details.

[Essential Algorithms]
Note that this algorithm is similar to those described in my book Essential Algorithms, Second Edition. That book explains many other algorithms for finding paths through networks such as the mazes used here.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics | Tagged , , , , , , , , , | Leave a comment

Quarantine Reading Suggestions

[reading suggestions]

Currently more than a billion people are locked down to slow the spread of the COVID-19 virus. If you’re a programmer, then you can hopefully work from home and remain productive. If you can’t, here are some suggestions for books that you can read to keep your programming skills sharp. Even if you can work from home, you may find some of these books provide a much needed distraction from current events.

Improve Your Programming and Problem Solving Skillz

These books will help you improve your programming skills. Some, like the algorithms and database design books, cover topics that every programmer should study. Others, like the software engineering book, will be useful to programmers, although perhaps not as directly as the algorithms and database design books. Finally, a few books such as the three-dimensional programming book, cover more specialized topics that are not absolutely necessary for everyone. (Although I find them extremely fun and interesting!)

Note that the database design and software engineering books were written to be usable in a classroom. The times that I have taught in the classroom, I found humor an invaluable way to keep students awake and interested, so those books have a slightly more humorous feel. I wouldn’t say they’re as funny as a Steve Martin or John Oliver show, but I think they’re more amusing than most software books.

Essential Algorithms, Second Edition: A Practical Approach to Computer Algorithms Using Python and C#

Algorithms are the basis of any program. A good algorithm can mean the difference between finding a solution to a problem in minutes, days, or not at all. This book uses relatively straightforward English to explain a wide variety of algorithms that you can use under many different circumstances. Algorithms include classics such as sorting and searching algorithms, plus newer more esoteric algorithms such as algorithms to solve the skyline problem.

(While you may not need to solve the skyline problem in your everyday work, it is still quite interesting because one of its algorithms provides an excellent demonstration of a divide-and-conquer problem-solving approach.)

WPF 3d, Three-Dimensional Graphics with WPF and C#

This easy-to-read guide provides everything you need to know to get started writing striking 3D graphics programs with WPF and C#. The book’s three parts describe 3D basics, building many different shapes, and advanced topics. More than 100 example programs covering such topics as:

  • The lights, cameras, materials, texture coordinates, and other details that you need to create a 3D scene
  • Orthographic, perspective, and other projections that emphasize different aspects of a scene
  • Special material treatments such as specular reflection, wireframes, and solid and translucent materials
  • Examples of many shapes including flat polygons, boxes, Platonic solids, spheres, tori, cones, and more
  • Advanced objects such as parametric surfaces, surfaces of transformation, fractal surfaces, and 2D and 3D text
  • Higher-level scene management to let users select and move objects
  • Advanced techniques such as loading models created in other applications and using skeletons

Essential Interview Puzzles Dissected, Solving and Understanding Interview Puzzles

Whether you’re applying for a programming job or a position on Wall Street, interview puzzles are the norm at many high-tech companies. This book explains how to solve more than 200 of the hardest and most common interview puzzles in use. Essential Interview Puzzles Dissected:

  • Shows how to solve more than 200 challenging interview puzzles
  • Reveals underlying techniques that let you solve problems that you haven’t seen before
  • Tells how you can show the interviewer that you can think in an organized fashion
  • Explains how to get “partial credit” when all else fails and you just can’t solve a puzzle
  • Includes programming challenges to give you a deeper understanding of puzzles (obviously only if you’re a programmer)

Beginning Software Engineering

A complete introduction to building robust and reliable software

Beginning Software Engineering demystifies the software engineering methodologies and techniques that professional developers use to design and build robust, efficient, and consistently reliable software. Free of jargon and assuming no previous programming, development, or management experience, this accessible guide explains important concepts and techniques that can be applied to any programming language. Each chapter ends with exercises that let you test your understanding and help you elaborate on the chapter’s main concepts. Everything you need to understand waterfall, Sashimi, agile, RAD, Scrum, Kanban, Extreme Programming, and many other development models is inside!

Beginning Database Design Solutions

The database is the foundation on which all other development rests. Make your foundation solid!

This concise introduction teaches database design concepts, methods, and techniques of high-quality database design. You’ll explore creating an initial logical design and refining it into a superior database that meets users’ requirements. Plus, you’ll see how to implement the design using specific types of database platforms and build databases using SQL scripts. Each chapter begins with a list of topics that will be covered, closes with a summary that provides the key points described in the chapter, and includes a list of review questions and a series of helpful exercises.

Other Books

If you would rather read something less technical, perhaps just to take your mind off of current affairs, consider the books described on my Favorite Books page. I haven’t kept that page up to date very well, particularly the Currently Reading section, partly because if I included every book that I like the page would be enormous. Still, you may be able to find something there that you like.


Follow me on Twitter   RSS feed   Donate




Posted in books | Tagged , , , , , | 2 Comments

Recommended Book on CD: “Fool” by Christopher Moore

Until recently my job required me to drive a lot. A whole lot. To avoid boredom I’ve been listening to books on CD. So far the best books I’ve listened to are those by Christopher Moore. Not only does Moore write ridiculously funny books, but he also picks the most amazing readers to put his books on CD.

This book follows the story of Pocket, a jester in the service of King Lear. If the name Lear sound familiar, that’s not a coincidence. The book is based on Shakespeare’s play King Lear with references to other plays thrown in. The plot is full of Shakespearean-style deceit and plot twists, together with Moore’s trademark general silliness and bawdy banter. If you’re easily offended by casual naughtiness, skip this book. (And most of Moore’s other books, too, for that matter.)

One thing I’ve learned while listening to books on CD is that the reader can make or break the book. One reader for another book made all male characters sound either whiny or like complete doofuses. Another reader made all non-American characters sound Russian whether they were Russian, French, or Middle Eastern.

Euan Morton does an amazing job reading Fool. Every character has a distinct voice that’s easy to distinguish from the others, his pace is fast enough to hold your attention, and his pronunciation and emphasis make the book simple to understand.

One extra benefit that I did not anticipate is that listening to the book made it easier for me to follow the relatively complicated plot. I have read this book before in print form, but I could only read it in relatively short chunks and I sometimes found it hard to keep track of what was happening. For some reason listening to the book made it easier for me to follow. That might just be me, but consuming the book in a new format gave me a much better appreciation of it.

As I mentioned, if you don’t like bawdy humor, give this book a pass. But if you like general silliness in a Shakespearean setting, this is a great listen.


Follow me on Twitter   RSS feed   Donate




Posted in books | Tagged , , , | 2 Comments

Animate maze solving, version 1

[animate maze solving]

The post Solve mazes in C# solves mazes very quickly, but it only shows you the final path from the start point to the end point. It might be nice to make a program to animate maze solving so you can see the paths that it tries before it finds the final solution.

To use the program, enter the maze’s width and height, and then click Create. After you create the maze, left click to set the start point and right click to set the end point. After you select the start and end points, the program searches for a solution while displaying the paths that it considers.

[Essential Algorithms]
Note that this algorithm is similar to those described in my book Essential Algorithms, Second Edition. That book explains many other algorithms for finding paths through networks such as the mazes used here.

Approaches

One approach would be to save all of the paths that the program tries in some sort of list. Then the program could use a timer to loop through the saved paths and display them. Unfortunately the list of paths could be quite long. For example, in order to solve the maze shown at the top of this post, the program examined 723 paths. That’s still a manageable number, but for larger mazes the program would use a lot of memory to store the paths.

One of the bigger problems with displaying paths as they are found is that the program uses recursion to find them. To display the paths, the program would need to pause while inside a possibly deep recursive search, display the current path, and then resume the recursive execution. This post and the next two show different approaches to displaying paths while searching for them.

This post uses a trick that allows a method to return an enumeration. That lets the method return a result to the calling method and then resume execution.

Code

After you select the maze’s start and end points, the program executes the following StartSolving method to search for a solution.

private int NumPaths = 0;
private bool FoundSolution = false;
private List<MazeNode> Path = null;

// Start solving the maze.
private void StartSolving()
{
    NumPaths = 0;

    // Remove any previous results.
    Path = new List<MazeNode>();
    foreach (MazeNode node in Nodes)
        node.InPath = false;

    // Make the nodes define their neighbors.
    foreach (MazeNode node in Nodes)
        node.DefineNeighbors();

    // Start at the start node.
    Path.Add(StartNode);
    StartNode.InPath = true;

    // Solve recursively.
    FoundSolution = false;
    Running = true;
    foreach (List<MazeNode> test_path in Solve(EndNode, Path))
    {
        NumPaths++;

        Path = test_path;
        picMaze.Refresh();
        Thread.Sleep(Interval);
        Application.DoEvents();

        if (!Running) break;
    }
    Running = false;

    // Show the result.
    picMaze.Refresh();
    Console.WriteLine("Examined " + NumPaths.ToString() + " paths.");
}

This method clears any previous path and builds the maze network. It sets FoundSolution to false to indicate that the program has not yet found a solution. It also sets Running to true to indicate that the program is still running. You’ll see how those variables are used shortly.

The most interesting part of the method is its foreach loop. That loop makes the program loop over the values returned by the Solve method described shortly.

For each path returned by the Solve method, the code sets variable Path equal to the latest test path. It then refreshes the picMaze PictureBox control to show the current path. (Download the example to see how it draws the path.)

The method then calls Thread.Sleep to pause the program so you can see the path. After the program wakes up, the program calls Application.DoEvents so it can process any pending events. In particular that allows the program to notice if you create a new maze, click to set new start or end points, or close the program.

If the Running variable is false, then you have stopped the program. In that case, the StartSolving method breaks out of its loop so the method ends.

If you press the Create button, click to set new start or end points, or close the program, the program calls the following StopRunning method.

// Stop running.
private void StopRunning()
{
    Running = false;
}

This code simply sets Running to false to tell the StartSolving method to break out of its loop.

One particularly interesting time when the program calls StopRunning is when you try to close the program’s form. When you do that, the Form1_FormClosing event handler calls StopRunning. If you don’t do that, then the program knows that it should close but it continues executing its search. The form disappears but its code keeps running. Comment out the call to StopRunning in the Form1_FormClosing event handler to see this strange affect.

The example’s final interesting new piece of code is the following Solve method. You may recall that this method returns an enumeration of test paths that the StartSolving method uses in its foreach loop.

private IEnumerable<List<MazeNode>> Solve(
    MazeNode end_node, List<MazeNode> path)
{
    // Yield this path.
    yield return path;

    // See if we have reached the end node.
    MazeNode last_node = path[path.Count - 1];
    if (last_node == end_node)
    {
        FoundSolution = true;
        yield break;
    }

    // Try each of the last node's children in turn.
    foreach (MazeNode neighbor in last_node.Neighbors)
    {
        if (!neighbor.InPath)
        {
            path.Add(neighbor);
            neighbor.InPath = true;

            foreach (List<MazeNode> test_path in Solve(end_node, path))
                yield return test_path;
            if (FoundSolution) yield break;

            neighbor.InPath = false;
            path.RemoveAt(path.Count - 1);

            // Redraw the path without this latest node.
            yield return path;
        }
    }
}

This method uses a neat little trick that allows a method to return the items in an enumeration one at a time. Notice that it is declared to return an IEnumerable of lists of MazeNode objects. Each item in the enumeration is a list of nodes that represents a test path.

The method takes as a parameter the path that it should examine. The method returns that path and then explores the paths leading out of the end of that path.

The code starts by using a yield return statement to return the initial path. The yield return statement pauses the Solve method and returns control to the calling method. The calling method might be another call to Solve.

If you look a bit farther along in the Solve method’s code, you’ll see where the method calls itself. When it does, it loops through the values returned by the recursive call and uses yield return to return them higher up the recursive call stack.

The recursive calls use yield return to pass values up the call stack until they reach the StartSolving method, and that method displays the current path.

At that point, control returns to the Solve method that first called yield return and the method resumes.

After the yield return statement, the Solve method checks to see if it has found the maze’s end point. If it has, then the code sets FoundSolution to true. It then calls yield break to end its enumeration and return control to the calling method.

After checking to see if it has found a complete path, the method loops through the neighbors of the current path’s last node. For each of those neighbors, the code checks the neighbor’s InPath value to see if the node is already in the path. (That happens if the neighbor is the current path’s second-to-last node. This check prevents the path from doubling back on itself.) If the node is not in the path, the method tries adding the neighbor to the path.

The code adds the neighbor to the current path list and sets the neighbor’s InPath value to true. The method then recursively calls itself and loops through the paths that the recursive call returns. It calls yield return to return each of the paths returned by the recursive call.

After it finishes examining paths via the neighboring nodes, the method checks the FoundSolution variable to see if a solution has been found. If a path has been found, the method calls yield break to finish end enumeration. Note that in this case the method does not remove the current neighbor from the path so the path returned to the calling method includes that neighbor. In fact, the recursive calls that this instance of the method made may have left many other nodes added to the end of the initial path.

If the program has not yet found a complete path, the code sets the current neighbor’s InPath value to false to remove the node from the path. The code also removes that node from the end of the Path list.

Finally the method calls yield return to return the original path without the neighbor added at the end. That makes the program display the initial path, then display paths that extend the initial path via neighbor nodes, and finally backtrack to the initial path.

Summary

I know this is a bit confusing, so here’s a summary. The Solve method is declared to return an IEnumerable<List<MazeNode>>. The method can then use yield return to return items for the enumeration.

The StartSolving method loops through the paths returned by the enumeration. That method could break out of its loop at any time, although in this example it does not.

When it calls itself recursively, the Solve method loops through the paths returned by the recursive call and uses yield return to return them. That adds them to its own enumeration.

When the method finds a path from the start point to the end point, it uses yield break to end its enumeration. It uses the variable FoundSolution to tell instances of the method farther up the call stack that the solution has been found so they know to end their enumerations.

There are a few key steps inside the StartSolving method’s foreach loop. Each time through the loop, the method displays the current path, sleeps for a while, calls Application.DoEvents, and then checks Running to see if it should break out of the loop early. That allows the program to stop the search if you click on the maze, click Create, or close the form.

Download the example and experiment with it to see how it works and to see additional details. My next few posts show other ways to manage the trick of displaying paths while continuing a recursive search for a solution path.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics | Tagged , , , , , , , , , | 2 Comments

Solve mazes in C#

[solve mazes]

This example shows how to solve mazes that were created by the example Make and draw a maze in C#. See that example for information about how to build a maze. Read the following sections to learn how to solve mazes.

MazeNode

[Essential Algorithms]
Note that this algorithm is similar to those described in my book Essential Algorithms, Second Edition. That book explains many other algorithms for finding paths through networks such as the mazes used here.

After the earlier example builds a maze, the program has a two-dimensional array of MazeNode objects that represent the maze. Each node represents a position within the maze. A node’s Predecessor property indicates the node that lies above it in the spanning tree that was used to generate the maze. (Again, see the earlier post to understand how that works.)

[solve mazes]

The maze’s spanning tree is drawn in pink in the picture on the right. The tree’s root is the square in the maze’s upper left corner, although you could use any node as the maze’s root.

In the previous example, each node also had a Neighbors array that contains references to the nodes to its North, South, East, and West. For the current example, I’ve renamed this array AdjacentNodes because I want to use Neighbors to list the nodes that you can reach from a node. The Neighbors list holds the nodes in the AdjacentNodes array that are not blocked by a maze wall.

The only other new field on the MazeNode class is InPath, a Boolean that indicates whether the node is in the path that we are currently studying. You’ll see how that works in the next section.

The following code snippet shows the fields defined by the MazeNode class.

public class MazeNode
{
    public const int North = 0;
    public const int South = North + 1;
    public const int East = South + 1;
    public const int West = East + 1;

    // Nodes that are adjacent in order North, South, East, West.
    public MazeNode[] AdjacentNodes = new MazeNode[4];

    // The predecessor in the spanning tree.
    public MazeNode Predecessor = null;

    // The node's bounds.
    public RectangleF Bounds;

    // True if the path contains this node.
    public bool InPath = false;

    // The nodes that you can reach from this node.
    public List<MazeNode> Neighbors = null;
    ...
}

The MazeNode class’s only new method is the following DefineNeighbors method.

// Define this node's neighbors.
public void DefineNeighbors()
{
    Neighbors = new List();
    foreach (MazeNode adj in AdjacentNodes)
    {
        // See if we can reach the adjacent node from this one.
        if ((adj != null) &&
            ((adj.Predecessor == this) ||
             (adj == this.Predecessor)))
        {
            Neighbors.Add(adj);
        }
    }
}

This method initializes the node’s Neighbors list. Recall that the AdjacentNodes array holds references to the nodes to the North, South, East, and West of the current node. Note that some of those entries might be null if the node lies at the edge of the maze. Some of those

The DefineNeighbors method loops through the node’s AdjacentNodes array. If an entry is not null, then the code must determine whether the path to that node is blocked by a wall. There is no wall if the spanning tree that generated the maze contains a link between the two nodes. That happens if either this node’s Predecessor is the other node, or if the other node’s Predecessor is this node. If the adjacent node meets those criteria, then the method adds it to the current node’s Neighbors list.

Solving

After you generate a maze, you can click on it to pick start and end positions. The following code executes when you click on the maze.

private MazeNode StartNode = null, EndNode = null;

private void picMaze_MouseClick(object sender, MouseEventArgs e)
{
    // Find the node clicked.
    if (Nodes == null) return;
    if (e.Button == MouseButtons.Left)
        StartNode = FindNodeAt(e.Location);
    else if (e.Button == MouseButtons.Right)
        EndNode = FindNodeAt(e.Location);

    // See if we have both nodes.
    if ((StartNode != null) && (EndNode != null))
        StartSolving();

    picMaze.Refresh();
}

The StartNode and EndNode variables store the start and end nodes. When you click on the maze, the code uses the FindNodeAt method described next to find the nodes at the points where you clicked. If you click with the left mouse button, the program stores the node in the StartNode variable. Similarly if you right-click, the code saves the node in the EndNode variable.

After saving the clicked node, the program checks whether the start and end nodes are both defined. If both nodes are non-null, the code calls the StartSolving method to solve the maze.

FindNodeAt

Before I describe the StartSolving method, I want to show you the following FindNodeAt method.

// Return the node at a point.
private MazeNode FindNodeAt(Point location)
{
    if (location.X < Xmin) return null;
    if (location.Y < Ymin) return null;

    int row = (location.Y - Ymin) / CellHgt;
    if (row >= NumRows) return null;

    int col = (location.X - Xmin) / CellWid;
    if (col >= NumCols) return null;

    return Nodes[row, col];
}

The method first compares the point’s X and Y coordinates to the maze’s smallest X and Y coordinates. If the point lies to the left or above the maze, the method returns null.

Next the code subtracts the maze’s smallest Y coordinate from the point’s Y coordinate and divides by the height of a maze cell. All of these values are integers so the result is truncated to an integer that gives the point’s row number in the maze. If the row number is greater than the maze’s largest row, the method returns null.

The method then uses similar steps to find the point’s column number.

Finally, if both the row and column lie within the maze, the method returns the node at that position in the Nodes array.

StartSolving

The algorithm that you use to solve mazes is relatively simple. You start at the start node. If that node is not the end node, you examine each of its neighbors in turn and recursively find a path from the neighbor to the end node.

To keep track of the path that it is currently exploring, the program uses the following Path variable.

private List Path = null;

The following StartSolving method begins the search for a solution.

// Start solving the maze.
private void StartSolving()
{
    // Remove any previous results.
    Path = new List<MazeNode>();

    // Make the nodes define their neighbors.
    foreach (MazeNode node in Nodes)
        node.DefineNeighbors();

    // Start at the start node.
    Path.Add(StartNode);
    StartNode.InPath = true;

    // Solve recursively.
    Solve(EndNode, Path);

    // Clear the InPath values.
    foreach (MazeNode node in Path)
        node.InPath = false;

    // Show the result.
    picMaze.Refresh();
}

This method creates a new Path list. It then loops through all of the maze’s nodes calling their DefineNeighbors methods to use the maze’s spanning tree to define the nodes’ neighbors.

Next the code adds the start node to the Path list. It sets the start node’s InPath value to true so we know that the node is in the path.

The method then calls the Solve method to solve the maze.

After is has found the path, the method loops through the nodes in the Path list and resets their InPath fields to false so they are ready the next time you want to solve the maze. The method finishes by refreshing the picMaze PictureBox to show the result.

The following code shows the Solve method that the program uses to actually solve mazes.

private bool Solve(MazeNode end_node, List<MazeNode> path)
{
    // See if we have reached the end node.
    MazeNode last_node = path[path.Count - 1];
    if (last_node == end_node) return true;

    // Try each of the last node's children in turn.
    foreach (MazeNode neighbor in last_node.Neighbors)
    {
        if (!neighbor.InPath)
        {
            path.Add(neighbor);
            neighbor.InPath = true;
            if (Solve(end_node, path)) return true;
            neighbor.InPath = false;
            path.RemoveAt(path.Count - 1);
        }
    }

    return false;
}

This method takes as parameters the desired end node and a list holding the nodes that are in the current test path.

The method first checks the last node on the path list to see if it has reached the desired end node. If the path reaches the end node, the method returns true to indicate that it has found a solution.

Next the code loops through the neighbors of the path’s final node. If a neighbor’s InPath field indicates that it is not already in the path, the code adds it to the end of the path. It then calls the Solve method recursively to see if the new path leads to a solution.

If the recursive call to Solve returns true, that means the path does lead to a solution. In that case the method leaves the path list unchanged and returns true so the calling method higher up the call stack knows that we found a solution. The recursive calls unwind until they reach the top and control returns to the StartSolving method that started the search.

If the recursive call to Solve returns false, that means the current path does not lead to a solution. For example, suppose the path up to this point was A → B → … → M. As we loop through the Neighbors list, we tried adding node N to the path, but the recursive call to Solve did not find a solution. In that case we reset node N’s InPath value so we know that it is no longer in the path and remove it from the end of the path. We then continue the loop to try other node M neighbors.

If the method finishes trying all of node M’s neighbors and cannot find a solution, the code returns false to indicate that there is no solution using the path A → M.

InPath

There are two interesting questions you can ask about the InPath field.

The first question about InPath is, “Why do we need InPath?”

That value lets the program know if a node is already in the path. If it is, we don’t want to add it to the path again.

Because this program creates its maze by using a spanning tree (see the earlier example), there can be no paths that contain loops. That means we don’t need to worry about the end of the current path leading into the path’s middle. The only reason we need the InPath field is to prevent the path from doubling back on itself. For example, when we extend the path A → B → C, one of node C’s neighbors that we will consider is node B. The InPath value prevents us from trying to add node B to the path again.

We could remove the InPath field if we keep track of the node that we just came from so we can avoid doubling back. You can make that change if you like. I think the version used here is a little (but only a little) less confusing. It’s also in keeping with other network search algorithms where loops might be an issue.

The second question about InPath is, “Do we need to reset InPath to false when we remove a node from the path?”

Because all of the allowed moves through the maze lie on a spanning tree, we do not need to reconsider a node after it has been removed from the test path. If a node could not lead to a solution the first time we visit it, then it won’t lead to a solution if we visit it again later. In fact, because of the maze’s structure, we will not visit that node again. We would only visit a node again if we approached it from a different direction, and that would mean there was a loop in the maze.

All of that means we don’t really need to reset the InPath values to false. I do it anyway for two reasons. First, that prepares the nodes in case you left- or right-click again to find a new path through the maze. In that case we need the InPath values to be false so we can begin the new search.

The second reason why I reset the InPath values is because it seems less sloppy and it fits with other path algorithms.

Drawing the Solution

When you click the Create button, the program creates the maze. It then draws it on a bitmap and displays that bitmap on the picMaze PictureBox. Once the PictureBox has a background image, it can display that whenever it needs to redraw.

When you left- and right-click on the maze to define the start and end points, the program saves those points and refreshes the PictureBox to execute the following Paint event handler.

private void picMaze_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    if (StartNode != null) StartNode.DrawCenter(e.Graphics, Brushes.Red);
    if (EndNode != null) EndNode.DrawCenter(e.Graphics, Brushes.Green);
    if ((Path != null) && (Path.Count > 1))
    {
        List<PointF> points = new List<PointF>();
        foreach (MazeNode node in Path)
            points.Add(node.Center);
        e.Graphics.DrawLines(Pens.Red, points.ToArray());
    }
}

When the PictureBox needs to redraw itself, it starts with its background image, which shows the maze. The Paint event handler then draws on that image. Because the result starts with the background image, it is important that the Paint event handler not clear its Graphics object. If it calls e.Graphics.Clear, then the method will erase the maze.

The event handler sets the Graphics object’s SmoothingMode and then draws the start and ends nodes if they are defined.

Then if the Path list holds a path, the code creates a new list of points. It loops through the nodes on the path and adds their centers to that list. The code then draws lines connecting the points in the list.

Conclusion

This method for solving mazes is fast and relatively straightforward. It starts at the start point. At each point it recursively tries moving to the neighbors at the end of the current path to see if they can produce a solution.

You can use this algorithm to solve mazes. It is also similar to some of the algorithms that you use to find paths and shortest paths through a network.

Download the example to see additional details and to experiment with it. You might also think about how you could make the program display the test paths as it considers them. I’ll post an example that does that when I have a chance. (And get an example working. 😉 )


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics | Tagged , , , , , , , , , , , , | 1 Comment

Make a simple program to analyze COVID-19 symptoms in C#

[COVID-19 symptoms]

This example shows how to build an extremely simple program to analyze COVID-19 symptoms. It should not be taken as actual medical advice. It’s presented here as an example of a very simple artificial intelligence application.


Background

This type of artificial intelligence application is a table-driven expert system. It uses a set of simple lookup tables to emulate the skills of an expert, in this case a doctor. In a real expert system, you would do a ton of research, testing, and adjusting to get the values in the tables just right. For this example I just guessed. They will work for extremely obvious cases. For example, if you have a runny nose, sneezing, and no other symptoms, then you are more likely to have allergies than COVID-19.

In any case, don’t use this program for important diagnoses. If you have serious concerns, consult a doctor. (If you can find one who has time for you.)

I picked the numbers in the tables based on values shown in the following chart included in the Business Insider article How coronavirus symptoms compare with those of the flu, allergies, and the common cold.


[COVID-19 symptoms]

Table Values

The idea behind the program is simple: the program gives you points for each of the COVID-19 symptoms that you have. The program assigns the following points for each of the symptoms shown in the previous chart.

Frequency Points
Common 20
Sometimes 5
Mild 3
Rare 1
No 0

Again, let me emphasize that I just picked those numbers arbitrarily. I don’t have enough data to adjust them properly.

The Program

The program uses the following constants and table values.

// Point values.
private const int Common = 20;
private const int Sometimes = 5;
private const int Mild = 3;
private const int Rare = 1;
private const int No = 0;

// Values for each diagnosis.
private int[,] Values =
{
    { Common, Common, Common, Sometimes, Sometimes, Sometimes, Sometimes, Rare, Rare, No },
    { Rare, Mild, No, Rare, Common, Common, Sometimes, No, Common, Common },
    { Common, Common, No, Common, Common, Common, Common, Sometimes, No, No },
    { Sometimes, Sometimes, Common, Sometimes, No, No, Sometimes, No, Common, Common },
};
private const int DiarrheaSymptom = 7;

// Indices in the array.
private int Corona = 0;
private int CommonCold = 1;
private int Flu = 2;
private int Allergies = 3;

The first set of constants defines the point values for the symptoms. The Values array holds the values for the symptoms for the different diagnoses. For example, the first row holds the values for the COVID-19 symptoms.

The last set of constants defines the row numbers for the diagnoses (although the program only uses the Flu constant.)

When the program starts, it executes the following Form_Load event handler.

// An array of CheckBoxes so we can loop over them.
private CheckBox[] CheckBoxes;

private void Form1_Load(object sender, EventArgs e)
{
    // Load the CheckBoxes array.
    CheckBoxes = new CheckBox[]
    {
        chkFever,
        chkDryCough,
        chkShortnessOfBreath,
        chkHeadaches,
        chkAchesAndPains,
        chkSoreThroat,
        chkFatigue,
        chkDiarrhea,
        chkRunnyNose,
        chkSneezing,
    };

    Analyze();
}

The CheckBoxes array holds references to the form’s CheckBox controls. The Form_Load event handler initializes the array and then calls the Analyze method.

All of the program’s CheckBox controls execute the following code when you check or uncheck them.

private void chkSymptom_CheckedChanged(object sender, EventArgs e)
{
    Analyze();
}

This code simply calls the Analyze method shown in the following code.

private void Analyze()
{
    int num_diagnoses = Values.GetUpperBound(0) + 1;
    int num_symptoms = Values.GetUpperBound(1) + 1;
    int[] totals = new int[num_diagnoses];
    for (int diagnosis = 0; diagnosis < num_diagnoses; diagnosis++)
    {
        for (int symptom = 0; symptom < num_symptoms; symptom++)
        {
            if (CheckBoxes[symptom].Checked)
                totals[diagnosis] += Values[diagnosis, symptom];
        }
    }

    // If an adult, remove diarrhea from flu.
    if (chkAdult.Checked && chkDiarrhea.Checked)
    {
        totals[Flu] -= Values[Flu, DiarrheaSymptom];
    }

    // Display results.
    Label[] labels = { lblCoronaVirus, lblCold, lblFlu, lblAllergies };
    for (int diagnosis = 0; diagnosis < num_diagnoses; diagnosis++)
    {
        labels[diagnosis].Width = totals[diagnosis];
        labels[diagnosis].Text = totals[diagnosis].ToString();
    }
}

This method first checks the Values array’s bounds to get the number of diagnoses and the number of symptoms. It then creates a totals array to hold the point totals for each of the diagnoses.

Next the code loops through the diagnoses. For each diagnosis, the code loops through the CheckBoxes array to see which are boxes checked. If a box is checked, then the code adds the point value for that diagnosis and symptom to the current diagnosis.

After it finishes calculating the basic point totals, the code makes one adjustment. The diarrhea symptom only applies to children. If the Adult and Diarrhea boxes are both checked, then code subtracts the Flu/Diarrhea value from the flu diagnosis total.

The code finishes by displaying the results. It creates an array holding references to the green result labels. It then loops through the diagnoses. For each diagnosis, the code sets the corresponding label’s width and text to indicate the totals.

Conclusion

Note that the numbers are point totals not percentages. If Allergies scores a 65 and COVID-19 scores 26, that doesn’t mean there is a 65% chance that you have allergies. It just means that you are more likely to have allergies than COVID-19.

There are other symptoms that you might want to add. For example, if you know that you have seasonal allergies and it is allergy season, then that should probably increase the Allergies/Runny Nose points.

Note that some people have very mild COVID-19 symptoms and don’t show symptoms initially. Also note that this program does not cover other possible causes for symptoms such as food poisoning (diarrhea), a strenuous workout (aches and pains), or lack of sleep caused by worrying about COVID-19 (fatigue). Duh.

But if you just have a runny nose and wonder if you have COVID-19, you probably shouldn’t rush to the emergency room.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms | Tagged , , , , , , | 4 Comments