Привет, у меня есть список строк из нескольких слов.
List<string> myList = new List<string>();
myList.Add("Robert is a bank manager");
myList.Add("Alice is a cashier");
myList.Add("Andrew is bank customer");
Теперь для каждой строковой строки в myList я хотел получить n-е слово. Итак, скажем, я хочу получить слово с четным номером из каждой строки, я хочу получить вывод как:
var[] output = {"is", "bank", "is", "cashier", "is", "customer"};
Я могу просто использовать цикл for и сохранять каждое n-е слово в выходном массиве, но любопытно, как сделать то же самое с помощью linq.
var n = 2;
myList.Select(s => s.Split().Where((t, i) => (i - 1) % n == 0))
Здесь используется перегрузка Where
, которая включает индекс в качестве аргумента. Обратите внимание, что это не является надежным/сильно протестированным/и т. д.
Один из подходов к выполнению этого в LINQ может быть следующим:
var res = myList.Select(x => x.Split()
.Where((_,i) => (i + 1) % 2 == 0))
.SelectMany(x => x);
Это создает список списков слов, которые были отфильтрованы с помощью индекса, а затем сглаживает список списков с помощью SelectMany
в функции идентификации.
Как отмечает @Xiaoy312 в комментариях, вы можете еще больше упростить это, используя SelectMany
напрямую, что приведет к следующему:
var res = myList.SelectMany(x => x.Split().Where((_, i) => (i + 1) % 2 == 0));
Вы можете уменьшить .Select(selector).SelectMany(x => x)
до просто SelectMany(selector)
Ничего себе, люди быстры на это! Вот как я бы реализовал это в методе:
public static IEnumerable<string> GetEveryNthWord(string input, int n)
{
return input.Split().Where((value, index) => (index + 1) % n == 0);
}
public static IEnumerable<string> GetEveryNthWord(IEnumerable<string> input, int n)
{
return input.SelectMany(sentence => GetEveryNthWord(sentence, n));
}
И в использовании:
private static void Main()
{
var myList = new List<string>
{
"Robert is a bank manager",
"Alice is a cashier",
"Andrew is bank customer"
};
var result = GetEveryNthWord(myList, 2).ToList();
/* result
Count = 6
[0]: "is"
[1]: "bank"
[2]: "is"
[3]: "cashier"
[4]: "is"
[5]: "customer"
*/
}
I am curious how to do the same using LINQ
Дайте кому-нибудь рыбу, и вы накормите его на один день; научите их ловить рыбу, бла-бла-бла.
Что вам нужно, так это мыслительный процесс для кода, улучшающего LINQ. Вот процесс, который я использую.
Первое, что я делаю, это смотрю на описание проблемы и думаю: «Где операции, которые выполняются над элементом каждый последовательности?»
for each string line in myList, I wanted to get nth word.
О, есть один.
Следующий вопрос: предположим, я хочу сделать это с элементом индивидуальный последовательности; как бы я это сделал? Могу ли я написать метод, который делает это?
Эта проблема, кажется, хорошо разбивается на две подзадачи:
1) Учитывая строку, получить последовательность слов. 2) Учитывая последовательность вещей, составить последовательность каждой n-й вещи.
Мы знаем, как сделать первый: это Split()
.
Какой второй? И снова у нас есть операция, в которой мы что-то делаем с каждым элементом последовательности. На этот раз мы фильтрация, поэтому вполне вероятно, что мы захотим использовать Where
.
Как уже отмечали другие, вы можете использовать Where
, который принимает для этого индекс. Напишем вспомогательный метод:
public static IEnumerable<T> TakeEveryNth(
this IEnumerable<T> items,
int n,
int offset = 0) =>
items.Where((item, i) => (i - offset) % n == 0);
(Обратите внимание на это решение: в нем нет ничего специфичного для последовательностей строк, поэтому я сделал его универсальным. Теперь у нас есть полезный инструмент не только для слов.)
Супер. Складываем два последних вместе:
public static IEnumerable<string> EveryNthWord(
this string sentence,
int n,
int offset = 0) =>
sentence.Split(" ").TakeEveryNth(n, offset);
Хорошо, мы хотим сделать это для каждого элемента в списке, а затем соединить все результаты вместе. Это SelectMany
. Итак, решение вашей проблемы:
public static IEnumerable<string> EveryNthWord(
this IEnumerable<string> sentences,
int n,
int offset = 0) =>
sentences.SelectMany(sentence => sentence.EveryNthWord(n, offset));
И теперь у нас есть решение вашей проблемы:
var result = sentences.EveryNthWord(2, 1).ToList();
И мы закончили.
Вот как вы решаете проблему, когда пытаетесь придумать решение LINQ: разбейте все на мелкие части и напишите для каждой части метод, который хорошо решает одну проблему. Затем объедините их вместе с операторами запроса.
Что, если бы мы захотели объединить все это в одно выражение? Гораздо проще это сделать, если он уже разбит. Просто объедините их вместе:
var n = 2;
var offset = 1;
var result = sentences
.SelectMany(sentence => sentence
.Split(" ")
.Where((item, i) => (i - offset) % n == 0))
.ToList();
Но, честно говоря, код будет более полезным и читабельным, если вы разделите его. Каждый вспомогательный метод полезен сам по себе, поэтому сохраните их!
Отличное объяснение подхода к решению.
Понравился мыслительный процесс! Возвращает ли это непечатаемые символы, такие как \r или \n?
Палец вверх за
SelectMany