Linq для разделения / анализа подстрок

У меня есть список таких строк, как:

  • Строка1
  • String1.String2
  • String1.String2.String3
  • Другое1
  • Другое1.Другое2
  • Test1
  • Stuff1.Stuff1
  • Text1.Text2.Text3
  • Folder1.Folder2.FolderA
  • Folder1.Folder2.FolderB
  • Folder1.Folder2.FolderB.FolderC

Теперь я хотел бы сгруппировать это в:

  • String1.String2.String3
  • Другое1.Другое2
  • Test1
  • Stuff1.Stuff1
  • Text1.Text2.Text3
  • Folder1.Folder2.FolderA
  • Folder1.Folder2.FolderB.FolderC

Если «String1» находится в следующем элементе «String1.String2», я проигнорирую первый, а если второй элемент находится в третьем, я возьму только третий «String1.String2.String3» и так далее (n элементов). Строка структурирована как узел / путь и может быть разделена точкой.

Как вы можете видеть в примере с папкой, в папке Folder2 есть два разных элемента вложенных папок, поэтому мне понадобятся обе строки.

Вы знаете, как справиться с этим с помощью Linq? Я бы предпочел VB.Net, но C # тоже подойдет.

С уважением, Ату


person Athu    schedule 22.03.2013    source источник


Ответы (4)


    Dim r = input.Where(Function(e, i) i = input.Count - 1 OrElse Not input(i + 1).StartsWith(e + ".")).ToList()

Условие в методе Where проверяет, является ли элемент последним от ввода или за ним не следует элемент, содержащий текущий.

В этом решении используется тот факт, что ввод - List(Of String), поэтому Count и input(i+1) доступны в O(1) время.

person MarcinJuraszek    schedule 22.03.2013
comment
Это хорошо работает, если List (of String) отсортирован. Спасибо. - person Athu; 22.03.2013

LINQ здесь не совсем правильный подход, потому что вам нужно получить доступ к более чем одному элементу одновременно.

Я бы сказал что-то вроде этого:

public static IEnumerable<string> Filter(this IEnumerable<string> source)
{
    string previous = null;
    foreach(var current in source)
    {
        if(previous != null && !current.Contains(previous))
            yield return previous;
        previous = current;
    }
    yield return previous;
}

Использование:

var result = strings.Filter();
person Daniel Hilgarth    schedule 22.03.2013

Довольно простой. Попробуй это:

var lst = new List<string> { /*...*/ };

var sorted =
    from item in lst
    where lst.Last() == item || !lst[lst.IndexOf(item) + 1].Contains(item)
    select item;
person Jan P.    schedule 22.03.2013
comment
Очень неэффективно! Last() будет каждый раз перечислять всю коллекцию! IndexOf() также является линейным. - person MarcinJuraszek; 22.03.2013
comment
Вы получите исключение ArgumentOutOfRange. - person Phil; 22.03.2013
comment
@Phil: Я добавил list.Last() == item, чтобы исключение не возникало, работает в LinqPad. - person Jan P.; 22.03.2013
comment
@MarcinJuraszek: Неправильно. Если source реализует IList<T> Last использует Count и индексатор. Однако это деталь реализации, так что, может быть, на нее не стоит полагаться ... - person Daniel Hilgarth; 22.03.2013
comment
@jaydotnet: вам нужно отредактировать свое сообщение, чтобы я мог удалить свой голос против, хотя я думаю, вам следует использовать StartsWith, а не Contains. - person Phil; 22.03.2013
comment
@jaydotnet - хорошо, интересно. Однако что, если бы список был {String1, String2.String1} или {String1, String11.String2}? Собственно тот же вопрос относится и к моему решению :( - person Phil; 22.03.2013
comment
@Phil: Вы правы ... чтобы решить эту проблему, вы должны определить некоторое разделение строк, которое не покрывается ни одним из данных решений. - person Jan P.; 26.03.2013
comment
@yaydotnet Могу ли я восстановить свой ответ - мне показалось, что он получился слишком сложным? - person Phil; 26.03.2013

следующая простая строка может помочь, я не уверен в стоимости производительности через

        List<string> someStuff = new List<string>();
        //Code to the strings here, code not added for brewity
        IEnumerable<string> result = someStuff.Where(s => someStuff.Count(x => x.StartsWith(s)) == 1);
person Vamsi    schedule 22.03.2013