-1

I have a problem that I really cannot get my head around. I know how to sort data in general but this one is taxing me!

I have a list of values in an array. The values look like this:

[03;02HTransactions
[03;16HPost Transactions
[04:02HDividends
[04;16HPostDividends
[01:01H-----------------------------------------------------
[05:01H-----------------------------------------------------
[02:16HDate: Today
[02:02HTrades

So its essentially ANSI formatting from a terminal screen which i'm trying to re-construct into a list so that I can print it on our test logs so it at least looks vaguely readable.

So this is how it works within the first 6 characters: [XX:YY where XX is the row number and YY is the column number. The H doesn't matter its just formatting.

Here is what I've got so far:

        List<string> rows = new List<string>();

        for (int i = 0; i <= filteredLines.Count - 1; i++)
        {
            int rowIndex = Convert.ToInt32(filteredLines[i].Substring(1, 2));
            Dictionary<int, string> columns = new Dictionary<int, string>();
            foreach (string row in filteredLines)
            {
                int innerRowIndex = Convert.ToInt32(row.Substring(1, 2));
                if (innerRowIndex == rowIndex)
                {
                    int columnIndex = Convert.ToInt32(filteredLines[i].Substring(4, 2));
                    string value = filteredLines[i].Remove(0, 7);
                    columns.Add(columnIndex, value);
                }
            }

            string columnConcatenated = "";

            for (int j = 0; j <= columns.Count; j ++ )
            {
                columnConcatenated = columnConcatenated + columns[j];
            }

            rows.Add(columnConcatenated);
        }

What I essentially want to do is to to build up the lines and sort them into a list based on the row number so it looks like:

--------------------------------------------
  Trades          Date: Today
  Transactions    Post Transactions
  Dividends       Post Dividends
--------------------------------------------

my example isn't 100% accurate as its hard to count the exact columns, but you get the idea. They just need to be on the same line in the correct order.

I cant help but feel i'm probably not going about this the best way. So is there an ideal way for me to achieve this?

Festivejelly
  • 670
  • 2
  • 12
  • 30
  • Use a SortedDictionary: http://msdn.microsoft.com/en-us/library/f7fta44c(v=vs.110).aspx but only if all row numbers are unique! – Davio Jul 22 '14 at 09:14
  • so `filteredLines` is a `string[]` and every element in the `filteredLines` is a string like `[03;02HTransactions`, etc.? – mihai Jul 22 '14 at 09:22

3 Answers3

0

Okay, I would implement it like so:

  • Create a simple POCO/class to represent a log entry with the properties row, column and text
  • Implement the IComparable interface, so these items can be sorted, on row # first and on column # second
  • Parse every log line and create a simple POCO Entry object for each
  • Use a simple List<Entry> and sort it afterwards
  • Use a StringBuilder to build up the final output
  • Loop over every Entry in the list, checking it's row # and perhaps entering some newlines for our StringBuilder if there are gaps
  • If we get an Entry with a row number which is the same as a previous one (which you can use a temp variable for), don't output a newline, but append the Entry.text to this line instead, at the column you want
Davio
  • 4,609
  • 2
  • 31
  • 58
0

You already have code to parse each line, extracting its row, column, and displayed text. If the lines of text are not sparse, you could represent this as basically a 2D dynamic array of characters that automatically pads itself out with spaces or empty lines, like so:

public class StringBuilderList : IList<string>
{
    readonly List<StringBuilder> list = new List<StringBuilder>();
    readonly char pad = ' ';
    const char DefaultPad = ' ';

    public StringBuilderList(char pad)
    {
        this.pad = pad;
    }

    public StringBuilderList() : this(DefaultPad) {}

    public void SetString(int iLine, int iChar, string text)
    {
        list.EnsureCount(iLine + 1);
        if (list[iLine] == null)
            list[iLine] = new StringBuilder(iChar + text.Length);
        var sb = list[iLine];
        sb.OverwriteAt(iChar, text, pad);
    }

    #region IList<string> Members

    public int IndexOf(string item)
    {
        for (int i = 0; i < Count; i++)
            if (this[i] == item) // this is not memory-efficient.
                return i;
        return -1;
    }

    public void Insert(int index, string item)
    {
        var sb = new StringBuilder(item);
        list.Insert(index, sb);
    }

    public void RemoveAt(int index)
    {
        list.RemoveAt(index);
    }

    public string this[int index]
    {
        get
        {
            // Hide the nulls from the caller!
            var sb = list[index];
            if (sb == null)
                return string.Empty;
            return sb.ToString();
        }
        set
        {
            if (string.IsNullOrEmpty(value))
            {
                if (list[index] != null)
                    list[index].Length = 0;
            }
            else if (list[index] == null)
            {
                list[index] = new StringBuilder(value);
            }
            else
            {
                list[index].Length = 0;
                list[index].Append(value);
            }
        }
    }

    #endregion

    #region ICollection<string> Members

    public void Add(string item)
    {
        list.Add(new StringBuilder(item));
    }

    public void Clear()
    {
        list.Clear();
    }

    public bool Contains(string item)
    {
        return IndexOf(item) >= 0;
    }

    public void CopyTo(string[] array, int arrayIndex)
    {
        foreach (var str in this)
            array[arrayIndex++] = str;
    }

    public int Count
    {
        get { return list.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(string item)
    {
        int index = IndexOf(item);
        if (index < 0)
            return false;
        RemoveAt(index);
        return true;
    }

    #endregion

    #region IEnumerable<string> Members

    public IEnumerator<string> GetEnumerator()
    {
        foreach (var sb in list)
            yield return (sb == null ? string.Empty : sb.ToString());
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

SetString is the method you would use, it copies the string into the 2d array of characters, expanding it as required with empty lines and/or space characters.

And helper methods:

public static class ListHelper
{
    public static void Resize<T>(this List<T> list, int count)
    {
        if (list == null || count < 0)
            throw new ArgumentException();
        int oldCount = list.Count;
        if (count > oldCount)
        {
            list.Capacity = count;
            for (int i = oldCount; i < count; i++)
                list.Add(default(T));
        }
        else if (count < oldCount)
        {
            for (int i = oldCount-1; i >= count; i--)
                list.RemoveAt(i);
        }
    }

    public static void EnsureCount<T>(this List<T> list, int count)
    {
        if (list == null || count < 0)
            throw new ArgumentException();
        if (count > list.Count)
            list.Resize(count);
    }
}

public static class StringBuilderHelper
{
    public static void OverwriteAt(this StringBuilder sb, int index, string text, char pad)
    {
        var textLen = text.Length;
        if (textLen + index > sb.Length)
        {
            for (int i = sb.Length, newLen = textLen + index; i < newLen; i++)
            {
                sb.Append(pad);
            }
        }

        for (int i = 0; i < textLen; i++)
        {
            sb[index + i] = text[i];
        }
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
0

So a few people have some solutions though they seemed a bit over complicated for what I needed. I managed to resolve the issue myself using a pen and paper then trying it in visual studio. Here is what I did:

First I created a list by looking through the original array and I sorted and removed duplicates:

        List<int> rowList = new List<int>();

        for (int i = 0; i <= filteredLines.Count - 1; i++)
        {
            int rowIndex = Convert.ToInt32(filteredLines[i].Substring(1, 2));
            rowList.Add(rowIndex);
        }

        rowList = rowList.Distinct().ToList<int>();
        rowList.Sort();

Next I created a container for my final list that would hold the values, then I ran my sorting routine which makes use of a SortedList in order to ensure the columns are sorted before I concatenate them:

        foreach (int listRow in rowList)
        {
            SortedList<int, string> columnList = new SortedList<int, string>();
            foreach(string row in filteredLines)
            {
                int rowIndex = Convert.ToInt32(row.Substring(1, 2));
                if(rowIndex==listRow)
                {
                    int columnIndex = Convert.ToInt32(row.Substring(4, 2));
                    string value = row.Remove(0, 7);
                    if(columnList.ContainsKey(columnIndex))
                    {
                        columnList[columnIndex] = columnList[columnIndex] + value;
                    }
                    else
                    {
                        columnList.Add(columnIndex, value);
                    }
                }
            }

            string concatenatedColumns = "";
            foreach(string col in columnList.Values)
            {
                concatenatedColumns = concatenatedColumns + col;
            }

            parsedAndSortedRows.Add(concatenatedColumns);
        }

It does the job fine and puts all the columns in order on the correct row as I wanted. Thanks for the help to everyone though it was through the different answers I helped come up with this solution.

Festivejelly
  • 670
  • 2
  • 12
  • 30