Каков 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 61

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

DotNetFiddle.Net Пример

Если вы не используете .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]));
}

это предполагает, конечно, что списки имеют одинаковый размер и не меняются. Если вы заранее знаете размер списка, вы также можете создать его экземпляр до нужного размера, а затем просто установить элемент во время цикла.