Search code examples
c#stringlistparsingtranspose

How to transpose rows of string into columns using lists in a foreach nested loop C#


I have a string structured as following:

RLLR
LRRL
RVVL
RRRR

// string was made like:
string s = "RLLR" + "\n" + "LRRL" + "\n" + "RVVL" + "\n" + "RRRR"; 

What I want to do to this table is transpose the rows to columns, so it will look like:

RLRR
LRVR
LRVR
RLLR

What I've done so far is that I converted the string to an array so i can loop through it like so:

List<string> line_value = new List<string>();//list for one line of array
List<string> single_value = new List<string>();//list for individual characters for those lines

string s = "RLLR" + "\n" + "LRRL" + "\n" + "RVVL" + "\n" + "RRRR"; 
string[] strarray = new string[]{""};
strarray = s.Split("\n");
int z = 0;
foreach(string line in strarray)
{
     line_value.Add(line);//adds single line to list

      foreach(char letter in line_value[z])
      {
        Console.WriteLine(letter.ToString());
        single_value.Add(letter.ToString());
      }
   z++;
}  

By doing it this way, I can print out the string like so, where everything is horizontal:

R
L
L
R
L
R
R
.
.
.
R

However, I am still kind of confused on how to establish the string so it will be transposed like so:

RLRR
LRVR
LRVR
RLLR

How would I transpose the string so it would turn the rows into columns?


Solution

  • The easy way of doing this is to do it without a foreach loop, and use a for loop abusing the fact that you can simply swap the column and row indexes.

    using System.Text;
    
    static string TransposeRowsToColumns(string rowString)
    {
        string[] rows = rowString.Split("\n");
    
        StringBuilder columnBuilder = new StringBuilder();
    
        for (int columnIndex = 0; columnIndex < rows[0].Length; columnIndex++)
        {
            for (int rowIndex = 0; rowIndex < rows.Length; rowIndex++)
            {
                columnBuilder.Append(rows[rowIndex][columnIndex]);
            }
            
            columnBuilder.Append("\n");
        }
    
        return columnBuilder.ToString();
    }
    

    Note that the above code relies on the fact that the number of columns is uniform.

    If you're wanting to do this with a foreach loop with lists, you can do it like:

    static string TransposeRowsToColumnsList(string rowString)
    {
        string[] rows = rowString.Split("\n");
        List<List<string>> grid = new List<List<string>>();
    
        int columnIndex = 0;
    
        foreach (string row in rows)
        {
            grid.Add(new List<string>());
    
            foreach (string column in rows.Select(r => string.Concat(r.Skip(columnIndex).Take(1))))
            {
                grid[columnIndex].Add(column);
            }
    
            columnIndex++;
        }
    
        return string.Join("\n", grid.Select(r => string.Concat(r.Select(c => c))));
    }
    

    Usage:

    string s = "RLLR" + "\n" + "LRRL" + "\n" + "RVVL" + "\n" + "RRRR"; 
    
    Console.WriteLine(TransposeRowsToColumns(s));
    Console.WriteLine(TransposeRowsToColumnsList(s));
    

    Edit

    For changing the input to essentially split the columns by space instead of the assumption that they're a single character, we can alter the second method to be like:

    static string TransposeRowsToColumnsList(string inputString, string columnSplitBy = "", string rowSplitBy = "\n")
    {
        IEnumerable<IEnumerable<string>> inputGrid = inputString.Split(rowSplitBy).Select(r =>
        {
            return columnSplitBy == "" ? r.Select(c => new string(c, 1)).ToArray() : r.Split(columnSplitBy);
        });
        
        List<List<string>> outputGrid = new List<List<string>>();
    
        int columnIndex = 0;
    
        foreach (IEnumerable<string> row in inputGrid)
        {
            outputGrid.Add(new List<string>());
    
            foreach (string column in inputGrid.Select(r => string.Concat(r.Skip(columnIndex).Take(1))))
            {
                outputGrid[columnIndex].Add(column);
            }
    
            columnIndex++;
        }
    
        return string.Join(rowSplitBy, outputGrid.Select(r => string.Concat(string.Join(columnSplitBy, r.Select(c => c)))));
    }
    

    Although this gets messy really quick. For a more scalable solution, we can create extension methods to separate each stage of the algorithm and spit out the desired result.

    We first define an interface that can convert a string to a desired type with an implementation of converting decimals:

    public interface IStringConverter<T>
    {
        T ConvertFromString(string input);
    }
    
    public class DecimalConverter : IStringConverter<decimal>
    {
        public decimal ConvertFromString(string input)
        {
            return decimal.Parse(input);
        }
    }
    

    Next we can define all the extension methods we'll need to transpose the grid to the way we want:

    public static class CustomExtensions
    {
        public static IEnumerable<string> ForceSplit(this string input, string pattern)
        {
            return pattern != string.Empty ? input.Split(pattern) : input.Select(x => x.ToString());
        }
        
        public static IEnumerable<IEnumerable<string>> ConvertToGrid(this string input, string columnSplit = "", string rowSplit = "\n")
        {
            return input.Split(rowSplit).Select(r => r.ForceSplit(columnSplit));
        }
        
        public static IEnumerable<IEnumerable<T>> ConvertToGrid<T>(this string input, IStringConverter<T> converter, string columnSplit = "", string rowSplit = "\n")
        {
            return input.Split(rowSplit).Select(r => r.ForceSplit(columnSplit).Select(converter.ConvertFromString));
        }
    
        public static IEnumerable<IEnumerable<T>> PivotGrid<T>(this IEnumerable<IEnumerable<T>> input)
        {
            return input
                .SelectMany(r => r.Select((c, index) => new {column = c, index}))
                .GroupBy(i => i.index, i => i.column)
                .Select(g => g.ToList());
        }
    
        public static string ConvertToString<T>(this IEnumerable<IEnumerable<T>> input, string columnSplit = "", string rowSplit = "\n")
        {
            return string.Join(rowSplit, input.Select(r => string.Join(columnSplit, r)));
        }
    }
    

    Things of note:

    • We are now converting each element into a cell of a desired type through ConvertToGrid
    • We are able to Pivot the grid from rows to columns (thanks to this answer)
    • We can then convert the grid back to string format if desired

    Usage

    string letters = "RLLR" + "\n" + "LRRL" + "\n" + "RVVL" + "\n" + "RRRR"; 
    string numbers = "25.0 45.7 23" + "\n" + "12.4 67.4 0.0" + "\n" + "0.00 0.00 0.00" + "\n" + "67.8 98.4 0.00"; 
    
    string transposedLetters = TransposeRowsToColumnsList(letters);
    string transposedNumbers = TransposeRowsToColumnsList(numbers, " ");
    
    string pivotedLetters = letters
        .ConvertToGrid()
        .PivotGrid()
        .ConvertToString();
    
    string pivotedNumbers = numbers
        .ConvertToGrid(new DecimalConverter(), " ")
        .PivotGrid()
        .ConvertToString(" ");
    

    I personally find the extension method approach more maintainable and more extensible, but the original method is easier to call.