Linq query Group By with contains - c#

I have a list of the List of string that is Currency Code.
var currencyCode = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
And i Have another complex object.
var rate = new List<Rate>()
{
new Rate() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Rate() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Rate() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
I will have the matched currency in the below list:
var matchedCurrency = from c in rate
where currency.Any(w => c.CurrencyCode.Contains(w))
select c;
What i wanted is that the matching currency list should be in grouped, grouped by currency code.
I tried by the following way but it did not worked.
var Grp = rate.GroupBy(item => currency.Any(w => item.CurrencyCode.Contains(w)))
.Select(group => new
{
group.Key,
DataList = group.ToList()
});
I don't get i am actually missing. I have tried by various ways.
I know i can loop through the rate and push into another object. But that does not look nice i wanted to do this by using Linq. But i could not achieve the point.
Output will be displayed with this object:
public class CurrencyMap
{
public string Code { get; set; }
public List<Currency> currency { get; set; }
}
public class Currency
{
public string CurrencyName { get; set; }
public string CurrencyCode { get; set; }
public string BranchName { get; set; }
}
enter code here
EDIT:
I missed the things at first but i also need to have the empty list if the matching code was not found in the rate.
In Rate there is not the matching list for "BHD", "LYD". But i also need to have the empty list with the code "BHD", "LYD"

First select the matching currency code, then group by the selected code.
var groupedRates = rate
.Select(r => new
{
rate = r,
code = currencyCode.FirstOrDefault(c => r.CurrencyCode.Contains(c))
})
.GroupBy(x => x.code, x => x.rate); //maybe you don't want to throw away the resolved code like I do in the element selector...
Edit: I guess I was a bit to focused on the grouping aspect. Since you want to include all currency codes and mentioned a specific output structure, forget about grouping and just select your result:
var groupedRatesList = currencyCode
.Select(c => new CurrencyMap
{
Code = c,
currency = rate
.Where(x => x.CurrencyCode.Contains(c))
.Select(x => new Currency
{
BranchName = x.BranchName,
CurrencyCode = x.CurrencyCode, // or maybe you want to insert c here?
CurrencyName = x.CurrencyName
})
.ToList()
})
.ToList();

It is a rather hacky approach but you could use Regex.Match to achieve this. The basic idea is that you need the value from currencyCode as the key for your grouping.
This can be returned by a sucessfull match with regex. The property Match.Value will contain the string value for the key
Disclaimer: Unfortunately all negative matches will be return also as empty groups. You would need to filter then the empty groups out:
var result = currencyCode.SelectMany
(
x=> rate.Where(r=> r.CurrencyCode.Contains(x))
.GroupBy(r=> Regex.Match(r.CurrencyCode, x).Value)
).Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Actually it works also without regex:
var result = rate.GroupBy(r => currencyCode.FirstOrDefault(c=> r.CurrencyCode.Contains(c)))
.Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Disclaimer 2: Like all pattern matching it will lead to problems if you have ambiguous patterns. If a CurrencyCode value contains more than 1 of the abriviations ( may be inside the word ) you can get non sensical results/ or double entries.
Although I found it to be an intriguing problem to solve with linq, personally I would refrain from this approach. If I would have to return to this code after 9 months to maintain it, I would be way more happy to read this:
Dictionary<string,IEnumerable<Rate>> groupedSet = new Dictionary<string, IEnumerable<Rate>>();
foreach (var key in currencyCode)
{
IEnumerable<Rate> _result = rate.Where(x => x.CurrencyCode.Contains(key));
if (_result.Any())
{
groupedSet.Add(key, _result);
}
}
than to start remembering what da hack I wrote back then and what I might have thought of when I wrote it back then....

Not the best way since this approach assumes you have fixed length of Currency but you can try this:-
int currencyLength = currencyCode.First().Length;
var result = rate.Where(x => currencyCode.Any(z => x.CurrencyCode.Contains(z)))
.GroupBy(x => x.CurrencyCode.Substring(0, currencyLength))
.Select(x => new
{
Currency = x.Key,
List = x.ToList()
});
Fiddle

Try this;
var currencyCodes = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
var currencies = new List<Currency>()
{
new Currency() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Currency() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Currency() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
List<CurrencyMap> maps = currencies.Select(c => new
{
Currency = c,
FoundCode = currencyCodes.FirstOrDefault(code => c.CurrencyCode.Contains(code))
})
.Where(o => o.FoundCode != null)
.GroupBy(o => o.FoundCode)
.Select(grp => new CurrencyMap() { Code = grp.Key, Currencies = grp.Select(o => o.Currency).ToList() })
.ToList();

Related

Convert list entries to array

In order to generate a graph using d3 I need to convert my list of time entries to several arrays.
I store my data in a list of work records per day per staff
I need to be able to get an array of all days, and then a array each per member of staff.
So lets say staff x has 3.5h against 01/1/19 and 4.5h against 03/1/19
Staff y has 6h agaist 2/1/19
I'd expect my arrays to look as following:
Dates[1/1/19, 2/1/19, 3/1/19]
X[3.5,0,4.5]
Y[0,6,0]
Some of my code is:
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
public class TicketWorkRecord
{
public DateTime Date { get; set; }
public decimal TimeSpent { get; set; }
}
Assuming you have a class called StaffMember that looks like this:
public class StaffMember
{
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
// Other properties
}
And after adding the following constructor to your TicketWorkRecord class:
public TicketWorkRecord(DateTime date, decimal timeSpent)
{
Date = date;
TimeSpent = timeSpent;
}
Let's create a dummy data for X and Y staff members:
StaffMember X = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{
new TicketWorkRecord(DateTime.Today.Date, 3.5M),
new TicketWorkRecord(DateTime.Today.Date.AddDays(2), 4.5M)
}
};
StaffMember Y = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{ new TicketWorkRecord(DateTime.Today.Date.AddDays(1), 6M) }
};
var staffMembers = new List<StaffMember>() { X, Y };
Now, you can construct your desired 3 arrays using the following code:
var dates = staffMembers.SelectMany(s => s.TimeByDateByStaff)
.Select(t => t.Date)
.Distinct()
.OrderBy(d => d).ToArray();
var xTimes = dates.Select(d => X.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
var yTimes = dates.Select(d => Y.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
To test it:
Console.WriteLine("Dates: " + string.Join(",", dates.Select(d => d.ToShortDateString())));
Console.WriteLine("xTimes: " + string.Join(",", xTimes));
Console.WriteLine("yTimes: " + string.Join(",", yTimes));
Output:
Dates: 12/01/2019,13/01/2019,14/01/2019
xTimes: 3.5,0,4.5
yTimes: 0,6,0
If TicketWorkRecord has a property specifying which staff member it is (X or Y), then this would be pretty straight forward using LINQ:
var dates = TimeByDateByStaff.Select(x => x.Date.ToString("MM/dd/yy")).ToArray();
var staffXTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "X" ? x.TimeSpent : 0M).ToArray();
var staffYTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "Y" ? x.TimeSpent : 0M).ToArray();
Alternatively, if the exact staff members aren't known at compile time then you can get the time entries by staff member at runtime:
var timeSpentByStaffMembers = TimeByDateByStaff
.Select(x => x.StaffMember)
.Distinct()
.ToDictionary(
key => key,
value => TimeByDateByStaff.Select(x => x.StaffMember == value ? x.TimeSpent : 0M).ToArray());
With the following data:
var TimeByDateByStaff = new List<TicketWorkRecord>
{
new TicketWorkRecord
{
Date = new DateTime(2019, 1, 1),
TimeSpent = 3.5M,
StaffMember = "X"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 2, 1),
TimeSpent = 6M,
StaffMember = "Y"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 3, 1),
TimeSpent = 4.5M,
StaffMember = "X"
}
};
The LINQ statements above produce the following output:
If i understand you correctly, you want to split your list of objects into individuals fields arrays.
If yes, Lets say you have the following list
List<Object> ObjectsList = ObjectsList;
string[] ExtractDates = ObjectsList.Select(x=>x.Date).ToArray();
double[] TimeSpent = ObjectsList.Select(x=> x.TimeSpent).ToArray();
and so forth, you can apply where condition to filter based on members

