Условное агрегирование LINQ на основе значений следующих элементов - PullRequest
4 голосов
/ 30 марта 2012

Что является хорошим LINQ-эквивалентом этого псевдокода: «дан список строк, для каждой строки, которая не содержит символа табуляции, объедините его (с разделителем канала) до конца предыдущей строки, ивернуть полученную последовательность "?

Дополнительная информация:

У меня есть List<string>, представляющий строки в текстовом файле с разделителями табуляции.Последнее поле в каждой строке всегда является многострочным текстовым полем, и файл был сгенерирован ошибочной системой, которая обрабатывает поля со встроенными символами новой строки.Таким образом, я получаю список, подобный следующему:

1235 \t This is Record 1
7897 \t This is Record 2
8977 \t This is Record 3
continued on the next line
and still continued more
8375 \t This is Record 4

Я хотел бы объединить этот список, объединяя все пустые строки (строки без символов табуляции) до конца предыдущей строки.Например:

1235 \t This is Record 1
7897 \t This is Record 2
8977 \t This is Record 3|continued on the next line|and still continued more
8375 \t This is Record 4

Решить это с помощью цикла for() было бы легко, но я пытаюсь улучшить свои навыки LINQ, и мне было интересно, есть ли достаточно эффективное решение LINQ для этой проблемы.Есть ли?

Ответы [ 4 ]

3 голосов
/ 30 марта 2012

Это не проблема, которая должна быть решена с помощью LINQ.LINQ предназначен для перечисления, тогда как это лучше всего решить путем итерации.

Правильное перечисление последовательности означает, что ни один элемент не знает других элементов, что, очевидно, не будет работать в вашем случае.Используйте цикл for, чтобы вы могли аккуратно проходить строки по порядку и по порядку.

0 голосов
/ 05 апреля 2012

Попробовав решение for(), я попробовал решение LINQ и предложил следующее.Для моего достаточно маленького (10K строк) файла он был достаточно быстрым, и мне было все равно, насколько он эффективен, и я обнаружил, что он гораздо более читабелен, чем эквивалентное for() решение.

var lines = new List<string>      
{      
    "1235 \t This is Record 1",      
    "7897 \t This is Record 2",      
    "8977 \t This is Record 3",      
    "continued on the next line",      
    "and still continued more",      
    "8375 \t This is Record 4"      
};  
var fixedLines = lines
        .Select((s, i) => new 
            { 
                Line = s, 
                Orphans = lines.Skip(i + 1).TakeWhile(s2 => !s2.Contains('\t')) 
            })
        .Where(s => s.Line.Contains('\t'))
        .Select(s => string.Join("|", (new string[] { s.Line }).Concat(s.Orphans).ToArray()))
0 голосов
/ 30 марта 2012

Просто сделал для моего любопытства.

var originalList = new List<string>
{
    "1235 \t This is Record 1",
    "7897 \t This is Record 2",
    "8977 \t This is Record 3",
    "continued on the next line",
    "and still continued more",
    "8375 \t This is Record 4"
};

var resultList = new List<string>();

resultList.Add(originalList.Aggregate((workingSentence, next) 
    => { 
            if (next.Contains("\t"))
            {
                resultList.Add(workingSentence);    
                return next;
            }
            else
            {
                workingSentence += "|" + next;
                return workingSentence;
            }
    }));

Список результатов должен содержать то, что вы хотите.

Обратите внимание, что это не оптимальное решение. Строка workingSentence += "|" + next; может создавать множество временных объектов в зависимости от вашего шаблона данных.

Оптимальное решение может заключаться в том, чтобы хранить несколько индексных переменных для просмотра впереди строк и объединять их, когда следующая строка содержит символ табуляции, а не объединять один за другим, как показано выше. Однако он будет более сложным, чем приведенный выше, из-за проверки границ и хранения нескольких индексных переменных :).

Обновление : следующее решение не будет создавать временные строковые объекты для объединения.

var resultList = new List<string>();
var tempList = new List<string>();

tempList.Add(originalList.Aggregate((cur, next)
    => {
            tempList.Add(cur);
            if (next.Contains("\t"))
            {
                resultList.Add(string.Join("|", tempList));
                tempList.Clear();       
            }
            return next;
    }));

resultList.Add(string.Join("|", tempList));

Ниже приведено решение с использованием цикла for.

var resultList = new List<string>();
var temp = new List<string>();
for(int i = 0, j = 1; j < originalList.Count; i++, j++)
{
    temp.Add(originalList[i]);
    if (j != originalList.Count - 1)
    {   
        if (originalList[j].Contains("\t"))
        {
            resultList.Add(string.Join("|", temp));
            temp.Clear();
        }
    }
    else // when originalList[j] is the last item
    {
        if (originalList[j].Contains("\t"))
        {
            resultList.Add(string.Join("|", temp));
            resultList.Add(originalList[j]);
        }
        else
        {
            temp.Add(originalList[j]);
            resultList.Add(string.Join("|", temp));
        }
    }
}
0 голосов
/ 30 марта 2012

Вы могли бы сделать что-то вроде этого:

string result = records.Aggregate("", (current, s) => current + (s.Contains("\t") ? "\n" + s : "|" + s));

Я обманул и заставил Решарпера сгенерировать это для меня.Это близко - хотя сверху остается пустая строка.

Однако, как вы можете видеть, это не очень читабельно.Я понимаю, что вы ищете учебное упражнение, но я бы сделал хороший читаемый цикл foreach над этим в любой день.

...