CodeNinja
CodeNinja

Reputation: 3278

Implement the for loop in linq

I have been trying to implement the below foreach to linq , but couldn't get my head around doing it. Can anyone give me pointers on how I can go about changing this to linq or in general on how to think in terms of linq rather than using for loops.

class Program
    {
        static void Main(string[] args)
        {
            List<Sales> S1 = new List<Sales>();
            S1.Add(new Sales(1,1,false));
            S1.Add(new Sales(1, 1, false));
            S1.Add(new Sales(2, 2, false));
            S1.Add(new Sales(3, 3, false));
            S1.Add(new Sales(4, 4, false));

            List<Sales> S2 = new List<Sales>();
            S2.Add(new Sales(3, 3, false));
            S2.Add(new Sales(4, 4, false));

            //if S1 Product1 == S2 Product1 and S1 Product2 == S2 Product2 then S1 isSold == true.
            for(int i = 0; i < S2.Count; i++)
            {
                for(int j = 0; j < S1.Count; j++)
                {
                    if(S1[j].Product1 == S2[i].Product1 && S1[j].Product2 == S2[i].Product2)
                    {
                        S1[j].ISSold = true;
                    }
                }
            }
        }
        public class Sales
        {
            public int Product1 { get; set; }
            public int Product2 { get; set; }
            public bool ISSold { get; set; }

            public Sales(int product1, int product2, bool iSSold)
            {
                Product1 = product1;
                Product2 = product2;
                ISSold = iSSold;
            }
        }

    }
}

Upvotes: 1

Views: 9795

Answers (3)

Steve
Steve

Reputation: 622

It's normally considered bad practice to have side-effects within a Linq query, so I'd recommend using a structure of

foreach(var item in /*query*/)
  item.IsSold = true;

If S1 and S2 may be very large in the real use case, the current method of intersecting them is very slow - O(n^2) because it iterates the whole of S2 for each element of S1, and the other answers simply translate this poor performance into a Linq calling syntax.

Linq has a function for doing an intersection, so if S1 and S2 may be quite large, then using this will make your intentions clearer and also allow for improved performance with large sets.

The nested for and if loops can be rewritten as:

    foreach (Sales item in S1.Intersect(S2, new SalesProductComparer()))
      item.ISSold = true;

... if you define a helper class like

  public class SalesProductComparer : IEqualityComparer<Sales>
  {
    public bool Equals(Sales x, Sales y)
    {
      return x.Product1 == y.Product1 && x.Product2 == y.Product2;
    }
    public int GetHashCode(Sales obj)
    {
      return obj.Product1 + 65537 * obj.Product2;
    }
  }

The GetHashCode function is critical to Linq's performance with an intersection query - most elements of S1 and S2 will not even need to be compared with each other because they return different hash codes.

In the worst case (e.g. having a GetHashCode(...) function that simply does return 0) it would end up comparing every item in S1 to every item in S2, the same as your current code.

Upvotes: 0

cherepets
cherepets

Reputation: 259

My suggestion is

        S1.Where(x => S2.Any(y => x.Product1 == y.Product1 && x.Product2 == y.Product2))
          .Select(x => x.ISSold = true);
          .ToList();

Upvotes: 2

zaitsman
zaitsman

Reputation: 9519

Not sure what was wrong with foreach, one way you could do this is:

var result = S1.Select(x =>
 {
     if (S2.Any(s2 => s2.Product1 == x.Product1 && s2.Product2 == x.Product2))
     {
         x.ISSold = true;
     }

     return x;
 });

Upvotes: 1

Related Questions