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.

10
  • 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())? Commented Jan 7, 2015 at 9:50
  • 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? Commented Jan 7, 2015 at 9: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"... Commented Jan 7, 2015 at 9:52
  • 1
    well, can you then at least explain what "clean" means? Commented Jan 7, 2015 at 9: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 Commented Jan 7, 2015 at 10:04

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

8 Comments

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
But this does not set the properties to String.Empty but creates new objects...
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#).
I see you've corrected your answer already ;-) I was just about to say - sure you can hehe
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.
|
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();

Comments

0
var groups = List.GroupBy(obj => obj.PropA);
foreach (var group in groups)
{
    foreach (var item in group.Skip(1))
    {
        item.PropA = "";
        item.PropB = "";
    }
}

Comments

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;
    }
}

Comments

-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;
    }
}

Comments

-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;
                }

            });

3 Comments

I did with foreach but it's may be a bit cleaner in Linq.
...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.
@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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.