Avoid Repeating Group by Linq in C# - c#

I need to optimize My Code. I Have Some Repeating Code. But I would like to optimize it. Can any one please help me to optimize My Code. How Can I greate Common Function For this???
foreach (var item in hotellocation.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count()))
{
if (item.Key != "")
{
lstHotelLocation.Add(new HotelLocation()
{
Name = item.Key,
count = item.Value
});
}
}
//need to Apply to linq
foreach (var item in hoteltype.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count()))
{
if (item.Key != "")
{
lstHotelType.Add(new HotelTypeFilter()
{
Name = item.Key,
count = item.Value
});
}
}

The first thing to do is get rid of those foreach loops, as they are incongruous with LINQ, and ditch the dictionary, since it's pointless:
var lstHotelLocation = hotellocation.GroupBy(x => x)
.Where(g => g.Key != "")
.Select(g => new HotelLocation {
Name = kv.Key,
count = g.Count()
})
.ToList();
var lstHotelType = hoteltype.GroupBy(x => x)
.Where(g => g.Key != "")
.Select(g => new HotelTypeFilter {
Name = g.Key,
count = g.Count()
})
.ToList();
If you want to further remove the duplication, you can do this:
static List<T> AssembleCounts<T>(IEnumerable<string> values,
Func<string, int, T> makeObject)
{
return values.Where(x => !string.IsNullOrEmpty(x))
.GroupBy(x => x)
.Select(g => makeObject(g.Key, g.Count()))
.ToList();
}
var lstHotelLocation = AssembleCounts(hotellocation,
(k, c) => new HotelLocation {
Name = k, count = c
});
var lstHotelType = AssembleCounts(hoteltype,
(k, c) => new HotelTypeFilter {
Name = k, count = c
});

Related

Duplicated linq query

I have 2 almost identical linq queries and want to remove repeating code from it. The only difference is the extra property in the GroupBy depending on some true/false condition.
How can I conditionally group by in linq without repeating the code like below?
var allergensList = _context.RecipeAllergens
.Where(x => x.ParentId == Id && x.AllergenId != null)
.ToList();
var allergens = new List<AllergenInfo>();
if (isRecipe)
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, x.AllergenMaycontains })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
else
{
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();
}
You can left grouping by x.AllergenMaycontains but under condition
allergens = allergensList
.GroupBy(x => new { x.AllergenName, x.AllergenIcon, AllergenMaycontains = isRecipe ? x.AllergenMaycontains : false })
.Select(a =>
{
var v = a.OrderBy(x => x.AllergenMaycontains).First();
return new AllergenInfo
{
AllergenName = v.AllergenName,
AllergenIcon = v.AllergenIcon,
AllergenMayContain = v.AllergenMaycontains ?? false
};
})
.ToList();

Turning a Dictionary<Guid,IList<String>> into Dictionary<string,IList<Guid>> With LINQ?

I have a Dictionary<Guid,IList<string>> which shows all the names an entity can have.
I want to convert this to see all the names mapped to all the entities.
so:
[["FFF" => "a", "b"],
["EEE" => "a", "c"]]
Becomes
[["a" => "FFF", "EEE"],
["b" => "FFF"],
["c" => "EEE"]]
I know this is easy to do with foreaches but I'm wondering if there is a way with LINQ / ToDictionary?
private static void Main(string[] args)
{
var source = new Dictionary<Guid, IList<string>>
{
{ Guid.NewGuid(), new List<string> { "a", "b" } },
{ Guid.NewGuid(), new List<string> { "b", "c" } },
};
var result = source
.SelectMany(x => x.Value, (x, y) => new { Key = y, Value = x.Key })
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Select(y => y.Value).ToList());
foreach (var item in result)
{
Console.WriteLine($"Key: {item.Key}, Values: {string.Join(", ", item.Value)}");
}
}
var dic = new Dictionary<string, List<string>>()
{
{"FFF", new List<string>(){"a", "b"}},
{"EEE", new List<string>(){"a", "c"}}
};
var res = dic.SelectMany(x => x.Value, (x,y) => new{Key = y, Value = x.Key})
.ToLookup(x => x.Key, x => x.Value);
Dictionary<int,IList<string>> d = new Dictionary<int ,IList<string>>(){
{1,new string[]{"a","b"}},
{2,new string[]{"a","d"}},
{3,new string[]{"b","c"}},
{4,new string[]{"x","y"}}};
d.SelectMany(kvp => kvp.Value.Select(element => new { kvp.Key, element}))
.GroupBy(g => g.element, g => g.Key)
.ToDictionary(g => g.Key, g => g.ToList());

