Подтвердить что ты не робот

Выражение LINQ для кратчайшего общего префикса

Может ли кто-нибудь помочь мне с хорошим выражением LINQ для преобразования списка строк в другой список, содержащий только кратчайшие отдельные общие префиксы для строк? Разделитель для префиксов ..

Пример: ["A", "A.B.D", "A", "A.B","E","F.E", "F","B.C"]

Переход к: ["A", "E", "F", "B.C"]

Удалено:

  • "A.B.D" и "A.B", потому что префикс "A" уже находится в списке
  • "A", потому что это дубликат
  • "F.E", потому что "F" уже в списке

Спасибо!

4b9b3361

Ответ 1

Здесь вы идете:

from set in
    (from item in list select item.Split('.')).GroupBy(x => x[0])
select
  set.First()
     .TakeWhile((part, index) => set.All(x => x.Length > index && x[index].Equals(part)))
     .Aggregate((x, y) => String.Format("{0}.{1}", x, y));

В качестве объяснения:

  • Сначала мы разделили все строки на. и группа по их первому токену.
  • Затем мы рассмотрим первый элемент каждой группы, и мы берем от него части, в то время как каждый элемент этой группы продолжает соответствовать (TakeWhile).
  • Затем мы берем все эти части и перестраиваем их с помощью Aggregate (String.Format).

Ответ 2

EDIT: благодаря комментариям, указывающим на ошибку в моем предыдущем подходе.

Чтобы обойти этот недостаток, этот запрос должен работать:

var list = new List<string> { "A.B.D", "A", "A.B","E","F.E", "F","B.C", "B.C.D" };
var result = list.OrderBy(s => s)
                 .GroupBy(s => s[0])
                 .Select(g => g.First());

foreach (var s in result)
{
    Console.WriteLine(s);
}

Неправильный подход:

Следующий запрос будет группировать каждую строку по первому символу. Затем, если количество групп имеет более одного элемента, выбран ключ, в противном случае выбран один элемент.

var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = list.GroupBy(s => s[0])
                 .Select(g => g.Count() > 1 ? g.Key.ToString() : g.Single());

foreach (var s in result)
{
    Console.WriteLine(s);
}

Ответ 3

string[] source = {"A", "A.B", "A.B.D", "B.C", "B.C.D", "B.D", "E", "F", "F.E"};
var result = 
source.Distinct()
      .Select(str => str.Split('.'))
      .GroupBy(arr => arr[0])
      .Select(g =>
        {
          return string.Join(".", 
                 g.Aggregate((arr1, arr2) =>
                    {
                      return arr1.TakeWhile((str, index) => index < arr2.Length 
                                               && str.Equals(arr2[index]))
                                 .ToArray();
                    }));
        });

Шаги:

(1) Удалите дублированные элементы с помощью Distinct()

(2) Разделить каждый элемент на массив, также подготовиться к группировке

(3) Группируйте эти массивы с помощью первой строки в массиве

(4) Для каждой группы создайте один общий префикс, объединив все массивы в группе. Логикой для агрегирования является то, что для двух массивов arr1 и arr2 взять элементы в arr1 до (1) вне границ (2) соответствующий элемент в arr2 отличается от

Примечание. Я добавляю два оператора return в код, чтобы сделать его более понятным. Это может быть короче, если удалить return и скобки {}.

Ответ 4

    var items = new[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
    var result = items
        .OrderBy(s => s.Length)
        .Distinct()
        .ToLookup(s => s.Substring(0, 1))
        .Select(g => g.First());

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

Урожайность:    "A", "E", "F", "B.C"

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

Ответ 5

Пригвожденно - если предположить, что если в списке источников содержатся "Q.X" и "Q.Y", тогда результат должен содержать "Q".

var source = new []
{
    "A", "A.B.D", "A",
    "A.B", "E", "F.E",
    "F", "B.C",
    "Q.X", "Q.Y",
    "D.A.A", "D.A.B",
};

Func<string, int> startsWithCount =
    s => source.Where(x => x.StartsWith(s)).Count();

var results =
    (from x in source.Distinct()
    let xx = x.Split('.')
    let splits = Enumerable
        .Range(1, xx.Length)
        .Select(n => String.Join(".", xx.Take(n)))
    let first = startsWithCount(splits.First())
    select splits
        .Where(s => startsWithCount(s) == first)
        .Last()
    ).Distinct();


// results == ["A", "E", "F", "B.C", "Q", "D.A"]

Ответ 6

Как насчет:

var possible = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var shortest = possible.Distinct().Where(x => possible.Distinct().Where(y => !y.Equals(x) && x.StartsWith(y)).Count() == 0).ToList();

Он проверяет список на себя, исключая одинаковые элементы и любые элементы, которые начинаются с любого другого элемента. Я не уверен в эффективности, хотя:)

