Union of 2 list by GroupBy - c#

I have two lists with 1 columns Currency and Total like
list1
Currency Total
USD 10
EUR 25
list2
Currency Total
USD 10
EUR 25
INR 55
How can i get an overall Sum from the two lists in a single list grouped on currency basis like this
ListSummary
Currency Total
USD 20
EUR 50
INR 55
The following Linq code is generating the 2 lists for me
var license_sum = licenses.GroupBy(x => x.Currency,
(key, values) => new {
Currency = key,
Total = values.Sum(x => x.FeesCustom == 0 ? x.FeesNormal : x.FeesCustom)
});
var trans_sum = translations.GroupBy(x => x.Currency,
(key, values) => new {
Currency = key,
Total = values.Sum(x => x.FeesCustom == 0 ? x.FeesNormal : x.FeesCustom)
});
From these 2 i am planning for overool_sum list

You could use Concat linq extension to concat both lists and then use GroupBy to group on currecy..
var overool_sum = license_sum.Concat(trans_sum )
.GroupBy(x => x.Currency)
.Select(x=> new
{
Currency = x.Key,
Total = x.Sum(x => x.Total)
})
.ToList();

var overool_sum = licenses.Concat(translations).GroupBy(x => x.Currency,
(key, values) => new {
Currency = key,
Total = values.Sum(x => x.FeesCustom == 0 ? x.FeesNormal : x.FeesCustom)
});
Provided that they are the same types - otherwise just .Select a common type first :-)

This should work for you:
class MyClass {
public string Currency { get; set; }
public decimal Total { get; set; }
}
void Main() {
var list1 = new List<MyClass>(){
new MyClass{ Currency = "USD" , Total = 10},
new MyClass{ Currency = "EUR" , Total = 25},
};
var list2 = new List<MyClass>(){
new MyClass{ Currency = "USD" , Total = 10},
new MyClass{ Currency = "EUR" , Total = 25},
new MyClass{ Currency = "INR" , Total = 55},
};
var list3 = list1.Concat(list2);
var list4 = list3.GroupBy(x => x.Currency).Select(y => new MyClass {
Currency = y.Key,
Total = y.Sum(z => z.Total)
});
Console.WriteLine(list4);
}

Related

Combine multiple lists into one list and order by amount