String concatenation in GroupBy

This query below doesn't work because String.Join is not translatable.
PostgreSQL has the string_agg(expression, delimiter) feature though.
Is there anyway to use it from Linq?
var vwTourWithCategorieses = Context.Tours
.Join(Context.TourCategories, t => t.TourId, tc => tc.TourId,
(t, tc) => new { t.TourId, t.Name, tc.CategoryId})
.Join(Context.Categories, x => x.CategoryId, c => c.CategoryId,
(x, c) => new { x.TourId, TourName = x.Name, CategoryName = c.Name})
.GroupBy(x => new { x.TourId, x.TourName },
(key, c) => new VwTourWithCategories
{
TourId = key.TourId,
Name = key.TourName,
Categories = string.Join(",", c.Select(i => i.CategoryName))
})
.ToList();
Yes, unfortunately String.Join is not supported by EF, but I think you could project the result that you expect using Linq to objects after you materialize your query:
var query= Context.Tours
.Join(Context.TourCategories, t => t.TourId, tc => tc.TourId,
(t, tc) => new { t.TourId, t.Name, tc.CategoryId})
.Join(Context.Categories, x => x.CategoryId, c => c.CategoryId,
(x, c) => new { x.TourId, TourName = x.Name, CategoryName = c.Name})
.GroupBy(x => new { x.TourId, x.TourName }).ToList()
var result=query.Select( g=> new VwTourWithCategories
{
TourId = g.Key.TourId,
Name = g.Key.TourName,
Categories = string.Join(",", g.Select(i => i.CategoryName))
});
If you want to see all the CLR methods that are supported, you can check this link.
Update
Your query could be simpler if you use navigation properties. I think that is a many to many relationship, so you could do something like this:
var query= Context.Tours.Select(t=> new
{
t.TourId,
t.Name,
CategoryNames = t.TourCategories.Select(tc=>tc.Category.Name)
}
).ToList();
var result=query.Select( g=> new VwTourWithCategories
{
TourId = g.Key.TourId,
Name = g.Key.TourName,
Categories = string.Join(",", g.Select(i => i.CategoryName))
});

Get Max occurence of element in Linq

Hello all i have 2 class for the example i will name it A and B
A is a list and every A element have a list of B element.
B element have a type
I want to get a B element by occurance of it in my list of B element.
var listB = A
.SelectMany(a => a.B);
var listBId = listB
.Where(b => b.Type == SelectedType)
.Select(b => b.Id);
var IdMaxoccur = listBId
.GroupBy(x => x)
.OrderByDescending(x => x.Count())
.First()
.Key;
I find this a bit heavy for just get the max occurence of an id in a list....
Do you know a better way to do this?
I think your code is good enough if you rewrite and simplify it a bit and handle case when your list is empty. This code assumes that 0 is not a valid Id.
var result = A
.SelectMany(x => x.B)
.Where(x => x.Type == selectedType)
.GroupBy(x => x.Id, new { Id = x.Key, Count = x.Count() })
.OrderByDescending(x => x.Count)
.FirstOrDefault();
Console.WriteLine("Max ID = {0}, Count = {1}", result.Id, result.Count);
If you still think that your existing code is too complex, you could write extension method to hide complexity
public static int TryGetBIdWithMaxOccur(this IEnumerable<A> input, SelectedTypeEnum selectedType)
{
var result = input
.SelectMany(x => x.B)
.Where(x => x.Type == selectedType)
.GroupBy(x => x.Id, new { Id = x.Key, Count = x.Count() })
.OrderByDescending(x => x.Count)
.Select(x => x.Id)
.FirstOrDefault();
return result;
}
Then you can use it like this:
var result = A.TryGetBIdWithMaxOccur(SelectedTypeEnum.CoolValue);
if(result != default(int))
{
//do stuff
}
just an idea
var bs = A.SelectMany().Where().Select(b=>b.Id).OrderBy();
int current = -1, maxB = -1; // make sure it is stub Id
int currentCount = 0, maxCount = 0;
foreach(var b in bs)
{
if (b != current)
{
// check if previous was max
if (currentCount > maxCount)
{
maxB = current;
maxCount = currentCount;
}
// change current
current = b;
currentCount = 0;
}
currentCount ++;
}
To make it a bit shorter you can put the Where selector inside SelectMany and use GroupBy overload:
var idMaxOccur = A
.SelectMany(a => a.B.Where(b => b.Type == selectedType))
.GroupBy(b => b.Id, b => b.Id)
.OrderByDescending(g => g.Count())
.First().Key;

