1

I'm new to c# and want to process strings according to the following pattern:

var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };

/*
- if string contains > 5 characters --> Split
- check, which is the longest array from the split
- use the longest split to be an array 2D
*/

// expected result
var new_data = new List<object[]> {
new object[] { "ABCDE",  80, "TestM", "PQRST" },
new object[] { "FGHIJ", " ", "ain", "UVWXY" },
new object[] { "KLMNO", " ",    " ",    " "  }
}
Thomas
  • 1,225
  • 7
  • 16
Blank S
  • 13
  • 2

2 Answers2

1

You will have to constrain your List<object> to a List<string>, since you cannot assure a valid conversion back to the original type, once you split it.

var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
        
List<string> stringData = data.Select(o => o.ToString()).ToList();
        
const int maxCharacters = 5;
int nrOfEntries = data.Count;

List<string[]> result = new List<string[]>();
while (true)
{
    bool finished = true;

    string[] newRow = new string[nrOfEntries];
    for (int i = 0; i < nrOfEntries; i++)
    {
        string currentString = stringData[i];
        if (string.IsNullOrEmpty(currentString))
        {
            newRow[i] = " ";
            continue;
        }

        int length = currentString.Length;
        int charactersToTake = Math.Min(length, maxCharacters);
        int charactersRemaining = length - charactersToTake;

        newRow[i] = currentString.Substring(0, charactersToTake);
        switch (charactersRemaining)
        {
            case 0:
                stringData[i] = null;
                break;
            default:
                stringData[i] = currentString.Substring(charactersToTake, charactersRemaining);
                finished = false;
                break;
        }
    }
    result.Add(newRow);

    if(finished)
        break;
}

You could use List<object[]> result, but that list will only contain strings (and will only be useful as such) since there is no way you can convert back arbitrary objects, as stated before.

Thomas
  • 1,225
  • 7
  • 16
0

I would use Linq to solve the problem. (Be sure you have using System.Linq; at the top of your code file!)

First of all, we define a function to break down an object into several strings with length 5 or less or the object itself, if it is not a string.

object[] BreakDownObject(object o)
    => BreakDownObjectToEnumerable(o).ToArray();

IEnmuerable<object> BreakDownObjectToEnumerable(object o) 
{
   // If object is string, thant yield return every part 
   // with 5 characters (or less than 5, if necessary,
   // for the last one)
   if(o is string s) 
   {
       for(int i = 0; i < s.Length; i += maxStringLength) 
       {
           yield return s.Substring(i, Math.Min(s.Length - i, maxStringLength));
       }
   }
   // object is not a string, don't break it up
   else
   {
      yield return o;
   }
}

Wie use Substring in Combination with Math.Min. If length - index is smaller than 5, than we use this instead for the substring.

If we use this function on all items of the list we get an array of arrays of object. This array could be interpreted as "columns", because the first index gives us the columns, and the second index the subsequent broken down strings.

var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };

object[][] columns = data.Select(BreakDownObject).ToArray();

Now we want to transpose the array, so rows first. We write a function, that takes an index and our array of arrays and returns the row with that index. (Again I use Linq-IEnumerable for easier creation of the array):

object[] GetRowAtIndex(int index, object[][] columns)
    => GetRowAtIndexAsEnumerable(index, columns).ToArray();

IEnumerable<object> GetRowAtIndexAsEnumerable(int index, object[][] columns)
{
    foreach(var column in columns)
    {
        // Each column has different length,
        // if index is less than length, we
        // return the item at that index
        if(index < column.Length)
        {
            yield return column[index];
        }
        // If index is greater or equal length
        // we return a string with a single space 
        // instead.
        else
        {
            yield return " ";
        }
    }
}

This function also fills up missing items in the columns with a one-space string.

Last but not least, we iterate through the rows, until no column has items left:


List<object[]> GetAllRows(object[][] columns)
    => GetAllRowsAsEnumerable(columns);

Enumerable<object[]> GetAllRowsAsEnumerable(object[][] columns)
{
   int index = 0;
   while(true)
   {
        // Check if any column has items left
        if(!columns.Any(column => index < column.Length))
        {
            // No column with items left, left the loop!
            yield break;
        }

        // return the row at index
        yield return GetRowAtIndex(index, columns);

        // Increase index
        ++index;
    }
}

Put it together as one function:

List<object[]> BreakDownData(List<object> data)
{
   object[][] columns = data.Select(BreakDownObject).ToArray();
   return GetAllRows(columns);
}

After that, your code would be:

var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };

var new_data = BreakDownData(data);
Martini Bianco
  • 1,484
  • 1
  • 13
  • 23