Вот пример таблицы данных (данные являются поддельными, но в том же формате, что и мои бизнес-данные, поступающие из внешней системы):
R1 Pears,Apples,Bananas 10
Oranges 5
R2 Apricots 15
Bananas 222
Apples,Oranges 15
Данные представлены в виде строки. Столбцы разделены табуляцией, строки - CRLF. Вывод должен быть одинаковым. Несколько значений разделяются запятыми.
Вот требуемый «сплющенный» вывод:
R1 Pears 10
R1 Apples 10
R1 Bananas 10
R1 Oranges 5
R2 Apricots 15
R2 Bananas 222
R2 Apples 15
R2 Oranges 15
Каждый столбец заполняется там, где есть пробелы, а столбцы с несколькими значениями (разделенные запятыми) дублируются и заполняются в других столбцах.
- Сложное предположение: количество столбцов на входе равно , произвольно .
- Упрощенное предположение: только один столбец будет разделяться запятыми.
Я работаю над этим с относительно наивным решением (циклы и некоторая рекурсия), но я бы хотел посмотреть, не будет ли это более подходящим вариантом LINQ или другого решения.
Я сейчас работаю в VB.NET, но C # тоже подойдет.
Вот мой ответ до сих пор ... оптимизация не очень важна для меня, но ясность всегда хорошая вещь.
Public Shared Function FlattenPlainTextTable(ByVal InputTable As String) As String
Const RowDelimiter As String = vbCRLF
Const ColDelimiter As String = vbTab
Const MultDelimiter As String = ","
''// First pass: determine the number of columns and which column if any contains
''// multiple values; build a new collection of rows pre-split into columns so the
''// split work can be reused for the second pass.
Dim rows As New System.Collections.Generic.List(Of String())
Dim maxColumnIndex As Integer
Dim multiValueColumnIndex As Integer = -1
Dim thisRow() As String
Dim foundComma As Integer
For Each row As String In Split(InputTable, RowDelimiter)
thisRow = Split(row, ColDelimiter)
rows.Add(ThisRow)
maxColumnIndex = Math.Max(maxColumnIndex, thisRow.GetUpperBound(0))
If multiValueColumnIndex < 0 Then
''// We haven't found a multi-value column yet. Function only supports,
''// at maximum, one multi-value column. Look for a comma in this cell,
''// and if found, make this the multi-value column.
foundComma = row.IndexOf(MultDelimiter)
If foundComma > 0 Then
Dim beforeComma As String
beforeComma = row.Substring(0, foundComma - 1)
''// The column index is the number of column delimiters found before
''// the comma. Faster than splitting into an array and looking for
''// the comma.
multiValueColumnIndex = beforeComma.Length - beforeComma.Replace(ColDelimiter, "").Length
End If
End If
Next
''// If no multi-value column was found, pretend it's the first column--simpler
''// logic to assume there is one.
If multiValueColumnIndex < 0 Then multiValueColumnIndex = 0
''// Initialize lastRow with the maximum number of columns found in the original
''// lastRow is used to fill down values where blanks are found on subsequent rows.
Dim lastRow() As String = Split(New String(","c, maxColumnIndex + 1), ",")
Dim outputTable As New StringBuilder()
Dim thisVal As String
Dim MuliValueColumnValues() As String
Dim multiValues() As String
For Each ThisRow In Rows
''// Get the multi-value column's data first so we know how many times to repeat the row.
If ThisRow.GetUpperBound(0) < multiValueColumnIndex Then
''// If the multi-value column is after the jagged edge of this row, create an array of
''// one blank value.
MuliValueColumnValues = Split("", MultDelimiter) ''// assures GetUpperBound(0)=0
Else
MuliValueColumnValues = Split(ThisRow(multiValueColumnIndex), MultDelimiter)
End If
''// Repeat this row for as many multi-value values were found
For RowRepeat As Integer = 0 To MuliValueColumnValues.GetUpperBound(0)
For columnIndex As Integer = 0 To MaxColumnIndex
If columnIndex = multiValueColumnIndex Then
''// Value is one of the multiple-value values
thisVal = MuliValueColumnValues(RowRepeat)
ElseIf ThisRow.GetUpperBound(0) < columnIndex Then
''// This row's jagged edge already ended, default to blank
thisVal = ""
Else
thisVal = ThisRow(columnIndex)
End If
If thisVal = "" Then
''// Fill down
thisVal = lastRow(columnIndex)
Else
''// Change the fill-down value for next time. (Fill-down only
''// fills down the *last* value in the multi-value column, not
''// the whole set.)
lastRow(columnIndex) = thisVal
End If
If columnIndex > 0 Then outputTable.Append(ColDelimiter)
outputTable.Append(thisVal)
Next
outputTable.Append(RowDelimiter)
Next
Next
return outputTable.ToString()
End Function