Get most common values/pattern from a list of lists of objects

Update 2: More concise
I have this data in my database
I need to get the most frequent masteries used for a champion. How can I achieve that with LINQ?
This is how I get the data:
List<List<Masteries.Mastery>> MasteryList = new List<List<Masteries.Mastery>>();
Conex.Command.CommandText = "SELECT masteries FROM matchdetails_participantsdetails_na WHERE championID=#champID;";
Conex.Command.Parameters.AddWithValue("#champID", championID);
using (MySqlDataReader dbReader = Conex.Command.ExecuteReader())
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
while (dbReader.Read())
{
List<Masteries.Mastery> tmpMastery = new List<Masteries.Mastery>();
tmpMastery = serializer.Deserialize<List<Masteries.Mastery>>(dbReader.GetString("masteries"));
MasteryList.Add(tmpMastery);
}
}
Mastery Class
class Mastery
{
public int ID
{
set; get;
}
public byte Rank
{
set; get;
}
}
From what I understand you want to get external list where internal list with max count resides?
List<Mastery> maxList = new List<Mastery>();
var max = 0;
foreach (var list in MasteryList)
{
var masteryList = list
.GroupBy(gp => new { gp.ID, gp.Rank }) // Agrupamos por ID y Rango
.OrderByDescending(g => g.Count()) // Ordenamos de mayor a menor
.Select(x => new { Key = x.Key, Count = x.Count() }) // Seleccionamos el valor y la cantidad de repeticiones
.ToList();
var maxCurrent = masteryList.Max(x => x.Count);
if (max < maxCurrent)
{
maxList = list;
max = maxCurrent;
}
}
If that's the case then you should create a class ConfigurationMastery and use List<ConfigurationMastery> instead of List<List<Mastery>>. It will be easier for you to operate with this class. The above code can be simplified to use just linq.
I think I understand what you want. So, you have a list of List, with each item being a list that contains 10 masteries. Each of these masteries contains an ID and a rank.
You want to get the most popular masteries by grouping by both ID and rank as this constitutes a configuration. Finally, you want to order the list with the most frequently occurring configuration first. Try this:
var configurations = MasteryList.SelectMany(s => s)
.GroupBy(gp => new {gp.ID, gp.Rank} )
.OrderByDescending(g => g.Count())
.Select(x => new {
Id = x.Key.ID,
rank = x.Key.Rank,
count = x.Count()
})
.ToList();

