Posts

    • Code Complete
    • Data Structures and Problem Solving using Java
    • Precalculus
    • Design Patterns Explained
    • Code Complete
    • Precalculus
    • Data Structures and Problem Solving using Java
    • Precalculus
    • C# Network Programming
    • Precalculus
    • Code Complete
    • Precalculus
    • C# Network Programming
    • Precalculus
    • Code Complete
    • Precalculus
    • The Complete Reference C++
    • Precalculus
    • Linux Game Programming
    • Applied Cryptography
    • PostgreSQL
    • Practical Unix and Internet Security
  • Optional
    • ImgBurn Support
    • Building Packages

  • This is my summary of the Graphs and Paths chapter. All code is written in c# (converted from java).

    Types of Graphs (E - Edges, V - Vertices) Unweighted: O(|E|) Breadth-first search Weighted, no negative edges: O(|E| log|V|) Dijkstra’s algorithm Weighted, negative edges: O(|E| * |V|) Bellman-Ford algorithm Weighted, acyclic: O(|E|) Uses topological sort

    A class to represent the Edges:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    public class Edge
    {
    	public Vertex dest; // Second vertex in Edge
    	public double cost; // Edge cost
    	
    	public Edge(Vertex d, double c)
    	{
    		this.dest = d;
    		this.cost = c;
    	}
    }
    

    A class to represent the Vertices:

    public class Vertex
    {
    	public string name; // Vertex name
    	public List<Edge> adj; // Adjacent vertices
    	public double dist; // cost
    	public Vertex prev; // Previous vertex on shortest path
    	public int pos; // Position on path?
    	public int scratch; // Extra variable used in algorithm
    	
    	public Vertex(string nm)
    	{
    		this.name = nm;
    		this.adj = new List<Edge>();
    		reset();
    	}
    	
    	public void reset()
    	{
    		this.dist = Graph.INFINITY;
    		this.prev = null;
    		this.pos = 0;
    		this.scratch = 0 ;
    	}
    }
    

    A class to represent paths (used with dijkstra’s algorithm):

    /// <summary>
    /// Used with the dijkstra function
    /// </summary>
    public class Path : IComparable<Path>
    {
    	private Vertex vdest;
    	private double vcost;
    	
    	public Path(Vertex d, double c)
    	{
    		vdest = d;
    		vcost = c;
    	}
    	
    	public Vertex Dest
    	{
    		get {return vdest;}
    		set {vdest = value;}
    	}
    	
    	public double Cost
    	{
    		get { return vcost; }
    		set { vcost = value; }
    	}
    	
    	public int CompareTo(Path rhs)
    	{
    		double otherCost = rhs.Cost;
    		if (vcost < otherCost)
    		{
    			return -1;
    		}
    		else if (vcost > otherCost)
    		{
    			return 1;
    		}
    		
    		return 0;
    				
    	}
    }
    

    The PriorityQueue class. This is needed with the dijkstra algorithm. I never wrote this, found on the Internet (not sure where).

    [Serializable] 
    public class PriorityQueue<T>
    {
        List<T> list = new List<T>();
        Comparison<T> comparer; 
    
        public PriorityQueue():this(Comparer<T>.Default.Compare)
        {           
        }
    
        public PriorityQueue(IComparer<T> comparer)
        {
            this.comparer = comparer.Compare;
        }
    
        public PriorityQueue(Comparison<T> comparer)
        {
             this.comparer = comparer;
        }
    
    
        public int Count
        {
            get
            {
                return list.Count;
            }
        }
    
        public bool Empty
        {
            get { return list.Count == 0; }
        }
    
    
        public int Push(T element)
        {
            int p = list.Count;
            list.Add(element); 
            do
            {
                if (p == 0)
                    break;
                int p2 = (p - 1) / 2;
                if (Compare(p,p2) < 0)
                {
                    SwitchElements(p, p2);
                    p = p2;
                }
                else
                    break;
            } while (true);
            return p;
        }
    
        public void PushAll(IEnumerable<T> elements)
        {
            foreach (var item in elements)
                Push(item);
        }
    
        public T Pop()
        {
            if (Empty)
                throw new InvalidOperationException("Empty Priority Qoeue");
    
            int p = 0;
            T result = list[0];
            list[0] = list[list.Count - 1];
            list.RemoveAt(list.Count - 1);
            do
            {
                int pn = p;
                int p1 = 2 * p + 1;
                int p2 = 2 * p + 2;
                if (list.Count > p1 && Compare(p,p1) > 0)
                    p = p1;
                if (list.Count > p2 && Compare(p,p2) > 0)
                    p = p2;
    
                if (p == pn)
                    break;
    
                SwitchElements(p, pn);
            } while (true);
    
            return result; 
        }
    
        public T Peek()
        {
            if (Empty)
                throw new InvalidOperationException("Invalied Priority Qoeue");
    
            return list[0];
        }
    
    
    
        public void Clear()
        {
            list.Clear();
        }
    
        public void Update(T element)
        {
            Update(list.IndexOf(element));
        }
    
        public bool Contains(T element)
        {
            return list.Contains(element);
        }
    
    
        int Compare(int i, int j)
        {
            return comparer(list[i], list[j]);
        }
    
        void SwitchElements(int i, int j)
        {
            T h = list[i];
            list[i] = list[j];
            list[j] = h;
        }
    
        void Update(int i)
        {
            int p = i, pn;
            int p1, p2;
            do
            {
                if (p == 0)
                    break;
                p2 = (p - 1) / 2;
                if (Compare(p, p2) < 0)
                {
                    SwitchElements(p, p2);
                    p = p2;
                }
                else
                    break;
            } while (true);
            if (p < i)
                return;
            do
            {
                pn = p;
                p1 = 2 * p + 1;
                p2 = 2 * p + 2;
                if (list.Count > p1 && Compare(p, p1) > 0)
                    p = p1;
                if (list.Count > p2 && Compare(p, p2) > 0)
                    p = p2;
    
                if (p == pn)
                    break;
                SwitchElements(p, pn);
            } while (true);
        }
    }
    

    The Graph and GraphException class:

    
    /// <summary>
    /// Graph class: evaluate shortest paths.
    /// 
    /// CONSTRUCTION: with no parameters.
    /// 
    /// ***********************PUBLIC OPERATIONS*************
    /// void AddEdge(string v, string w, double cvw) - Add additional edge
    /// void PrintPath(string w) - Print path after alg is run
    /// void Unweighted(string s) - Single-source unweighted
    /// void Dijkstra(string s) - Single-source weighted
    /// void Negative(string s) - Single-source negative
    /// void Acyclic(string s) - Single-source acyclic
    /// *********************ERRORS**************************
    /// Some error checking is performed to make sure that graph the graph is ok
    /// and that graph statisfies properties needed by each algorithm.
    /// Exceptions are thrown if errors are detected.
    /// </summary>
    public class Graph
    {
    	public static readonly double INFINITY = double.MaxValue;
    	
    	
    	/// <summary>
    	/// Add a new edge to the graph.
    	/// </summary>
    	/// <param name="sourceName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	/// <param name="destName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	/// <param name="cost">
    	/// A <see cref="System.Double"/>
    	/// </param>
    	public void AddEdge(string sourceName, string destName, double cost)
    	{
    		Vertex v = GetVertex(sourceName);
    		Vertex w = GetVertex(destName);
    		v.adj.Add(new Edge(w, cost));
    	}
    	
    	/// <summary>
    	/// Driver routine to handle unreachables and print total cost.
    	/// It calls recursive routine to print shortest path to destNode
    	/// after a shortest path algorithm has run.
    	/// </summary>
    	/// <param name="destName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	public void PrintPath(string destName)
    	{
    		Vertex w = (Vertex)vertexMap[destName];
    		if(w == null)
    		{
    			throw new KeyNotFoundException();
    		}
    		else if(w.dist == INFINITY)
    		{
    			System.Console.WriteLine(destName + " is unreachable");
    		}
    		else
    		{
    			System.Console.Write("(Cost is: " + w.dist + ")");
    			PrintPath(w);
    			System.Console.WriteLine("");
    		}
    	}
    	
    	/// <summary>
    	/// Unweighted single-source, shortest-path problem.
    	/// 
    	/// Find the shortest path (measured by number of edges) from a designated
    	/// vertex S to every vertex.
    	/// </summary>
    	/// <param name="startName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	public void Unweighted(string startName)
    	{
    		ClearAll();
    		
    		Vertex start = (Vertex)vertexMap[startName];
    		if(start == null)
    		{
    			throw new NullReferenceException("Start vertex not found");
    		}
    		
    		Queue<Vertex> q = new Queue<Vertex>();
    		q.Enqueue(start); start.dist=0;
    		
    		while(q.Count>0)
    		{
    			Vertex v = q.Dequeue();
    			foreach(Edge e in v.adj)
    			{
    				Vertex w = e.dest;
    				
    				if(w.dist == INFINITY)
    				{
    					w.dist = v.dist + 1;
    					w.prev = v;
    					q.Enqueue(w);
    				}
    			}
    		}
    	}
    	
    	/// <summary>
    	/// Positive-weighted, single-source, shortest-path problem
    	/// 
    	/// Find the shortest path (measured by total cost) from a designated vertex S
    	/// to every vertex.  all edge costs are nonnegative.
    	/// </summary>
    	/// <param name="startName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	/// <remarks>Will not work with negative edges</remarks>
    	public void Dijkstra(string startName)
    	{
    		PriorityQueue<Path> pq = new PriorityQueue<Path>();
    		
    		Vertex start = (Vertex)vertexMap[startName];
    		if(start == null)
    		{
    			throw new NullReferenceException("Start vertex not found");
    		}
    		
    		ClearAll();
    		pq.Push(new Path(start, 0)); // add
    		start.dist = 0;
    		
    		int nodesSeen = 0;
    		
    		while(pq.Count > 0 && nodesSeen < vertexMap.Count)
    		{
    			Path vrec = pq.Pop(); //remove
    			Vertex v = vrec.Dest;
    			if(v.scratch != 0) // already processed v
    			{
    				continue;
    			}
    			v.scratch = 1;
    			nodesSeen++;
    			
    			foreach(Edge e in v.adj)
    			{
    				Vertex w = e.dest;
    				double cvw = e.cost;
    				
    				if(cvw < 0)
    				{
    					throw new GraphException("Graph has negative edges");
    				}
    				
    				if(w.dist > (v.dist + cvw))
    				{
    					w.dist = v.dist + cvw;
    					w.prev = v;
    					pq.Push(new Path(w, w.dist));
    				}
    			}
    		}
    	}
    	
    	/// <summary>
    	/// Most General Case.
    	/// 
    	/// Negative-Weighted, single-source, shortest-path problem.
    	/// Find the shortest path (measured by total cost) from a designated vertex S to
    	/// every vertex.  Edge costs may be negative.
    	/// </summary>
    	/// <param name="startName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	public void Negative(string startName)
    	{
    		ClearAll();
    		
    		Vertex start = (Vertex)vertexMap[startName];
    		if(start == null)
    		{
    			throw new NullReferenceException("Start vertex not found");
    		}
    		
    		Queue<Vertex> q = new Queue<Vertex>();
    		q.Enqueue(start);
    		start.dist = 0;
    		start.scratch++;
    		
    		while (q.Count > 0)
    		{
    			Vertex v = q.Dequeue();
    			if(v.scratch++ > (2*vertexMap.Count))
    			{
    				throw new GraphException("Negative cycle detected");
    			}
    			
    			foreach(Edge e in v.adj)
    			{
    				Vertex w = e.dest;
    				double cvw = e.cost;
    				
    				if(w.dist > (v.dist + cvw))
    				{
    					w.dist = v.dist + cvw;
    					w.prev = v;
    					
    					//Enqueue only if not already on the queue
    					if((w.scratch++ % 2) == 0)
    					{
    						q.Enqueue(w);
    					}
    					else
    					{
    						w.scratch--; // undo the enqueue increment	
    					}
    				}
    			}
    		}
    	}
    	
    	/// <summary>
    	/// Weighted single-source, shortest-path problem for acyclic graphs.
    	/// Find the shortest path (measured by total cost) from a designated
    	/// vertex S to every vertex in an acyclic graph.  Edge costs are unrestricted.
    	/// </summary>
    	/// <param name="startName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	public void Acyclic(string startName)
    	{
    		Vertex start = (Vertex)vertexMap[startName];
    		if (start == null)
    		{
    			throw new NullReferenceException("Start vertex not found");
    		}
    		
    		ClearAll();
    		
    		Queue<Vertex> q = new Queue<Vertex>();
    		start.dist = 0;
    		
    		// Compute the indegrees
    		List<Vertex> vertexSet = new List<Vertex>();
    		foreach(Vertex v in q)
    		{
    			vertexSet.Add(v);
    		}
    		
    		foreach(Vertex v in vertexSet)
    		{
    			foreach(Edge e in v.adj)
    			{
    				e.dest.scratch++;
    			}
    		}
    		
    		foreach(Vertex v in vertexSet)
    		{
    			if(v.scratch == 0)
    			{
    				q.Enqueue(v);
    			}
    		}
    		
    		int iterations;
    		for(iterations = 0; q.Count > 0; iterations++)
    		{
    			Vertex v = q.Dequeue();
    			
    			foreach(Edge e in v.adj)
    			{
    				Vertex w = e.dest;
    				double cvw = e.cost;
    				
    				if(--w.scratch == 0)
    				{
    					q.Enqueue(w);
    				}
    				
    				if(v.dist == INFINITY)
    				{
    					continue;
    				}
    				
    				if(w.dist > (v.dist + cvw))
    				{
    					w.dist = v.dist + cvw;
    					w.prev = v;
    				}
    			}
    		}
    		
    		if(iterations != vertexMap.Count)
    		{
    			throw new GraphException("Graph has a cycle!");
    		}
    	}
    	
    	/// <summary>
    	/// If vertexName is not present, add it to vertexMap.
    	/// In either case, return the Vertex
    	/// </summary>
    	/// <param name="vertexName">
    	/// A <see cref="System.String"/>
    	/// </param>
    	/// <returns>
    	/// A <see cref="Vertex"/>
    	/// </returns>
    	private Vertex GetVertex(string vertexName)
    	{
    		Vertex v = (Vertex)vertexMap[vertexName];
    		if(v == null)
    		{
    			v = new Vertex(vertexName);
    			vertexMap.Add(vertexName, v);
    		}
    		return v;
    	}
    	
    	/// <summary>
    	/// Recursive routine to print shortest path to dest after running
    	/// shortest path algorithm.  The path is known to exist.
    	/// </summary>
    	/// <param name="dest">
    	/// A <see cref="Vertex"/>
    	/// </param>
    	private void PrintPath(Vertex dest)
    	{
    		if(dest.prev != null)
    		{
    			PrintPath(dest.prev);
    			System.Console.Write(" to ");
    		}
    		System.Console.Write(dest.name);
    	}
    	
    	/// <summary>
    	/// Initializes the vertex output info prior to running
    	/// any shortest path algorithm.
    	/// </summary>
    	private void ClearAll()
    	{
    		foreach(Vertex v in vertexMap.Values)
    		{
    			v.reset();
    		}
    	}
    	
    	/// <summary>
    	/// <string, Vertex>
    	/// </summary>
    	public System.Collections.Hashtable vertexMap = new System.Collections.Hashtable();
    	
    }
    
    public class GraphException : Exception
    {
    	public GraphException(string name) : base(name)
    	{}
    }
    

    The main command line program:

    class MainClass
    {
    	/// <summary>
    	/// A main routine that
    	/// 1. Reads a file (supplied as a command-line parameter) containing edges.
    	/// 2. Forms the graph.
    	/// 3. Repeatedly prompts for two vertices and runs the shortest path algorithm.
    	/// 
    	/// The data file is a sequence of lines of the format source destination.
    	/// </summary>
    	public static void Main(string []args)
    	{
    		Graph g = new Graph();
    		StreamReader fin = new StreamReader("input.txt");//StreamReader(args[0]);
    		
    		
    		try
    		{
    			string line;
    			while( (line = fin.ReadLine()) != null )
    			{
    				string[] words = line.Split(char.Parse(" "));
    				try
    				{
    					if (words.Length != 3)
    					{
    						System.Console.WriteLine("Skipping bad line " + line);
    						continue;
    					}
    					
    					string source = words[0];
    					string dest = words[1];
    					int cost = int.Parse(words[2]);
    					g.AddEdge(source, dest, cost);
    				}
    				catch(Exception exi)
    				{
    						System.Console.WriteLine("Skipping bad line " + line);
    				}
    			}
    		}
    		catch(IOException ex)
    		{
    			System.Console.WriteLine(ex.StackTrace);
    		}
    		
    		System.Console.WriteLine("File read...");
    		System.Console.WriteLine(g.vertexMap.Count);
    		
    		while(ProcessRequest(g)){}
    	}
    				
    	/// <summary>
    	/// Process a request; return if end of file.
    	/// </summary>
    	/// <param name="fin">
    	/// A <see cref="StreamReader"/>
    	/// </param>
    	/// <param name="g">
    	/// A <see cref="Graph"/>
    	/// </param>
    	/// <returns>
    	/// A <see cref="System.Boolean"/>
    	/// </returns>
    	public static Boolean ProcessRequest(Graph g)
    	{
    		string startName = null;
    		string destName = null;
    		string alg = null;
    		
    		try
    		{
    			Console.Write("Enter start node: ");
    			if( (startName = Console.ReadLine()) == null )
    			{
    				return false;
    			}
    			Console.Write("Enter destination node: ");
    			if( (destName = Console.ReadLine()) == null )
    			{
    				return false;
    			}
    			Console.Write("Enter algorithm (u, d, n, a): ");
    			if( (alg = Console.ReadLine()) == null )
    			{
    				return false;
    			}
    			
    			if(alg == "u")
    			{
    				g.Unweighted(startName);
    			}
    			else if(alg == "d")
    			{
    				g.Dijkstra(startName);
    			}
    			else if(alg == "n")
    			{
    				g.Negative(startName);
    			}
    			else
    			{
    				g.Acyclic(startName);
    			}
    			
    			g.PrintPath(destName);
    		}
    		catch(IOException ioex)
    		{
    			Console.WriteLine(ioex.Message);
    		}
    		catch(Exception ex)
    		{
    			Console.WriteLine(ex.Message);
    		}
    		
    		return true;
    	}
    }
    

    The input graph (input.txt):

    D C 10
    A B 12
    D B 23
    A D 87
    E D 43
    B E 11
    C A 19
    

    As I no longer have the time or the money to go back to university to finish my degree I have decided that I will do my own self paced study. I am going to learn as much as I can on my own. I already have some books from when I was in university and I am going to finish them. Then I am going to get more an finish them.

    The main purpose of this page is to track my progress as I move through study material. As I progress I will be adding more study material.

    Code Complete

    • Chapter 1 Welcome to Software Construction
    • Chapter 2 Metaphors for a Richer Understanding of Software Development
    • Chapter 3 Measure Twice, Cut Once: Upstream Prerequisites
    • Chapter 4 Key Construction Decisions


    Data Structures and Problem Solving using Java

    • Chapter 5 Big O Already done but I am going to review it
    • Chapter 7 Recursion Already done but I am going to review it
    • Chapter 8 Sorting Already done but I am going to review it
    • Chapter 14 Graphs and Paths
    • Chapter 17 Linked Lists - Singly/Sorted Linked Lists, Doubly Linked Lists
    • Chapter 18 Trees
    • Chapter 19 Binary Tree Search


    Precalculus

    • Chapter 1 - The Basics


    Design Patterns Explained


    Code Complete

    • Chapter 5 Design in Construction
    • Chapter 6 Working Classes
    • Chapter 7 High-Quality Routines
    • Chapter 8 Defensive Programming
    • Chapter 9 The Pseudo-code Programming Process


    Precalculus

    • Chapter 2 - Functions


    Data Structures and Problem Solving using Java

    • Chapter 20 Hash Tables
    • Chapter 21 Priority Queue
    • Chapter 11 Stacks and Compilers


    Precalculus

    • Chapter 1 - Graphs of Functions


    C# Network Programming

    • Chapter 2 IP Programming Basics
    • Chapter 3 C# Network Programming Classes
    • Chapter 4 DNS and C#
    • Chapter 5 Connection-Oriented Sockes


    Precalculus

    • Chapter 4 - Exponential and Logarithmic Functions


    Code Complete

    • Chapter 10 General Issues in Using Variables
    • Chapter 11 The power of Variables in Names
    • Chapter 12 Fundamental Data Types
    • Chapter 13 Unusual Data Types


    Precalculus

    • Chapter 5 - Trigonometry


    C# Network Programming

    • Chapter 6 Connectionless Sockets
    • Chapter 7 Using the C# helper Classes
    • Chapter 8 Asynchronous Sockets
    • Chapter 9 Threads
    • Chapter 10 Multicasting


    Precalculus

    • Chapter 6 - Analytic Trigonometry


    Code Complete

    • Chapter 14 Organizing Straight-Line Code
    • Chapter 15 Using Conditionals
    • Chapter 16 Controlling Loops
    • Chapter 17 Unusual Control Structures
    • Chapter 18 Table-Driven Methods
    • Chapter 19 General Control Issues


    Precalculus

    • Chapter 7 - Additional Topics in Trigonometry


    The Complete Reference C++

    • Review chapters 1 - 10 before moving on to Linux Game Programming


    Precalculus

    • Chapter 8 - Miscellaneous Topics


    Linux Game Programming

    • Chapter 4 Mastering SDL


    Applied Cryptography

    • Chapter 1 - Foundations
    • Chapter 2 - Protocol Building Blocks
    • Chapter 3 - Basic Protocols
    • Chapter 4 - Intermediate Protocols
    • Chapter 5 - Advanced Protocols
    • Chapter 6 - Esoteric Protocols


    PostgreSQL

    • Chapter 9 - Transactions and Locking
    • Chapter 10 - Functions, Stored Procedures, and Triggers
    • Chapter 12 - Database Design


    Practical Unix and Internet Security

    • Chapter 1 - Introduction: Some Fundamental Questions
    • Chapter 2: Unix History and Lineage
    • Chapter 3 - Policies and Guidelines
    • Chapter 4 - Users, Passwords, and Authentication


    The Mythical Man Month
    Computer Networks (Andrew S. Tanenbaum)
    Operating Systems Design and Implementation (Andrew S. Tanenbaum)
    http://portal.acm.org/toc.cfm?id=SERIES11430&type=series&coll=ACM&dl=ACM


    This is a checklist of items and actions that must be performed to build/develop DeVeDe on Windows.

    First there are several packages that must be installed. All packages are for x86 and not 64bit and all python packages are for version 2.6.*.

    • Install Python 2.7 (make sure it is 2.7 not 3.*) - http://python.org/download/
    • Install Python for Win32 Extensions - http://sourceforge.net/projects/pywin32/ (direct http://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/pywin32-214.win32-py2.7.exe/download).
    • Install PyGTK 2.22 all in one installer - http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.22/pygtk-all-in-one-2.22.5.win32-py2.7.msi
    • Check out DeVeDe master from github https://github.com/majorsilence/Devede (new url) - to setup git on windows see http://help.github.com/win-set-up-git/
    • From an install of devede (http://files.majorsilence.com/devede/downloads/316-9/devede-setup-3.16.9-build7.msi) copy the bin folder into the "src" folder. This provides the executables that DeVeDe requires to do its work.
    • Double click devede.py to start devede.
    • </ul>

      Optional

      ImgBurn Support

      • Install ImgBurn - http://www.imgburn.com/
      • ImgBurn is used when it is detected to create the ISO files. Otherwise mkisofs.exe is used and it has major problems on Vista and Windows 7.
      • ImgBurn must be run once after install to add its location to the registry path.
      • </ul>

        Building Packages

        If you plan on building windows executables for distribution you will also want to follow these steps.
        • Install py2exe - http://sourceforge.net/projects/py2exe/files/ - direct link (http://sourceforge.net/projects/py2exe/files/py2exe/0.6.9/py2exe-0.6.9.win32-py2.7.exe/download)
        • Install wix - http://sourceforge.net/projects/wix/files/. As of this posting this is not currently integrated in devede win32 build but it will be soon.
        • From the GTK install directory copy the "etc", "lib", and "share" folders to the devede trunk folders. These are used when building devede.exe
        • Create zipped source package (trunk-src.zip), devede.exe (trunk\dist\devede.exe, you need the entire "dist" folder), and msi installers by running devede_build.py (You may have to edit this file to point to the correct location of python).
        • </ul> You may also want to download the GTK+ Preference Tool. You should be able to find it at http://sourceforge.net/projects/gtk-win/files/. This tool will allow you to set the GTK theme on your Windows user account. At this point DeVeDe should be running in a development environment on your computer.

    Results of reading Design Patterns Explained. My definitions are very loose. For better understanding read the book or visit Wikipedia.

    Facade pattern - Basically Create a wrapper around methods/classes to to ease use (simplified interface).

    Adapter pattern - Basically Create a wrapper for a class to meet a defined interface. Similar to a Facade.

    Bridge pattern - The abstraction/interface is separate from the implementation. Create the interface. Then program the implementation to the interface. The interface is never a concrete implementation itself.

    Abstract Factory pattern Create an abstract class. Create concrete classes from abstract class. Have another class that. Abstract/Interface A Implementation B Implementation C

    Class D returns

    Class E calls D which returns either B or C. Since both implement A they both have the same methods and properties and can be used interchangeably. At least this is what I gathered from the chapter.

    • First, identify the rules for instantiation and define an abstract class with an interface that has a method for each object that needs to be instantiated. • Then, implement concrete classes from this class for each family. • The client object uses this factory object to create the server objects that it needs.

    Strategy Pattern - Encapsulating an algorithm(s) in an abstract class and using one of them at a time inter-changeably. GOF: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.

    Decorator Pattern - Attach additional responsibilities to a object dynamically.

    Singleton pattern - used in single threaded applications. Purpose is to make sure only one instance of a class is instantiated.

    Double-Checked Locking Pattern - like singleton but used in multi threaded application. Purpose is to make sure only one instance of a class is instantiated.

    Eg. Constructor is private so the only instance can be created with the Instance Property

      public sealed class Login
      {
             private static volatile Login instance;
             private static object syncRoot = new Object();
    
             private Login() { }
    
             public static Login Instance
             {
                get 
                {
                   if (instance == null) 
                   {
                      lock (syncRoot) 
                      {
                          if (instance == null)
                          {
                              instance = new Login();
                          }
                      }
                   }
    
                   return instance;
                }
             }
      }
    

    Observer pattern An object called the subject maintains a list of its dependants, called observers, and notifies them automatically of any state changes, by calling one of their methods.

    Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

    The Template Method Pattern Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Redefine the steps in an algorithm without changing the algorithm’s structure.

    Factory Pattern Method “When there is a method involved in making an object, the approach is called a Factory Method.”

    According to the Gang of Four, the intent of the Factory Method is to: Define an interface for creating an object, but let sub-classes decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.


    This is a simple method of history tracking of database changes that in done within in the application. There are other methods to do this including creating triggers within the database itself.

    Basically this is one small function that you pass in a dataset and transaction. It loops through each table and each row and column in the table and detects the current state of the row and records it in an audit table.

    First lets take a look at the audit table. The audit table tracks the tablename, field that was changed, the original and new value of the changed column, the action taken (insert, modified, delete), the user that did the action and the date. The tablename and code (primary key) field can be used to search the history of a row. Also included is orig_binary and new_binary for storing values for binary columns instead of original and new.

    SQL (SQLite)

    CREATE TABLE [audit] (
    [id] INTEGER  PRIMARY KEY,
    [tablename] VARCHAR(50)  NOT NULL,
    [field] NVARCHAR(50)  NOT NULL,
    [original] NVARCHAR(4000)  NOT NULL,
    [new] NVARCHAR(4000)  NOT NULL,
    [action] NVARCHAR(10)  NOT NULL,
    [user] NVARCHAR(50)  NOT NULL,
    [date] DATE  NOT NULL,
    [code] INTEGER  NOT NULL,
    [orig_binary] BLOB  NULL,
    [new_binary] BLOB  NULL
    );
    
    
    CREATE TABLE [actor] (
    [id] INTEGER  PRIMARY KEY,
    [first_name] VARCHAR(50)  NOT NULL,
    [last_name] NVARCHAR(50)  NOT NULL,
    [date_of_birth] NVARCHAR(25)  NOT NULL
    );
    
    

    So it should be obvious that this approach is to use one table for tracking all changes in every table. Another option would be to create an audit table for each table and every time a row is changed copy it to the audit table first. You would then have to scan the audit table and check each column to see what the change that was made.

    I would like to point out that I do not particularly like the code shown below. I would prefer to use a RowUpdated event handler but since I am SQLiteDataAdapters with sql text instead of stored procedures with return row I am settling for this. In another post I will show using the updated event with Microsoft SQL server and Stored procedures.

    Here is the code that set ups the transactions and calls the audit function. The audit function must be called before the DataAdapter update. This is all done within one transaction so that nothing is recorded in the audit table unless records are saved in the main table.

    You should notice that the DoAudit function takes as parameters a DataSet that is to be tracked in the audit, a code (if empty it will use the primary key column as the code) and a SQLiteTransaction.

    Please excuse the incompleteness of the class Program as I am in the middle of rewritting this article.

    class Program
    {
        private static SQLiteDataAdapter daActor;
        private static DataSet dsActor;
        private static Audit auditTracking;
    
        static void Main(string[] args)
        {
            auditTracking = new Audit();
            dsActor = new DataSet();
    
            SQLiteConnection cn = HelperFunctions.CreateConnection();
            daActor = new SQLiteDataAdapter("SELECT * FROM actor;", cn);
            if (System.IO.File.Exists("hello.db"))
            {
                daActor.Fill(dsActor);
            }
    
            bool exit = false;
            Console.WriteLine("h - for help");
            while (exit == false)
            {
                Console.Write("Command: ");
                string input = Console.ReadLine();
                switch (input)
                {
                    case "q":
                        exit = true;
                        break;
                    case "0":
                        HelperFunctions.CreateDatabase();
                        daActor.Fill(dsActor);
                        break;
                    case "n":
                        NewActor();
                        break;
                    case "p":
                        PrintAllActors();
                        break;
                    case "pa":
                        PrintAuditTable();
                        break;
                    case "h":
                        Console.WriteLine("q - quite program");
                        Console.WriteLine("0 - Create Database");
                        Console.WriteLine("n - Add new actor");
                        Console.WriteLine("p - Print all actors");
                        Console.WriteLine("pa - Print audit table");
                        Console.WriteLine("h - Print Help");
                        Console.WriteLine("");
                        break;
                }
            }
    
        }
    
        private static void PrintAuditTable()
        {
            DataTable dt = new DataTable();
            SQLiteConnection cn = HelperFunctions.CreateConnection();
            SQLiteCommand cmd = new SQLiteCommand("SELECT * FROM audit;", cn);
            cn.Open();
            SQLiteDataReader reader = cmd.ExecuteReader();
            dt.Load(reader);
            reader.Close();
    
            foreach (DataRow row in dt.Rows)
            {
                Console.WriteLine("ID: " + row["id"]);
                Console.WriteLine("tablename: " + row["tablename"]);
                Console.WriteLine("field: " + row["field"]);
                Console.WriteLine("original: " + row["original"]);
                Console.WriteLine("new: " + row["new"]);
                Console.WriteLine("action: " + row["action"]);
                Console.WriteLine("user: " + row["user"]);
                Console.WriteLine("date: " + row["date"]);
                Console.WriteLine("code: " + row["code"]);
                Console.WriteLine("orig_binary: " + row["orig_binary"]);
                Console.WriteLine("new_binary: " + row["new_binary"]);
                Console.WriteLine("");
            }
        }
    
        private static void PrintAllActors()
        {
            foreach (DataRow row in dsActor.Tables[0].Rows)
            {
                Console.WriteLine("Actor: " + row["first_name"] + " " + row["last_name"]);
                Console.WriteLine("DOB: " + row["date_of_birth"]);
                Console.WriteLine("");
            }
        }
        private static void NewActor()
        {
            DataRow row = dsActor.Tables[0].NewRow();
    
            row["id"] = DBNull.Value;
    
            Console.Write("First Name: ");
            row["first_name"] = Console.ReadLine();
    
            Console.Write("Last Name: ");
            row["last_name"] = Console.ReadLine();
    
            Console.Write("Date of Birth: ");
            row["date_of_birth"] = Console.ReadLine();
    
            dsActor.Tables[0].Rows.Add(row);
    
            UpdateDatabase();
        }
    
        private static void UpdateDatabase()
        {
            SQLiteConnection cn = HelperFunctions.CreateConnection();
    
            cn.Open();
            daActor.SelectCommand.Connection = cn;
            SQLiteTransaction txn = cn.BeginTransaction();
            try
            {
                SQLiteCommandBuilder cmd = new SQLiteCommandBuilder(daActor);
                daActor.InsertCommand = cmd.GetInsertCommand();
                daActor.UpdateCommand = cmd.GetUpdateCommand();
                daActor.DeleteCommand = cmd.GetDeleteCommand();
    
                daActor.InsertCommand.Transaction = txn;
                daActor.UpdateCommand.Transaction = txn;
                daActor.DeleteCommand.Transaction = txn;
    
                // call the audit function.  If the daActor.Update command succeeds then
                // there will be an audit trail.  If it fails the audit will be rolled back.
                auditTracking.DoAudit(dsActor, "", txn);
                daActor.Update(dsActor);
                txn.Commit();
            }
            catch (Exception ex)
            {
                // rollback action and audit trail.
                txn.Rollback();
                TrapErrors(ex, true);
            }
            finally
            {
                cn.Close();
            }
        }
    
        public static void TrapErrors(Exception ex, bool showMessage)
        {
            if (showMessage)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
    

    The Main function runs the code that lets the user enter new actors and then calls the UpdateDatabase function to update the actor and audit table.

    Here is the audit class that does the actual work.

    The DoAudit function is passed a DataSet and a Transaction. It will loop through each row in each table that is in the DataSet. If there are any changes to the values such as an Insert, Update, or Delete it will record this change in the audit table. It will attempt to identify the primary key and use that as the code column in the audit table.

    public class Audit
    {
        private SQLiteDataAdapter daAudit;
    
    
        /// <summary>
        /// Check the specfied table in the dataset and record them in the audit table.
        /// Currently is only an example and does not work
        /// </summary>
        /// <param name="ds">DataSet</param>
        /// <param name="code">string - generally the primary key of the table</param>
        /// <param name="txn">IDbTransaction</param>
        /// <remarks>Requires a table with columns: tablename, action, user, date, new, original, field, code.
        /// The "code" field is the one that is to be searched.</remarks>
        public void DoAudit(DataSet ds, string code, SQLiteTransaction txn)
        {
            if (ds.Tables.Count <= 0)
            {
                return;
            }
    
            DataSet dsAudit = new DataSet();
            DataRow row_Audit;
            daAudit = new SQLiteDataAdapter("Select * from audit WHERE 1=2;", txn.Connection);
            daAudit.Fill(dsAudit);
    
            dsAudit.Tables[0].Columns["id"].AllowDBNull = true;
            dsAudit.Tables[0].Columns["orig_binary"].AllowDBNull = true;
            dsAudit.Tables[0].Columns["new_binary"].AllowDBNull = true;
    
            SQLiteCommandBuilder cmd = new SQLiteCommandBuilder(daAudit);
            daAudit.InsertCommand = cmd.GetInsertCommand();
            daAudit.InsertCommand.Transaction = txn;
    
    
            daAudit.InsertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
            daAudit.InsertCommand.Transaction = txn;
    
            string tableName = "";
            string primaryKey = "";
    
    
            foreach (DataTable tbl in ds.Tables)
            {
                tableName = tbl.TableName;
    
                if (ds.Tables[tableName].PrimaryKey.Length > 0)
                {
                    primaryKey = ds.Tables[tableName].PrimaryKey[0].ColumnName.Trim();
                }
    
                foreach (DataRow x in tbl.Rows)
                {
                    int codeID = -1;
    
                    for (int i = 0; i <= tbl.Columns.Count - 1; i++)
                    {
                        row_Audit = dsAudit.Tables[0].NewRow();
                        row_Audit["id"] = DBNull.Value;
                        row_Audit["tablename"] = tableName;
                        row_Audit["date"] = DateTime.Now;
                        row_Audit["action"] = x.RowState.ToString();
                        row_Audit["code"] = codeID;
                        row_Audit["user"] = System.Environment.UserName; //Login.LoggedInUser;
                        row_Audit["new_binary"] = null;
                        row_Audit["orig_binary"] = null;
    
    
                        string original = "";
                        string current = "";
    
                        // deletes should have blank current values
                        if (x.RowState != DataRowState.Deleted)
                        {
                            current = x[i, DataRowVersion.Current].ToString().Trim();
                        }
    
                        // Insert should have blank original values.
                        if (x.RowState != DataRowState.Added)
                        {
                            original = x[i, DataRowVersion.Original].ToString().Trim();
                        }
    
    
                        if (tbl.Columns[i].ColumnName == primaryKey)
                        {
                            try
                            {
                                if (HelperFunctions.IsNumeric(current, System.Globalization.NumberStyles.Integer))
                                {
                                    codeID = int.Parse(current);
                                }
                                else
                                {
                                    codeID = -1;
                                }
                            }
                            catch
                            {
                                codeID = -1;
                            }
                            row_Audit["code"] = codeID;
                        }
    
                        if (current != original)
                        {
    
                            row_Audit["field"] = ds.Tables[tableName].Columns[i].ColumnName;
                            row_Audit["new"] = current;
                            row_Audit["original"] = original;
    
                            dsAudit.Tables[0].Rows.Add(row_Audit);
                        }
                    }
    
                }
    
                daAudit.Update(dsAudit);
            }
        }
    }
    
    

    As can be seen in this code it will also work on fields that are binary blobs.

    New HelperFunctions class: This class has functions for creating a new sample database named hello.db, returning a connection to the sample database and testing if a field is numeric.

    
    class HelperFunctions
    {
    
        public static void CreateDatabase()
        {
    
            SQLiteConnection.CreateFile("hello.db");
            SQLiteConnection cn = CreateConnection();
            String.Format(CultureInfo.InvariantCulture, "Data Source = {0}; Version = 3", "database.sql");
    
            string sql = System.IO.File.ReadAllText("database.sql", System.Text.Encoding.UTF8);
            SQLiteCommand cmd = new SQLiteCommand(sql, cn);
            cmd.ExecuteNonQuery();
        }
    
        public static SQLiteConnection CreateConnection()
        {
            return new SQLiteConnection("Data Source = 'hello.db'; Version = 3");
        }
    
        static public bool IsNumeric(string val, System.Globalization.NumberStyles NumberStyle)
        {
            Double result;
            return Double.TryParse(val, NumberStyle, System.Globalization.CultureInfo.CurrentCulture, out result);
        }
    }