Ответ 7

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

class Program
{
    static void Main(string[] args)
    {
        var input = new string[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C", "B.C.D", "B.E" };

        var output = FilterFunc(input);
        foreach (var str in output)
            Console.WriteLine(str);

        Console.ReadLine();
    }

    static string[] FilterFunc(string[] input)
    {
        if (input.Length <= 1)
            return input;
        else
        {
            var firstElem = input[0];
            var indexNr = firstElem.Length;
            var maxFilteredElems = 0;
            for (int i = firstElem.Length; i > 0; i--)
            {
                var numberOfFilteredElems = input.Where(x => x.StartsWith(firstElem.Substring(0, i))).Count();
                if (numberOfFilteredElems > maxFilteredElems)
                {
                    maxFilteredElems = numberOfFilteredElems;
                    indexNr = i;
                }
            }
            var prefix = firstElem.Substring(0, indexNr);
            var recursiveResult = FilterFunc(input.Where(x => !x.StartsWith(prefix)).ToArray());
            var result = recursiveResult.ToList();
            prefix = prefix.EndsWith(".") ? prefix.Substring(0, prefix.Length - 1) : prefix;
            result.Insert(0, prefix);
            return result.ToArray();
        }
    }
}

Возможно, код был более эффективным и более организованным, но у него нет времени для этого. Я думаю, что другие решения не так до сих пор, так что почему вы получаете мой более длинный. Я думаю, вам нужно решить эту проблему рекурсивно, чтобы получить кратчайший список.

Ответ 8

Моя попытка, перемещает элементы, удаляя все префикс с другим элементом.



static void Run()
{
    var list = new string[] {"A", "A.B.D", "A",
                            "A.B", "E", "F.E",
                            "F", "B.C",
                            "Q.X", "Q.Y",
                            "D.A.A", "D.A.B"
                        };

    int size = 0;
    var prefixList = new string[list.Length];
    Array.Copy(list, prefixList, list.Length);

    for (int i = 0; i < list.Length; i++)
        prefixList 
        = prefixList
            .Where(c => !c.StartsWith(list[i]) || c == list[i])
            .Distinct()
                .ToArray();

    foreach (string s in prefixList)
        Console.WriteLine(s);
    Console.ReadLine();
}

Ответ 9

var list = new[] { "A.B.D", "A", "E", "A.B", "F", "F.E", "B.C.D", "B.C" };

var result = from s in list
             group s by s.Split('.').First() into g
             select LongestCommonPrefix(g);

foreach (var s in result)
{
    Console.WriteLine(s);
}

Вывод:

A
E
F
B.C

Метод поиска самого длинного общего префикса из здесь (замените / на .).

Ответ 10

Мое понимание вопроса гласит список, содержащий как "B.C", так и "B.E", но "B" не получит "B.C" и "B.E".

string[] items = { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
char delimiter = '.';
var result = (from item in items.Distinct()
             where !items.Any(other => item.StartsWith(other + delimiter))
             select item).ToArray();

foreach (var item in result)
{
    Console.WriteLine(item);
}

Выход

A
E
F
B.C

также работает с многосимвольными префиксами

string[] items = 
{
    "Alpha",
    "Alpha.Beta.Delta",
    "Alpha",
    "Alpha.Beta",
    "Echo",
    "Foxtrot.Echo",
    "Foxtrot",
    "Baker.Charlie"
 };

получает

Alpha
Echo
Foxtrot
Baker.Charlie

Ответ 11

var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };

var result = (list.Select(a => a.Split('.').First())).Distinct();

Ответ 12

Если я строго придерживаюсь определения, которое было предоставлено, ответ проще, чем кажется:

  • удалить дубликаты = > отдельные
  • удалить любой элемент, который начинается с любого другого элемента в списке.

поэтому получаем:

from item in items.Distinct()
where !items.Any(other => other != item && item.StartsWith(other + '.'))
select item;

Для вопросов B.C и B.D это работает так, как указано: ни один не включает другой, поэтому ни одно из условий удаления, упомянутых dave, не запускается.

Я признаю, что могут быть более интересные участники, но я боюсь, что просто не в вопросе;)

Обновление: добавлено предложение разделителя в where для учета слов multi- char. спасибо svick!