Каков c#-идиоматический способ применения оператора в двух списках?
Я привык делать это (с других языков):
a = 1, 2, 3;
b = 5, 1, 2;
c = a * b; // c = 5, 2, 6
это занимает два списка одинакового размера и применяет функцию к их членам, по одному за раз, чтобы получить список результатов. Это может быть такая простая функция, как умножение (выше) или что-то более сложное:
c = b>a ? b-a : 0; // c = 4, 0, 0
Я могу придумать несколько разных способов сделать это в C#, но я не уверен, как это сделает программист, обученный C#. Что это правильный способ сделать это в C# мир?
(единственная часть, о которой я спрашиваю, это гдеc = f(a,b)
. Я знаком с созданием списков и доступ к их элементам.)
4 ответа:
var c = a.Zip(b, (x, y) => x * y);
для более сложного после вашего редактирования:
var c = a.Zip(b, (x, y) => x > y ? x - y : 0);
отметим, что
Zip
- Это метод расширения, как СEnumerable
это действует наIEnumerable<T>
иQueryable
это действует наIQueryable<T>
, поэтому возможно, что, если лямбда-это тот, с которым может иметь дело данный поставщик запросов, что он может быть обработан как SQL-запрос в базе данных или каким-либо другим способом, отличным от In-memory in. NET.кто-то упомянул, что это был новый с 4.0 в комментариях. Это не трудно реализовать для 3.5 себя:
public class MyExtraLinqyStuff { public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) { //Do null checks immediately; if(first == null) throw new ArgumentNullException("first"); if(second == null) throw new ArgumentNullException("second"); if(resultSelector == null) throw new ArgumentNullException("resultSelector"); return DoZip(first, second, resultSelector); } private static IEnumerable<TResult> DoZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) { using(var enF = first.GetEnumerator()) using(var enS = second.GetEnumerator()) while(enF.MoveNext() && enS.MoveNext()) yield return resultSelector(enF.Current, enS.Current); } }
для .NET2. 0 или. NET3.0 вы можете иметь то же самое, но не как метод расширения, который отвечает на другой вопрос из комментариев; в то время не было действительно идиоматического способа делать такие вещи в .NET или, по крайней мере, не с твердым консенсусом среди тех из нас, кто кодирует в .NET. У некоторых из нас были методы, подобные приведенным выше в наших наборах инструментов (хотя и не методы расширения, очевидно), но это было больше, чем мы были под влиянием других языков и библиотек, чем что-либо еще (например, я делал такие вещи, как выше, из-за вещей, которые я знал из STL C++, но это было едва ли единственным возможным источником вдохновения)
предполагая .Net 3.5 со списками равной длины:
var a = new List<int>() { 1, 2, 3 }; var b = new List<int>() { 5, 1, 2 }; var c = a.Select((x, i) => b[i] * x);
результат:
5
2
6
Если вы не используете .NET 4.0 вот как написать свой собственный метод расширения, чтобы сделать Zip.
static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) { using (IEnumerator<TFirst> e1 = first.GetEnumerator()) using (IEnumerator<TSecond> e2 = second.GetEnumerator()) { while (e1.MoveNext() && e2.MoveNext()) { yield return resultSelector(e1.Current, e2.Current); } } }
для .NET-версий без LINQ я бы рекомендовал цикл for для выполнения этого:
List<int> list1 = new List<int>(){4,7,9}; List<int> list2 = new List<int>(){11,2,3}; List<int> newList = new List<int>(); for (int i = 0; i < list1.Count; ++i) { newList.Add(Math.Max(list1[i], list2[i])); }
это предполагает, конечно, что списки имеют одинаковый размер и не меняются. Если вы заранее знаете размер списка, вы также можете создать его экземпляр до нужного размера, а затем просто установить элемент во время цикла.