Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Wednesday, May 30, 2012

PeekDataReader

For a current project, I needed to write an XML file for a batch transaction process. It just so happens that several transactions needed to be wrapped within a single batch element for a single order. What I needed was a simple extension class that wrapped SqlDataReader and allowed me to “peek” at the next row in the result set to check if the transaction was still within the current batch.
While doing a brief search, I found the following helper on Stack Overflow (SO). The correct way to do this is by implementing IDataReader as you cannot extend SqlDataReader. This is because SqlDataReader’s type constructor is marked internal in System.Data.dll and cannot be inherited.
The implementation on SO did not contain the pass-thru attributes and methods needed to implement IDataReader, IDataRecord, and IDisposable, so I decided to post this on my blog. Hope this helps someone out there!
sealed public class PeekDataReader : IDataReader
    {
        private IDataReader wrappedReader;
        private bool wasPeeked;
        private bool lastResult;

        public PeekDataReader(IDataReader wrappedReader)
        {
            this.wrappedReader = wrappedReader;
        }

        public bool Peek()
        {
            // If the previous operation was a peek, do not move...
            if (this.wasPeeked)
                return this.lastResult;

            // This is the first peek for the current position, so read and tag
            bool result = Read();
            this.wasPeeked = true;
            return result;
        }

        public bool Read()
        {
            // If last operation was a peek, do not actually read
            if (this.wasPeeked)
            {
                this.wasPeeked = false;
                return this.lastResult;
            }

            // Remember the result for any subsequent peeks
            this.lastResult = this.wrappedReader.Read();
            return this.lastResult;
        }

        public bool NextResult()
        {
            this.wasPeeked = false;
            return this.wrappedReader.NextResult();
        }

        #region IDataRecord pass-thru attributes and methods
        public int FieldCount { get { return this.wrappedReader.FieldCount; } }
        public object this[int i] { get { return this.wrappedReader[i]; } }
        public object this[string name] { get { return this.wrappedReader[name]; } }
        public bool GetBoolean(int i) { return this.wrappedReader.GetBoolean(i);}
        public byte GetByte(int i) { return this.wrappedReader.GetByte(i); }
        public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
            return this.wrappedReader.GetBytes(i,fieldOffset,buffer,bufferoffset,length);}
        public char GetChar(int i) { return this.wrappedReader.GetChar(i); }
        public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            return this.wrappedReader.GetChars(i,fieldoffset,buffer,bufferoffset,length);
        }
        public IDataReader GetData(int i)
        {
            return this.wrappedReader.GetData(i);
        }
        public string GetDataTypeName(int i) { return this.wrappedReader.GetDataTypeName(i); }
        public DateTime GetDateTime(int i) { return this.wrappedReader.GetDateTime(i); }
        public decimal GetDecimal(int i) { return this.wrappedReader.GetDecimal(i); }
        public double GetDouble(int i) { return this.wrappedReader.GetDouble(i); }
        public Type GetFieldType(int i) { return this.wrappedReader.GetFieldType(i); }
        public float GetFloat(int i) { return this.wrappedReader.GetFloat(i); }
        public Guid GetGuid(int i) { return this.wrappedReader.GetGuid(i); }
        public short GetInt16(int i) { return this.wrappedReader.GetInt16(i); }
        public int GetInt32(int i) { return this.wrappedReader.GetInt32(i); }
        public long GetInt64(int i) { return this.wrappedReader.GetInt64(i); }
        public string GetName(int i) { return this.wrappedReader.GetName(i); }
        public int GetOrdinal(string name) { return this.wrappedReader.GetOrdinal(name); }
        public string GetString(int i) { return this.wrappedReader.GetString(i); }
        public object GetValue(int i) { return this.wrappedReader.GetValue(i); }
        public int GetValues(object[] values) { return this.wrappedReader.GetValues(values); }
        public bool IsDBNull(int i) { return this.wrappedReader.IsDBNull(i); }
        #endregion

        #region IDataReader pass-thru attributes and methods
        public int Depth { get { return this.wrappedReader.Depth; } }
        public bool IsClosed { get { return this.wrappedReader.IsClosed; } }
        public int RecordsAffected { get { return this.wrappedReader.RecordsAffected; } }
        public void Close() { this.wrappedReader.Close(); }
        public DataTable GetSchemaTable() { return this.wrappedReader.GetSchemaTable(); }
        #endregion

        #region IDisposable
        public void Dispose() { this.wrappedReader.Dispose(); }
        #endregion
    }


Sample use:
using (IDataReader reader = new PeekDataReader(/* actual reader */))
{
    if (reader.Peek())
    {
        // perform some operations on the first row if it exists...
    }

    while (reader.Read())
    {
        // re-use the first row, and then read the remainder...
    }
}

