3

I have an object "MyObject" with the properties (all string): "PropA", "PropB" and "PropC".

var List = new List();

I add some object in this list with the following value :

List.Add(new MyObject { PropA = "AA", PropB = "1", PropC = "TT"});
List.Add(new MyObject { PropA = "AA", PropB = "1", PropC = "TT"});
List.Add(new MyObject { PropA = "AA", PropB = "1", PropC = "TT"});

List.Add(new MyObject { PropA = "BB", PropB = "1", PropC = "TT"});
List.Add(new MyObject { PropA = "BB", PropB = "1", PropC = "TT"});

With linq, I'd like for each different "PropA" keep the first record but set to string.Empty the other. The result I'd like is a List with these values :

MyObject { PropA = "AA", PropB = "1", PropC = "TT"}
MyObject { PropA = "", PropB = "", PropC = "TT"}
MyObject { PropA = "", PropB = "", PropC = "TT"}
MyObject { PropA = "BB", PropB = "1", PropC = "TT"}
MyObject { PropA = "", PropB = "", PropC = "TT"}

I did with foreach but it's may be a bit cleaner in Linq, but the order of the result must be kept.

Christoph Fink
  • 22,727
  • 9
  • 68
  • 113
TheBoubou
  • 19,487
  • 54
  • 148
  • 236
  • 2
    I'm not sure if I understand your question correctly, so I'm only going to post this as a comment: Do you mean: `List.GroupBy(prop => prop.PropA).Select(group => group.First())`? – SBI Jan 07 '15 at 09:50
  • @SBI you got it right – Ehsan Sajjad Jan 07 '15 at 09:52
  • 4
    why is `PropB` modified but not `PropC` (i.e. what does "clean" mean)? Can you show with some pseudocode what you are trying to do? – default Jan 07 '15 at 09:52
  • 1
    @SBI: I though about the same until I saw the desired result. The OP still wants to get all objects, but all but the first in the group "cleaned"... – Christoph Fink Jan 07 '15 at 09:52
  • @ChrFin Oooh, glad you mentioned that. Completely missed that point. – SBI Jan 07 '15 at 09:53
  • the why is not important, the question is how :) I have 5 records in this list, I need keep 5 but some cleaned. By cleand I mean set to string.empty – TheBoubou Jan 07 '15 at 09:55
  • 1
    well, can you then at least explain what "clean" means? – default Jan 07 '15 at 09:56
  • @Kris-I: The "why" very well CAN be important, to possibly solve your "problem x" and not the "problem y" you asked at the moment, but is only caused by "x"... – Christoph Fink Jan 07 '15 at 09:56
  • 3
    keep using foreach. it won't be cleaner with LINQ since it's not designed to change/manipulate objects. it's all about querying. use it to get items you want and change them using foreach. If you wanna keep the original order this is the best way – Selman Genç Jan 07 '15 at 10:04
  • @Selman22 why shouldn't I use `LINQ` to change anything? I do it all the time (mostly to set the visiblity of something) and I have never had a single problem with this ;-) – t3chb0t Jan 07 '15 at 11:20

6 Answers6

4

This would work for the specific case:

var list = 
 List.GroupBy(x => x.PropA)
        .SelectMany(grp => new MyObject[] { grp.First() }
        .Concat(grp.Skip(1)
          .Select(x => { x.PropA = String.Empty; x.PropB = String.Empty; return x; } )
           )
         );

LinqPad result:

linqpad screenshot

As a side note, I don't think using Linq in this case is justified, it doesn't make the code faster or cleaner. One must use the available tools to write better, more performant, or cleaner code, but in this case, I don't think this is better than a foreach (at least a well thought foreach, and not a brute force one) in any possible way.

