OlleR
OlleR

Reputation: 272

Having a List<string> where rows can be the same value, how to add a int to make each row unique?

I have a List where I would like to add an internal counter when several items have the same name.

var myList = new List<string>();

myList.Add("a");
myList.Add("b");
myList.Add("b");
myList.Add("c");

And I want the result to be a01 b01 b02 c01 after some fancy LINQ stuff.

Any great ideas out there?

Upvotes: 1

Views: 138

Answers (5)

cuongle
cuongle

Reputation: 75306

Another simpler way:

  var result = myList.GroupBy(x => x)
            .SelectMany(g => g.Select((x, i) => x + (i + 1).ToString("00")));

Upvotes: 1

Anders Arpi
Anders Arpi

Reputation: 8397

See other answers for some fancy (and pretty confusing) LINQ solutions. If you don't necessarily need to use LINQ:

var myList = new List<string> { "a", "b", "c", "b" };

var counter = new ConcurrentDictionary<string, int>();
for (int i = 0; i < myList.Count; i++)
{
    var currVal = myList[i];
    counter.AddOrUpdate(currVal, 1, (value, count) => count + 1);
    myList[i] = currVal + counter[currVal].ToString("00");
}

ConcurrentDictionary is not needed, you can do the "add or update" thing manually, depending on how you value speed vs code clarity. Either way, in my opinion this is a much more readable and maintainable way to do what you want to do. Don't be scared of ye olde for loop. :)

Of course this could be done as an extension method, or a static method on some utility class etc.

Upvotes: 1

Wasp
Wasp

Reputation: 3425

Not saying that's nice, but it's a (mostly) Linq solution:

var indexed =   from index in myList.Aggregate(
                    new 
                    { 
                        Counters = new Dictionary<string, int>(), 
                        Items = new List<string>() 
                    }, 
                    (acc, cur) => 
                    { 
                        if (!acc.Counters.ContainsKey(cur))
                            acc.Counters.Add(cur, 0);
                        acc.Counters[cur] = acc.Counters[cur] + 1;
                        acc.Items.Add(cur + acc.Counters[cur]);
                        return acc;
                    }).Items
                select index;

The accumulation part is pretty ugly, but it does the job and all inside a Linq computation.

EDIT

If the initial list is already sorted, this expression is cleaner (but might be inefficient, you'd have to see how many items you have in your list):

var indexed =   from index in myList.Aggregate(
                    new 
                    { 
                        Counter = 0, 
                        Key     = (string)null,
                        Items   = Enumerable.Empty<string>() 
                    }, 
                    (acc, cur) => 
                    {
                        var counter = acc.Key != cur ? 1 : acc.Counter+1;
                        return new 
                        { 
                            Counter = counter,
                            Key     = cur,
                            Items   = acc.Items.Concat(
                                        Enumerable.Repeat(cur + counter, 1))
                        };
                    }).Items
                select index;

Upvotes: 1

Rawling
Rawling

Reputation: 50104

If you want to preserve the order, there's no amazingly nice way to do this in OOTB LINQ, but you could knock something up like

public static IEnumerable<TResult> SelectWithUniquifier<TSource, TResult>(
    this IEnumerable<TSource> source, Func<TSource, int, TResult> uniquifier)
{
     Dictionary<TSource, int> counter = new Dictionary<TSource, int>();
     foreach(TSource s in source)
     {
         int thisIndex = counter.ContainsKey(s) ? counter[s] : 0;
         counter[s] = thisIndex + 1;
         yield return uniquifier(s, thisIndex);
     }
}

just with a better name.

For your example you'd have

var result = myList.SelectWithUniquifier((s, i) => s + (i+1).ToString("00"));

as the index you get is zero-based.

Upvotes: 1

Jan P.
Jan P.

Reputation: 3297

var res =
    myList
        .GroupBy(item => item)
        .Select(item => String.Format("{0}{1}", item.Key, item.Count()));

Upvotes: 0

Related Questions