Wednesday, August 31, 2011

Bitwise XOR on Byte Array

Spent the past few hours scrambling for a generic XOR solution on two byte arrays. I needed a solution that would add the match value to a specific result value if it did not already exist (assuming the most significant byte starts at the beginning of the array). You'd think this would be somewhere in the C# libraries by now. After an hour of searching, I decided to write my own. Ended up with the solution below.

static byte[] BitwiseXOR(byte[] result, byte[] matchValue)
{
    if (result.Length == 0)
    {
        return matchValue;
    }

    byte[] newResult = new byte[matchValue.Length > result.Length ? matchValue.Length : result.Length];
            
    for (int i = 1; i < newResult.Length+1; i++)
    {
        //Use XOR on the LSBs until we run out
        if(i > result.Length)
        {
            newResult[newResult.Length - i] = matchValue[matchValue.Length - i];
        }
        else if (i > matchValue.Length)
        {
            newResult[newResult.Length - i] = result[result.Length - i];
        }
        else
        {
            newResult[newResult.Length -i] = 
                (byte)(matchValue[matchValue.Length - i] ^ result[result.Length - i]);
        }
    }
    return newResult;
}

Tuesday, August 23, 2011

Fuzzy matching: a programmer's view of the Damerau-Levenshtein algorithm


The sheer mechanics of fuzzy matching algorithms are fascinating. Optimized variations are behind search engines and optical character recognition (OCR) softwares we see on the market today. An interesting algorithm I've recently used for a patient matching solution utilizes the Damerau-Levenshtein distance algorithm to locate fuzzy matches.

The Levenshetin approach considers how many substitutions, deletions, and insertions are needed to make two strings equal (also known as the edit distance between the strings). The algorithm was later improved by Damerau to include transposition (step highlghted on line 55). This solution is an example of bottom-up dynamic programming, because a matrix is flood filled to find the minimum distance. The complexity is cubic, but is improved to K x L complexity when using a maximum distance of interest (where K is the maximum distance, and L is the shortest string). The algorithm below uses a maximum distance (threshold paramter) and other simple optimizations to vastly improve the performance of the Damerau-Levenshtein. It has been tested to run over 10K string compares/second with two given strings under ten characters in length and a maximum threshold of three edits.

public static int DamerauLevenshteinDistanceImproved(string string1, 
    string string2, int threshold)
{
    // Return trivial case - where they are equal
    if (string1.Equals(string2))
        return 0;

    // Return trivial case - where one is empty
    if (String.IsNullOrEmpty(string1) || String.IsNullOrEmpty(string2))
        return (string1 ?? "").Length + (string2 ?? "").Length;

    // Ensure string2 (inner cycle) is longer
    if (string1.Length > string2.Length)
    {
        var tmp = string1;
        string1 = string2;
        string2 = tmp;
    }

    // Return trivial case - where string1 is contained within string2
    if (string2.Contains(string1))
        return string2.Length - string1.Length;

    var length1 = string1.Length;
    var length2 = string2.Length;

    var d = new int[length1 + 1, length2 + 1];

    for (var i = 0; i <= d.GetUpperBound(0); i++)
        d[i, 0] = i;

    for (var i = 0; i <= d.GetUpperBound(1); i++)
        d[0, i] = i;

    for (var i = 1; i <= d.GetUpperBound(0); i++)
    {
        var im1 = i - 1;
        var im2 = i - 2;
        var minDistance = threshold;

        for (var j = 1; j <= d.GetUpperBound(1); j++)
        {
            var jm1 = j - 1;
            var jm2 = j - 2;
            var cost = string1[im1] == string2[jm1] ? 0 : 1;

            var del = d[im1, j] + 1;
            var ins = d[i, jm1] + 1;
            var sub = d[im1, jm1] + cost;

            //Math.Min is slower than native code here
            //d[i, j] = Math.Min(del, Math.Min(ins, sub));
            d[i, j] = del <= ins && del <= sub ? del : ins <= sub ? ins : sub;

            if (i > 1 && j > 1 && string1[im1] == string2[jm2] 
                         && string1[im2] == string2[jm1])
                d[i, j] = Math.Min(d[i, j], d[im2, jm2] + cost);

            if (d[i, j] < minDistance)
                minDistance = d[i, j];
        }

        if (minDistance > threshold)
            return int.MaxValue;
    }

    return d[d.GetUpperBound(0), d.GetUpperBound(1)] > threshold
        ? int.MaxValue
        : d[d.GetUpperBound(0), d.GetUpperBound(1)];
}