Grouping each 500 elements of array according to array elements

I have an array of 2000 strings. The strings are: "art", "economy", "sport" and "politic". I want to group each 500 elements and get their counts
Could anyone help please?
Another solution:
var count = 0;
var dictionaries =
strings.GroupBy(s => count++ / 500)
.Select(g => g.Distinct().ToDictionary(k => k, k => g.Count(s => s == k)))
.ToList();
This will create a List<Dictionary<string, int>>. Each dictionary represents a tally of 500 elements (or possibly less for the last dictionary), where the keys are strings and the values are the number of occurrences of the string among the 500 elements the dictionary represents.
There is no requirement to hardcode all the possible values that may be encountered.
For the maximum possible performance you can also use this version:
var count = 0;
var dictionaries =
strings.GroupBy(s => count++ / 500)
.Select(g => g.Aggregate(
new Dictionary<string, int>(),
(d, w) => { d[w] = (d.ContainsKey(w) ? d[w] + 1 : 1); return d; })
)
.ToList();
This version iterates over each element in your source array exactly once. The output is in the same format as the first version.
var result = strings.Select((s, i) => new { s, i })
.GroupBy(x => x.i / 500)
.Select(x => x.GroupBy(y => y.s)
.Select(z => new {
Name=z.Key,
Count=z.Count()
}).ToList())
.ToList();
Try
var grouping = Enumerable.Range(0,2000)
.Select(i => i / 500)
.Zip(Strings, (i,s) => new { Group = i, Str = s})
.GroupBy(anon => anon.Group,
anon => anon.Str,
(key,g) => new
{
Key = key,
Art = g.Count(str => str == "art"),
Economy = g.Count(str => str == "economy"),
Politic = g.Count(str => str == "politic"),
Sport= g.Count(str => str == "sport")
});
foreach(anon in grouping)
{
//textbox logic OP will have to change to suit
TextBox1.WriteLine(String.Format("Group: {0}", anon.Key));
TextBox1.WriteLine(String.Format("Art: {0}",anon.Art));
TextBox1.WriteLine(String.Format("Economy: {0}",anon.Economy ));
TextBox1.WriteLine(String.Format("Politic: {0}",anon.Politic ));
TextBox1.WriteLine(String.Format("Sport: {0}",anon.Sport));
}
Alternatively (as per Snowbear)
var grouping = Strings.Select((s,i) => new { Group = i / 500, Str = s})
.GroupBy(anon => anon.Group,
anon => anon.Str,
(key,g) => new
{
Key = key,
Art = g.Count(str => str == "art"),
Economy = g.Count(str => str == "economy"),
Politic = g.Count(str => str == "politic"),
Sport= g.Count(str => str == "sport")
});
foreach(anon in grouping)
{
//textbox logic OP will have to change to suit
TextBox1.WriteLine(String.Format("Group: {0}",anon.Key + 1));
TextBox1.WriteLine(String.Format("Art: {0}",anon.Art));
TextBox1.WriteLine(String.Format("Economy: {0}",anon.Economy ));
TextBox1.WriteLine(String.Format("Politic: {0}",anon.Politic ));
TextBox1.WriteLine(String.Format("Sport: {0}",anon.Sport));
}
int CountElementsInGroup = 500;
//from 500 to 1000
int NumberGroup = 2;
string[] GroupTypes = new string[4] { "art", "economy", "sport", "politic" };
//Fill example array
string[] arr = new string[2000];
Random rand = new Random();
for (int i = 0; i < arr.Length;i++ )
arr[i] = GroupTypes[rand.Next(0, 3)];
var res = (from p in arr.Skip((NumberGroup - 1) * CountElementsInGroup).Take(CountElementsInGroup)
group p by p into g
select new GroupCountClass { GroupName = g.Key, GroupCount = g.Count() });
textBox1.Text = "";
foreach (GroupCountClass c in res)
{
textBox1.Text += String.Format("GroupName:{0} Count:{1};",c.GroupName,c.GroupCount);
}

Categories

Resources