How to group and merge/flatten a list of anonymous objects in LINQ

I have a list of anonymous objects generated by a LINQ query that I do not have access to modify.
The objects have the following properties:
OrderId, RepId, FirstName, LastName, Address
Each "Rep" often places multiple orders, so there are a lot of rows where the only difference is the OrderId. There is a requirement that if the same Rep has placed multiple orders, to batch these together in groups of 6 with a new structure:
OrderId1, OrderId2, ..., OrderId6, RepId, FirstName, LastName, Address
But if the rep has placed say 8 orders, there would be a batch of 6 and a batch of 2. So the new objects don't always have the same number of properties.
I've started by grouping the initial result set by RepId, but I have no clue where to go next.
Is this possible using LINQ?
As your output have anonymous objects with different schema, that make the thing a little more complicate.
Ideally you should design your entity class to use list for orders instead of property like "OrderId1", "OrderId2"... That is not extensible and error prone. But for that specific question, we can combine LINQ and ExpandoObject to achieve this.
orders.GroupBy(order => order.RepId)
.SelectMany(orderGroup => orderGroup.Select((order, i) => new {
Order = order,
ReqId = orderGroup.Key,
SubGroupId = i / 6
}))
.GroupBy(h => new {
ReqId = h.ReqId,
SubGroupId = h.SubGroupId,
FirstName = h.Order.FirstName,
LastName = h.Order.LastName,
Address = h.Order.Address
})
.Select(orderWithRichInfo => {
dynamic dynamicObject = new ExpandoObject();
int i = 1;
foreach(var o in orderWithRichInfo)
{
((IDictionary<string, object>)dynamicObject).Add("OrderId" + i, o.Order.OrderId);
i++;
}
((IDictionary<string, object>)dynamicObject).Add("FirstName", orderWithRichInfo.Key.FirstName);
((IDictionary<string, object>)dynamicObject).Add("LastName", orderWithRichInfo.Key.LastName);
((IDictionary<string, object>)dynamicObject).Add("Address", orderWithRichInfo.Key.Address);
return dynamicObject;
});
Hope it helps.
First option.
If you want to get 6 OrderId-s as a list, you can create
class OrderBundle
{
public int RepId { get; set; }
public List<int> OrderIds { get; set; }
}
Group your items:
var orderBundels = orderList
.GroupBy(m => m.RepId)
.Select(g => new OrderBundle
{
RepId = g.Key,
OrderIds = g.Select(m => m.OrderId).ToList()
});
And then split them into groups:
List<OrderBundle> dividedOrderBundels = new List<OrderBundle>();
foreach (OrderBundle orderBundle in orderBundels)
{
int bundelCount = (int)Math.Ceiling(orderBundle.OrderIds.Count() / 6.0);
for (int i = 0; i < bundelCount; i++)
{
OrderBundle divided = new OrderBundle
{
RepId = orderBundle.RepId,
OrderIds = orderBundle.OrderIds.Skip(i * 6).Take(6).ToList()
};
dividedOrderBundels.Add(divided);
}
}
Second option:
You can achieve the same result without creating model like below:
var result = orderList
.GroupBy(m => m.RepId)
.SelectMany(g => g.Select((m, i) => new
{
RepId = g.Key,
FirstName = m.FirstName,
LastName = m.LastName,
Address = m.Address,
OrderId = m.OrderId,
BunleIndex = i / 6
}))
.GroupBy(m => m.BunleIndex)
.Select(g => new
{
RepId = g.Select(m => m.RepId).First(),
FirstName = g.Select(m => m.FirstName).First(),
LastName = g.Select(m => m.LastName).First(),
Address = g.Select(m => m.Address).First(),
OrderIds = g.Select(m => m.OrderId).ToList()
})
.ToList()