Combine multiple lists into one list and order by amount
I have a class like this
public class PriceList
{
public string Name { get; set; }
public int Amount { get; set; }
public int Price { get; set; }
}
First list. It's called VOne
Name Amount Price
P5 5000 6
P10 10000 10
P20 20000 20
Second list. It's called VTwo
Name Amount Price
P5 5000 5
P10 10000 10
P15 15000 15
P20 20000 21
Third list. It's called VThree
Name Amount Price
P1 1000 1
P10 10000 9
P20 20000 19
I want the result like this
Name Amount VOne VTwo VThree
P1 1000 0 0 1
P5 5000 6 5 0
P10 10000 10 10 9
P15 15000 0 15 0
P20 20000 20 21 19
This is what I try. It works but seems sophisticated. I need the simpler way to do this. Linq will be prefered but I have no idea how to use it.
static void Test()
{
var VOne = new List<PriceList>(new[]
{
new PriceList { Name = "P5", Amount = 5000, Price = 6},
new PriceList { Name = "P10", Amount = 10000 , Price = 10},
new PriceList { Name = "P20", Amount = 20000, Price = 20}
});
var VTwo = new List<PriceList>(new[]
{
new PriceList { Name = "P5", Amount = 5000, Price = 5},
new PriceList { Name = "P10", Amount = 10000 , Price = 10},
new PriceList { Name = "P15", Amount = 10000 , Price = 15},
new PriceList { Name = "P20", Amount = 20000, Price = 21}
});
var VThree = new List<PriceList>(new[]
{
new PriceList { Name = "P1", Amount = 5000, Price = 1},
new PriceList { Name = "P10", Amount = 10000 , Price = 9},
new PriceList { Name = "P20", Amount = 20000, Price = 19}
});
var prices = new List<PriceListResult>();
foreach (var m in VOne)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
foreach (var m in VTwo)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
foreach (var m in VThree)
{
var exist = prices.Any(x => x.Name == m.Name && x.Amount == m.Amount);
if (!exist)
prices.Add(new PriceListResult { Name = m.Name, Amount = m.Amount });
}
prices = prices.OrderBy(x => x.Name).ThenBy(x => x.Amount).ToList();
foreach (var price in prices)
{
var v1 = VOne.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v1 != null)
price.VOne = v1.Price;
var v2 = VTwo.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v2 != null)
price.VTwo = v2.Price;
var v3 = VThree.FirstOrDefault(x => x.Name == price.Name && x.Amount == price.Amount);
if (v3 != null)
price.VThree = v3.Price;
}
}
public class PriceListResult
{
public string Name { get; set; }
public int Amount { get; set; }
public int VOne { get; set; }
public int VTwo { get; set; }
public int VThree { get; set; }
}
My advice would be that you spend some time get acquainted to the standard LINQ methods
You can use Enumerable.Concat to put your three sequences into one sequence, then you can use one of the overloads of Enumerable.GroupBy to make groups of PriceLists that have the same amount. User parameter resultSelector to create one object per Amount and PriceLists with this Amount.
The problem is, that if you've got a PriceList, you can't see whether it is a PriceList of vOne, vTwo or vThree. So from every Price we'll have to remember the Amount, the Price and from which priceList the data originated.
I'll do it in small steps, so it is easy to see what is done. If you want, you can put it in one big LINQ. As all statements use delayed execution, this will not improve performance, however one big LINQ will deteriorate readability.
var vOne = VOne.Select(priceList => new
{
Id = 1,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
var vTwo = Select(priceList => new
{
Id = 2,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
vThree = Select(priceList => new
{
Id = 3,
Name = priceList.Name,
Amount = priceList.Amount,
Price = priceList.Price,
});
var allPriceLists = vOne.Concat(vTwo).Concat(vThree);
Make groups of sequences that have the same value for the combination [Name, Amount]. Use parameter resultSelector to create one object per group
var result = allPriceLists.GroupBy(priceList => new {priceList.Name, priceList.Amount},
// parameter resultSelector: use every [Name, Amount] combination,
// with all priceLists that have this combination to make one new:
(nameAmountCombinatin, priceListsWithThisCombination) => new
{
Name = nameAmountCombination.Name,
Amount = nameAmountCombination.Amount,
Vone = priceListsWithThisCombination
.Where(priceList => priceList.Id == 1)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
Vtwo = priceListsWithThisCombination
.Where(priceList => priceList.Id == 2)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
Vthree = priceListsWithThisCombination
.Where(priceList => priceList.Id == 3)
.Select(priceList => priceList.Amount)
.FirstOrDefault(),
})
Note: I assume that in Vone (etc.) doesn't have two PriceLists with the same [name, amount] combination. If you allow that Vone has two elements with [P5, 5000], consider to Sum the Amounts
If there is a missing Amount in one of the original PriceLists, you will get the default value for Price: 0.
make class like this:
public class PriceList
{
public string Name { get; set; }
public int Amount { get; set; }
public int Price { get; set; }
public string type {get;set;}
}
Then:
var list1Typed = List1.ForEach(f => f.type = "VOne");
var list2Typed = List2.ForEach(f => f.type = "VTwo");
var list3Typed = List3.ForEach(f => f.type = "VThree");
var all = list1Typed.Union(list2Typed).Union(list3Typed).ToList();
var allComplete = all.GroupBy(g => g.Name).Select(s =>
new PriceListResult(){
Name = g.Key,
Amount = g.First().Amount,
VOne = g.Any(a => a.Type == "VOne") ? g.Where(w => w.Type =="VOne").Sum(s => s.Amount) : 0,
VTwo = g.Any(a => a.Type == "VTwo ") ? g.Where(w => w.Type =="VTwo").Sum(s => s.Amount) : 0,
VThree = g.Any(a => a.Type == "VThree") ? g.Where(w => w.Type =="VThree").Sum(s => s.Amount) : 0
}).OrderBy(o => o.Amount).ToList();
Try this:
var all = list1.Union(list2).Union(list3).ToList();
then:
var grouped = all.GroupBy(p => new {
p.Name,
p.Amount
});
finally:
var results = grouped.Select(p => new {
p.Name,
p.Amount,
Price = string.Join(", ", p.Select(x => x.Price))
}).ToList().OrderBy(item => item.Amount);
var finalResult = results.Select(item => new PriceListResult {
Name = item.Name,
Amount = item.Amount ,
VOne = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[0],
VTwo = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[1],
VThree = Price.Split(new[] { ','},StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray()[2]
}).ToList();
I've edited Kai's answer and it works
var results = list1.Union(list2).Union(list3)
.GroupBy
(
p => p.Name,
p => p,
(key, list) => new { Name = key, Amount = list.First().Amount, List = list }
)
.Select(s => new
{
Name = s.Name,
Amount = s.Amount,
VOne = list1.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault(),
VTwo = list2.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault(),
VThree = list3.Where(x => x.Name == s.Name && x.Amount == x.Amount).Select(x => x.Price).FirstOrDefault()
})
.OrderBy(o => o.Amount).ToList();

Linq group by Sum but also return 0 if sum is null

I have the following type:
class ItemTotals
{
public decimal Total { get; set; }
public string ItemType { get; set; }
}
I am getting the totals for each ItemType using this:
var items = new List<ItemTotals>
{
new ItemTotals
{
Total = 10,
ItemType = "X"
},
new ItemTotals
{
Total = 10,
ItemType = "X"
},
new ItemTotals
{
Total = -5,
ItemType = "Y"
},
new ItemTotals
{
Total = -20,
ItemType = "Y"
}
};
List<ItemTotals> query = items.GroupBy(x => x.ItemType)
.Select(x => new ItemTotals
{
ItemType = x.Key,
Total = x.Sum(z => z.Total)
}).ToList();
This gives me total for X as 20 and Total Y as -25. But suppose if all of the itemtypes are X then I get only one result with total as -5 for X and no result for Total Y which I want as 0, is this possible?
Thanks
Add these lines after the list initialization
items.Add(new ItemTotals {Total = 0, ItemType = "X"});
items.Add(new ItemTotals {Total = 0, ItemType = "Y"});
One way would be to prepend a collection of known names paired with zeros to the collection, like this:
var[] names = new {"X", "Y", "Z"};
List<ItemTotals> query = items
.Concat(names.Select(n => new ItemTotals {Total = 0, ItemType = n}))
.GroupBy(x => x.ItemType)
... // And so on
The Concat ensures that there is at least one item for each name, and that the total for that item is zero. In a way, the added items serve as "sentinels": if an item name is there, they make no difference, but if the name is not there, they ensure that a line with zero is added to the result.
You can find the types which are not included in the list and add them inside loop:
List<string> itemTypes = new List<string>() {"X", "Y", "Z"};
foreach(var type in itemTypes)
{
if(!items.Any(x => x.ItemType == type)
items.Add(new ItemTotals() {ItemType = type, Total = 0})
}
List<ItemTotals> query = items.GroupBy(x => x.ItemType)....

join multiple IEnumerable<> using Linq

I have following class which is populated with the data
public class cntrydata
{
public string countryid { get; set; }
public string countryname { get; set; }
public IEnumerable<Data> data { get; set; }
}
public class Data
{
public int year { get; set; }
public float value { get; set; }
}
I have an IEnumerable result which has data like this:
IEnumerable<cntryData> result
USA
United States
2000 12
2001 22
2004 32
CAN
Canada
2001 29
2003 22
2004 24
I want to evaluate "result" object using LINQ to get following result:
2000 USA 12 CAN null
2001 USA 22 CAN 29
2003 USA null CAN 22
2004 USA 32 CAN 24
Also if result has more countries (say China with 1995 value 12) then result should look like this:
1995 USA null CAN null CHN 12
2000 USA 12 CAN null CHN null
2001 USA 22 CAN 29 CHN null
2003 USA null CAN 22 CHN null
2004 USA 32 CAN 24 CHN null
Can this be done using LINQ? Thank you.
I found it surprisingly hard to come up with a clean answer on this one, and I am still not really satisfied, so feedback is welcome:
var countries = result.Select(x => x.countryid).Distinct();
var years = result.SelectMany(x => x.data).Select(x => x.year).Distinct();
var data = result.SelectMany(x => x.data
.Select(y => new { Country = x.countryid,
Data = y }))
.ToDictionary(x => Tuple.Create(x.Country, x.Data.year),
x => x.Data.value);
var pivot = (from c in countries
from y in years
select new { Country = c, Year = y, Value = GetValue(c, y, data) })
.GroupBy(x => x.Year)
.OrderBy(x => x.Key);
public float? GetValue(string country, int year,
IDictionary<Tuple<string, int>, float> data)
{
float result;
if(!data.TryGetValue(Tuple.Create(country, year), out result))
return null;
return result;
}
pivot will contain one item per year. Each of these items will contain one item per country.
If you want to format each line as a string, you can do something like this:
pivot.Select(g => string.Format("{0} {1}", g.Key, string.Join("\t", g.Select(x => string.Format("{0} {1}", x.Country, x.Value)))));
Update
Here is now you use the code below to make a data table:
var newresult = result.SelectMany(cntry => cntry.data.Select(d => new { id = cntry.countryid, name = cntry.countryname, year = d.year, value = d.value }))
.GroupBy(f => f.year)
.Select(g => new { year = g.Key, placeList = g.Select(p => new { p.id, p.value })});
DataTable table = new DataTable();
table.Columns.Add("Year");
foreach(string name in result.Select(x => x.countryid).Distinct())
table.Columns.Add(name);
foreach(var item in newresult)
{
DataRow nr = table.NewRow();
nr["Year"] = item.year;
foreach(var l in item.placeList)
nr[l.id] = l.value;
table.Rows.Add(nr);
}
table.Dump();
And how that looks:
This is what linq can do, you could transform this to a data table easy enough, a list by year of locations and their values.
Flatten the input and then group by. Select what you want. Like this
var newresult = result.SelectMany(cntry => cntry.data.Select(d => new { id = cntry.countryid, name = cntry.countryname, year = d.year, value = d.value }))
.GroupBy(f => f.year)
.Select(g => new { year = g.Key, placeList = g.Select(p => new { p.id, p.value })});
Here is what the dump looks like in LinqPad.
Here is the full test code
void Main()
{
List<cntrydata> result = new List<cntrydata>()
{
new cntrydata() { countryid = "USA", countryname = "United States",
data = new List<Data>() {
new Data() { year = 2000, value = 12 },
new Data() { year = 2001, value = 22 },
new Data() { year = 2004, value = 32 }
}
},
new cntrydata() { countryid = "CAN", countryname = "Canada",
data = new List<Data>() {
new Data() { year = 2001, value = 29 },
new Data() { year = 2003, value = 22 },
new Data() { year = 2004, value = 24 }
}
}
};
var newresult = result.SelectMany(cntry => cntry.data.Select(d => new { id = cntry.countryid, name = cntry.countryname, year = d.year, value = d.value }))
.GroupBy(f => f.year)
.Select(g => new { year = g.Key, placeList = g.Select(p => new { p.id, p.value })});
newresult.Dump();
}
public class cntrydata
{
public string countryid { get; set; }
public string countryname { get; set; }
public IEnumerable<Data> data { get; set; }
}
public class Data
{
public int year { get; set; }
public float value { get; set; }
}
//group things up as required
var mainLookup = result
.SelectMany(
country => country.data,
(country, data) => new {
Name = country.Name,
Year = data.Year,
Val = data.Val
}
)
.ToLookup(row => new {Name= row.Name, Year = row.Year}
List<string> names = mainLookup
.Select(g => g.Key.Name)
.Distinct()
.ToList();
List<string> years = mainLookup
.Select(g => g.Key.Year)
.Distinct()
.ToList();
// generate all possible pairs of names and years
var yearGroups = names
.SelectMany(years, (name, year) => new {
Name = name,
Year = year
})
.GroupBy(x => x.Year)
.OrderBy(g => g.Key);
IEnumerable<string> results =
(
from yearGroup in yearGroups
let year = yearGroup.Key
//establish consistent order of processing
let pairKeys = yearGroup.OrderBy(x => x.Name)
let data = string.Join("\t",
from pairKey in pairKeys
//probe original groups with each possible pair
let val = mainLookup[pairKey].FirstOrDefault()
let valString = val == null ? "null" : val.ToString()
select pairKey.Name + " " + valString
)
select year.ToString() + "\t" + data; //resultItem

Compare members of a type - calculate the difference between members

I have the following type:
public class Parts
{
public string PartNo { get; set; }
public decimal Price { get; set; }
}
what I would like to do is to compare the price or each part with the cheapest one and display the difference in percentage.
This is what I have tried so far and it works:
var part1 = new Part {PartNo = "part1", Price = 10};
var part2 = new Part {PartNo = "part1", Price = 8};
var part3 = new Part {PartNo = "part1", Price = 12};
var parts = new List<Part> {part1, part2, part3};
var list = from p in parts
orderby p.Price ascending
select p;
var sb = new StringBuilder();
var counter = 0;
decimal firstPrice=0;
foreach (Part p in list)
{
if (counter == 0)
{
firstPrice = p.Price;
}
sb.Append(p.PartNo + ": " + p.Price + "," + ((p.Price/firstPrice)-1)*100 + Environment.NewLine);
counter++;
}
Console.WriteLine(sb.ToString(), "Parts List");
This outputs the following:
part1: 8, 0
part1: 10, 25.00
part1: 12, 50.0
This shows the price increase for each each part, and that is what I am trying to achieve but I was wondering is there a better way of calculating the price difference in percentage (e.g. with a LINQ query) or in any other way.
Thanks
I would calculate the difference as first step.
var cheapestPrice = parts.Min(p => p.Price);
var list = parts.Select(p => new {
Part = p,
DiffPercentage = ((p.Price - cheapestPrice) / cheapestPrice) * 100
});
foreach (var p in list)
Console.WriteLine("{0}: {1},{2}%", p.Part.PartNo, p.Part.Price, p.DiffPercentage);
// list defined as sorted by price ascending as per the code
var list = parts.OrderBy(p => p.Price); // less verbose way of saying the same
var firstPrice = list.First().Price;
var differences = list.Skip(1).Select(s => new {Part = s, PercentageDiff = (s.Price/firstPrice - 1)*100});
The .Skip(1) is optional. You may not want to compare cheapest price to itself.
Tim beat me to it, but use the select to create your string
void Main()
{
var part1 = new Part {PartNo = "part1", Price = 10};
var part2 = new Part {PartNo = "part1", Price = 8};
var part3 = new Part {PartNo = "part1", Price = 12};
var parts = new List<Part> {part1, part2, part3};
var lowest = parts.Min(p => p.Price );
var result = parts.Select (p => string.Format("Part #:{0} {1} -> {2}", p.PartNo, p.Price, ((p.Price/lowest)-1)*100 ));
result.ToList()
.ForEach(rs => Console.WriteLine (rs));
/*
Part #:part1 10 -> 25.00
Part #:part1 8 -> 0
Part #:part1 12 -> 50.0
*/
}
// Define other methods and classes here
public class Part
{
public string PartNo { get; set; }
public decimal Price { get; set; }
}

How to write a LINQ query combining group by and aggregates?

Given the following input, how do I write a LINQ query or expression to return an aggregated result set for the quantity?
Input:
var foo = new[] { new { PO = "1", Line = 2, QTY = 0.5000 },
new { PO = "1", Line = 2, QTY = 0.2500 },
new { PO = "1", Line = 2, QTY = 0.1000 },
new { PO = "1", Line = 2, QTY = -0.1000 }
}.ToList();
Desired result:
Something along the lines of
new { PO = "1", Line = 2, QTY = 0.7500 } // .5 + .25 + .1 + -.1
How would I write it for multiple lines as well (see the object model in foo)?
How about this:
var result = foo.GroupBy(x => x.Line)
.Select(g => new { PO = g.First().PO,
Line = g.Key,
QTY = g.Sum(x => x.QTY) });
In the case you just have one Line, just add a .Single() - result is an IEnumerable of the anonymous type defined when you set up foo.
Edit:
If both PO and Line should designate different groups (PO can have different values), they both have to be part of the group key:
var result = foo.GroupBy(x => new { x.PO, x.Line})
.Select(g => new {
PO = g.Key.PO,
Line = g.Key.Line,
QTY = g.Sum(x => x.QTY)
});
var query = (from t in foo
group t by new {t.PO, t.Line}
into grp
select new
{
grp.Key.PO,
grp.Key.Line,
QTY = grp.Sum(t => t.QTY)
}).ToList()

Categories

Resources