複数のアイテムが同じ名前の場合に内部カウンターを追加したいリストがあります。
var myList = new List<string>();
myList.Add("a");
myList.Add("b");
myList.Add("b");
myList.Add("c");
そして、いくつかの派手なLINQを使用した後、結果をa01 b01b02c01にします。
そこに素晴らしいアイデアはありますか?
いくつかの派手な(そしてかなり紛らわしい)LINQソリューションについては、他の回答を参照してください。必ずしも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
が必要ない場合は、速度とコードの明確さをどのように評価するかに応じて、手動で「追加または更新」を行うことができます。いずれにせよ、私の意見では、これはあなたがやりたいことをするためのはるかに読みやすく、保守しやすい方法です。ループのためにあなたがたの古いことを恐れないでください。:)
もちろん、これは拡張メソッドとして、または一部のユーティリティクラスの静的メソッドなどとして実行できます。
順序を保持したい場合、OOTB LINQでこれを行うための驚くほど良い方法はありませんが、次のようなものをノックアップすることができます
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);
}
}
より良い名前で。
あなたの例のためにあなたは持っているでしょう
var result = myList.SelectWithUniquifier((s, i) => s + (i+1).ToString("00"));
取得するインデックスはゼロベースであるため。
それがいいと言っているわけではありませんが、それは(ほとんど)Linqの解決策です:
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;
蓄積部分はかなり醜いですが、それは仕事とすべてをLinq計算の中で行います。
編集
最初のリストがすでにソートされている場合、この式はよりクリーンになります(ただし、非効率的である可能性があります。リストにあるアイテムの数を確認する必要があります)。
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;
別の簡単な方法:
var result = myList.GroupBy(x => x)
.SelectMany(g => g.Select((x, i) => x + (i + 1).ToString("00")));
var res =
myList
.GroupBy(item => item)
.Select(item => String.Format("{0}{1}", item.Key, item.Count()));