Get data by filtering and grouping LINQ query

Could someone help me to write a code to get data from grouped and filtered query?
Data are from simple datatable, and what I need is how to solve a problem if user enter only one of search param, but also have a possibility to enter more param?
public class Journal
{
public int ID {get; set;}
public string Field1 { get; set; }
public string Field2 { get; set; }
}
/*variables entered bu user:
searchParam1
searchParam2
...
searchParamN
*/
using (var dbContext = new databaseContext())
{
var serchresult = dbContext.Journals
.Where(p => p.Field1.StartsWith(SearchParam1) &&
p.Field2.StartsWith(SearchPParam2))
.GroupBy(f => f.ID)
.ToList();
}
To get filtered data I'v tried:
result = from tr in dbContext.Journals select tr;
if (!String.IsNullOrEmpty(SearchParam1)) {
result = result.Where(tr => tr.Field1.StartsWith(SearchParam1));
}
if (!String.IsNullOrEmpty(SearchParam2)) {
result = result.Where(tr => tr.Field2.StartsWith(SearchParam2));
}
But I need to add grouping :(
You are almost there. Split your query in two parts. In the first part do the dynamic filtering and in the second part do the rest.
var source = dbContext.Journals.AsQueryble();
if (!string.IsNullOrEmpty(SearchParam1))
source = source.Where(tr => tr.Field1.StartsWith(SearchParam1));
if (!string.IsNullOrEmpty(SearchParam2))
source = source.Where(tr => tr.Field2.StartsWith(SearchParam2));
var serchresult = source
.GroupBy(f => f.ID)
.ToList();
You could simplify like this.
result = dbContext.Journals.ToList();
result.Where(tr => ((!String.IsNullOrEmpty(SearchParam1) && tr.Field1.StartsWith(SearchParam1))
|| ( !String.IsNullOrEmpty(SearchParam2) && tr.Field2.StartsWith(SearchParam2)))
.GroupBy(f => f.ID)
.SelectMany(g => g.Select(x=> new { ID = g.Key.ID, x.Field1, x.Field2 }))
.ToList();

Select All distinct values in a column using LINQ

I created a Web Api in VS 2012.
I am trying to get all the value from one column "Category", that is all the unique value, I don't want the list to be returned with duplicates.
I used this code to get products in a particular category. How do I get a full list of categories (All the unique values in the Category Column)?
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAllProducts().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
To have unique Categories:
var uniqueCategories = repository.GetAllProducts()
.Select(p => p.Category)
.Distinct();
var uniq = allvalues.GroupBy(x => x.Id).Select(y=>y.First()).Distinct();
Easy and simple
I have to find distinct rows with the following details
class : Scountry
columns: countryID, countryName,isactive
There is no primary key in this. I have succeeded with the followin queries
public DbSet<SCountry> country { get; set; }
public List<SCountry> DoDistinct()
{
var query = (from m in country group m by new { m.CountryID, m.CountryName, m.isactive } into mygroup select mygroup.FirstOrDefault()).Distinct();
var Countries = query.ToList().Select(m => new SCountry { CountryID = m.CountryID, CountryName = m.CountryName, isactive = m.isactive }).ToList();
return Countries;
}
Interestingly enough I tried both of these in LinqPad and the variant using group from Dmitry Gribkov by appears to be quicker. (also the final distinct is not required as the result is already distinct.
My (somewhat simple) code was:
public class Pair
{
public int id {get;set;}
public string Arb {get;set;}
}
void Main()
{
var theList = new List<Pair>();
var randomiser = new Random();
for (int count = 1; count < 10000; count++)
{
theList.Add(new Pair
{
id = randomiser.Next(1, 50),
Arb = "not used"
});
}
var timer = new Stopwatch();
timer.Start();
var distinct = theList.GroupBy(c => c.id).Select(p => p.First().id);
timer.Stop();
Debug.WriteLine(timer.Elapsed);
timer.Start();
var otherDistinct = theList.Select(p => p.id).Distinct();
timer.Stop();
Debug.WriteLine(timer.Elapsed);
}

Categories

Resources