Jcl
  • 27,696
  • 5
  • 61
  • 92
  • The result must respect the order the first item like in the list the second and third "cleaned" (string.empty), the next line same than 4th entry and the 5th cleaned and not all cleaned and the 2 others after – TheBoubou Jan 07 '15 at 10:07
  • But this does not set the properties to `String.Empty` but creates new objects... – t3chb0t Jan 07 '15 at 10:58
  • I've updated the code so that it returns the same object, mutated. This will still be a new list, and the properties themselves are new objects (strings are immutable in C#). – Jcl Jan 07 '15 at 11:11
  • I see you've corrected your answer already ;-) I was just about to say - sure you can hehe – t3chb0t Jan 07 '15 at 11:11
  • @t3chb0t yeah, sorry, I thought you were the OP (and answered as if you were). I've deleted the comments since those don't apply if you are not the OP. Still, it's spitting out a new list, and the properties are new objects, and not mutated originals (and it's outside the `Linq` part of the question) :-) – Jcl Jan 07 '15 at 11:12
  • There are quite a few solutions to this. I've also posted one ;-] although it's quite similar to yours but without `Concat`. – t3chb0t Jan 07 '15 at 11:22
  • I really don't think Linq is the way to go for this (as I stated on my side note), but if the OP wants Linq, then so be it :-) – Jcl Jan 07 '15 at 11:29
  • 1
    You have a syntax error there. There is one `}` to many. The edit is too small for me to be accepted by the system. What I like about `LINQ` here is the easy groupping. – t3chb0t Jan 07 '15 at 11:35
1

How about this one:

    var result = List.GroupBy(prop => prop.PropA)
        .SelectMany(group => new [] { group.First() }.Concat(group.Skip(1).Select(x => { x.PropA = x.PropB = ""; return x; }))).ToList();
John
  • 3,627
  • 1
  • 12
  • 13
0
var groups = List.GroupBy(obj => obj.PropA);
foreach (var group in groups)
{
    foreach (var item in group.Skip(1))
    {
        item.PropA = "";
        item.PropB = "";
    }
}
Sushil
  • 5,265
  • 2
  • 17
  • 15
0

Yet another solution to this interesting question:

var result =
    list
    .GroupBy(x => x.PropA, (key, items) => new { Key = key, Items = items })
    .Select(x => x.Items.Select((item, index) =>
    {
        if (index == 0) return item;
        item.PropA = string.Empty;
        item.PropB = string.Empty;
        return item;
    }))
    .SelectMany(x => x)
    .ToList();

It modifies the original objects in each group but the first one. This time without Concat.


Sometimes it's all about performance. If someone concerned about this stumbles upon this question the answer is that in a case like this a simple loop is approx four times faster then the suggested linq queries:

For 1.000.000 items linq needs ~200ms and the following loop only ~45ms:

string prev = list.First().PropA;
foreach (var item in list.Skip(1))
{
    if (item.PropA == prev)
    {
        item.PropA = string.Empty;
        item.PropB = string.Empty;
    }
    else
    {
        prev = item.PropA;
    }
}
t3chb0t
  • 16,340
  • 13
  • 78
  • 118
-1

Without LINQ

HashSet<string> propA = new HashSet<string>();
HashSet<string> propB = new HashSet<string>();

for (int i = 0; i < list.Count; i++)
{
    if (!propA.Add(list[i].PropA))
    {
        list[i].PropA = string.Empty;
    }

    if (!propB.Add(list[i].PropB))
    {
        list[i].PropB = string.Empty;
    }
}
Denis
  • 5,894
  • 3
  • 17
  • 23
-2

This?

string currentValue = "";
            List.OrderBy(x => x.PropA).ToList()ForEach(x =>
            {
                if (string.IsNullOrEmpty(currentValue))
                {
                    // Assuming PropA will never be null
                    currentValue = x.PropA;
                    // this is first element
                    return;
                }
                if (x.PropA == currentValue)
                {
                    x.PropA = "";
                }
                else
                {
                    currentValue = x.PropA;
                }

            });
danish
  • 5,550
  • 2
  • 25
  • 28
  • 2
    _I did with foreach but it's may be a bit cleaner in Linq._ – Selman Genç Jan 07 '15 at 10:01
  • ...and only works if e.g. all "AA" follow each other, but as soon as there is a "BB" in between you will get two "AA"'s in the result. – Christoph Fink Jan 07 '15 at 10:02
  • 1
    @Kris-I You won't get anything cleaner with LINQ. You will end up using multiple extension methods. If you feel that is cleaner, well, go for it. – danish Jan 07 '15 at 10:05