Что такое С# -идиоматический способ применения оператора в двух списках?

Я привык делать это (с других языков):

 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 = f(a,b). Я знаком с созданием списков и доступом к их элементам.)

Ответ 1

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 запрос в базе данных или какой-либо другой способ, кроме встроенной памяти в .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, но это был едва ли единственный возможный источник вдохновения)

Ответ 2

Предполагая .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

Ответ 3

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

Ответ 4

Для .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]));
}

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