I have many Object1A, say IEnumerable<Object1A>.
public class Object1A {
public string text;
public datetime date;
public decimal percent;
public Object3 obj;
}
Many of these objects have the same text, date, and percent, but have a different obj. I want to transform the list such that the output will be a IEnumerable<Object1B> where
public class Object1B{
public string text;
public datetime date;
public decimal percent;
public IEnumerable<Object3> objs;
}
My current apporach is a bit clunky, and listed below
IEnumerable<Object1A> a = GetSomeConstruct();
var lookup = a.ToLookup( t => t.text);
var b = new List<Object1b>();
foreach(var group in lookup){
var itemA = group.first();
var itemB = new Object1b(){
text = itemA.text,
date = itemA.date,
percent = itemA.percent
};
itemB.objs = pair.Select(t => t.obj);
b.Add(itemB);
}
Can this approach be refined? It doesn't seem to run to slow, but it seems like it could be better. I'm looking for a more terse approach if possible.
edit: yeah, this was a dumb question, cudos to the downvote....
simple answer
var b_objects = a_objects.GroupBy(t => new {t.Text})
.Select( t => new Object1B
{ Text = t.Key.Text,
Percent = t.First().Percent,
Date = t.First().Date,
Objs = t.Select( o => o.Obj).ToList()
});
Guess you want something like this?
var b = from a in GetSomeConstruct()
group a.obj by new { a.text, a.date, a.percent } into grp
select new Object1B
{
text = grp.Key.text,
date = grp.Key.date,
percent = grp.Key.percent,
objs = grp
};
You can use anonymous types with join and group by. Their GetHashCode and Equals overloads operate on each member.
Related
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();
I would like to sum one column in list collection based on another column.
This is how class looks like
Date Payment
2015-09-09 500
2015-09-09 200
2017-01-03 150
2017-01-03 300
Result should be like this
Date Payment
2015-09-09 700
2017-01-03 450
Code:
var result = from e in scheduleDetails
let k = new
{
Date = e.ActualDueDate
}
group e by k into t
select new
{
Date = t.Key.Date,
Payment = t.Sum(e => e.EscrowPayment)
};
List<ScheduleDetails> temp = result.Select(t => new ScheduleDetails(t.Date, t.Payment)).ToList();
I do not want the anonymous type to be created hence I map it to the original object. For this, I had to create the constructor.
public ScheduleDetails(DateTime? date, decimal? payment)
{
this.ActualDueDate = date;
this.EscrowPayment = payment;
}
Questions:
Is this the best way to achieve this?
I want resultant object to be same as input object only
You don't need to create new range variable and anonymous type:
var temp = scheduleDetails.GroupBy(sd => sd.ActualDueDate)
.Select(g => new ScheduleDetails(g.Key, g.Sum(sd => sd.EscrowPayment)))
.ToList();
Also if ScheduleDetails class has public setters for it's ActualDueDate and EscrowPayment properties, you can use object initializer without adding constructor:
.Select(g => new ScheduleDetails {
ActualDueDate = g.Key,
EscrowPayment = g.Sum(sd => sd.EscrowPayment)
})
Query syntax (note that temp will be IEnumerable<ScheduleDetails> here):
var temp = from sd in scheduleDetails
group sd by sd.ActualDueDate into g
select new ScheduleDetails {
ActualDueDate = g.Key,
EscrowPayment = g.Sum(sd => sd.EscrowPayment)
};
I am making a group by linq statement where i convert a single list of data into an list with a nested list. Here is my code so far:
[TestMethod]
public void LinqTestNestedSelect2()
{
// initialization
List<combi> listToLinq = new List<combi>() {
new combi{ id = 1, desc = "a", name = "A", count = 1 },
new combi{ id = 1, desc = "b", name = "A", count = 2 },
new combi{ id = 2, desc = "c", name = "B", count = 3 },
new combi{id = 2, desc = "d", name = "B", count = 4 },
};
// linq group by
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = (from r in obj select new B() { des = r.des, count = r.count }).ToList() }).ToList();
// validation of the results
Assert.AreEqual(2, result.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
}
public class A
{
public int id;
public string name;
public List<B> descriptions;
}
public class B
{
public int count;
public string des;
}
public class combi
{
public int id;
public string name;
public int count;
public string desc;
}
This is fine if the objects are small like the example. However I will implement this for objects with a lot more properties. How can I efficiently write this statement so I don't have to write field names twice in my linq statement?
I would like to return the objects in the statement and I want something like:
// not working wishfull thinking code
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new (A){ this = obj.key , descriptions = obj.ToList<B>()}).ToList();
Background: I am re writing a web api that retrieves objects with nested objects in a single database call for the sake of db performance. It's basically a big query with a join that retrieves a crap load of data which I need to sort out into objects.
probably important: the ID is unique.
EDIT:
based on the answers so far I have made a solution which sort of works for me, but is still a bit ugly, and I would want it to be better looking.
{
// start part
return (from row in reader.AsEnumerable()
group row by row.id into grouping
select CreateA(grouping)).ToList();
}
private static A CreateA(IGrouping<object, listToLinq> grouping)
{
A retVal = StaticCreateAFunction(grouping.First());
retVal.descriptions = grouping.Select(item => StaticCreateBFunction(item)).ToList();
return ret;
}
I hope the StaticCreateAFunction is obvious enough for what it does. In this scenario I only have to write out each property once, which is what I really wanted. But I hope there is a more clever or linq-ish way to write this.
var result = (from row in listToLinq
group new B { des = row.desc, count = row.count } by new A { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = obj.ToList() }).ToList();
You can add to each of the A and B classes a constructor that receives a combi and then it takes from it only what it needs. For example for a:
public class A
{
public A(combi c)
{
id = c.id;
name = c.name;
}
}
public class B
{
public B(combi c)
{
count = c.count;
des = c.desc;
}
}
Then your query can look like:
var result = (from row in listToLinq
group row by new { row.id, row.name } into grouping
select new A(grouping.First())
{
descriptions = grouping.Select(item => new B(item)).ToList()
}).ToList();
If you don't like the grouping.First() you can then override Equals and GetHashCode and then in the group by do by a new a with the relevant fields (which will be those in the Equals) and then add a copy constructor from a
Another way, in which you decouple the A/B classes from the combi is to extract the convert logic to a collection of static methods.
I have items with the properties :
public int ClientId {get;set;}
public DateTime StartDateTime{get;set;}
public DateTime EndDateTime{get;set;}
And I want to calculate the total of the difference between all the datetimes of each client with group by , but this :
var retVal = (from t items group t by ClientId into z
select new
{
ClientId = z.Key,
TimeSpanClientTotal = z.Sum(h => (h.EndDateTime - h.StartDateTime))
}).ToList();
Doesn't work since Sum doesn't work well for TimeSpan , which is the return value of the difference between two DateTimes object .
Any suggestion how can I get the total TimeSpan of each client ?
Thanks
Enumerable.Sum is just an extension method you call on an IEnumerable. There is nothing special to it so you can easily create another extension method that sums timespans:
static class TimeSpanExtensions
{
public static TimeSpan Sum<TSource>(this IEnumerable<TSource> enumerable,
Func<TSource,TimeSpan?> func )
{
return enumerable.Aggregate(TimeSpan.Zero, (total, it) =>
total+=(func(it)??TimeSpan.Zero);
}
}
Assuming your class definition is
class Record
{
public int ClientId { get; set; }
public DateTime StartDateTime { get; set; }
public DateTime EndDateTime { get; set; }
public Record(int clientId, DateTime startDateTime, DateTime endDateTime)
{
ClientId = clientId;
StartDateTime = startDateTime;
EndDateTime = endDateTime;
}
}
You can write the same code you would for the numeric types:
var items = new[] {
new Record(1, DateTime.Now, DateTime.Now.AddHours(1)),
new Record(1, DateTime.Now, DateTime.Now.AddHours(1)),
new Record(1, DateTime.Now, DateTime.Now.AddHours(1))};
var total=items.Sum(h=>(h.EndDateTime-h.StartDateTime));
var grouped= (from t in items
group t by t.ClientId into z
select new
{
ClientId = z.Key,
TimeSpanClientTotal = z.Sum(h => (h.EndDateTime - h.StartDateTime))
}).ToList();
You can also use Enumerable.Aggregate directly:
var total= items.Aggregate(TimeSpan.Zero, (current, it) =>
current += (it.EndDateTime-it.StartDateTime));
The code can be uglier but you can do a lot more than simple addition.
You can use the .TotalMilliseconds Property
var retVal = (from t items group t by ClientId into z
select new
{
ClientId = z.Key,
TimeSpanClientTotal = z.Sum(h => (h.EndDateTime - h.StartDateTime).TotalMilliseconds)
}).ToList();
You can write it like that:
h.EndDateTime.Subtract(h.StartDateTime).TotalDays();
In my case, all proposed solutions didn't work. They caused error related with LINQ restrictions. So, my workaround looks like this:
var appointments = (from apps in DbContext.ClientAppointments
where apps.StartDate.Value.Date == date.Date
select new
{
SpecialistId = apps.SpecialistId,
Duration = (apps.EndDate.Value - apps.StartDate.Value).TotalSeconds
}).ToList();
var result = (from apps in appointments
group apps by apps.SpecialistId into g
select new AppointmentsDurationDailyDto
{
SpecialistId = g.Key ?? 0,
Date = date.Date,
Duration = g.Sum(apps => apps.Duration)
}).ToList();
In this solution first .ToList(); is important to make next grouping statement on client. In case if you need to get TimeSpan Duration you can easily convert it back by using TimeSpan.FromSeconds(Duration)
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);
}