Using Linq to extract data from a Table - c#

I have a table as shown in the picture below. I need to extract a list from the table where it lists it on the basis of the country. And there is no repetition of the country. So for example, China would appear only once, its total pay would be added, and its total Debt would be added and all other column would be added respectively. So It should look like something below:
Country --- Totay Pay---Total Debt--- NetAmount
China-- (3.1+4.2)B--- (2.0+2.0)B----(8+4)B
India--
Russia---
I would prefer if we can use LINQ expressions. I tried using GroupBy and other operators but can't get my head around.

GroupBy operator is good direction - you should group by country and sum other values using one of its overloads that:
Groups the elements of a sequence according to a specified key selector function and creates a result value from each group and its key.
Assuming data like:
var data = new List<Data>()
{
new Data() { Country = "China", Index = 2, TotalPay = 3.1M, TotalDebt = 2.0M, NetAmount = 8.0M},
new Data() { Country = "India", Index = 4, TotalPay = 2.1M, TotalDebt = 3.0M, NetAmount = 9.0M},
new Data() { Country = "China", Index = 8, TotalPay = 5.1M, TotalDebt = 4.0M, NetAmount = 10.0M},
};
you can write:
var results = data.GroupBy(x => x.Country, (key, elements) => new Data()
{
Country = key,
Index = elements.Sum(v => v.Index),
TotalPay = elements.Sum(v => v.TotalPay),
TotalDebt = elements.Sum(v => v.TotalDebt),
NetAmount = elements.Sum(v => v.NetAmount)
});

Related

Updating property values in one list with a property value average of matching items in another list

I have two Lists and need to update a property value of all the items in the 1st list with a property value average of all the matching items in another list.
class transaction
{
public string orderId;
public string parentOrderId;
public int quantity;
public decimal marketPrice;
public decimal fillPrice;
}
List<transaction> makerTransactions = new List<transaction>()
{
new transaction(){
orderId = "1",
parentOrderId = "1",
quantity = 100,
marketPrice = 75.87M,
fillPrice = 75.87M
}
};
List<transaction> takerTransactions = new List<transaction>()
{
new transaction(){
orderId = "2",
parentOrderId = "1",
quantity = 50,
marketPrice = 75.97M,
fillPrice = 75.97M
},
new transaction(){
orderId = "3",
parentOrderId = "1",
quantity = 50,
marketPrice = 75.85M,
fillPrice = 75.85M
}
};
Trying to make this work with LINQ extension methods but cant figure out the correct way.
makerTransactions.All(mt => mt.fillPrice = takerTransactions
.Where(tt => tt.parentOrderId == mt.orderId)
.Average(ta => ta.fillPrice));
try this:
makerTransactions.ForEach(mt => mt.fillPrice = takerTransactions
.Where(tt => tt.parentOrderId == mt.orderId)
.Average(ta => ta.fillPrice));
All is an extension method. It tells you if all the elements in a collection match a certain condition and, apparently, it's not what you need.
To make it more efficient, first create a dictionary and use that to take the averages from:
var priceDictionary = takerTransactions
.GroupBy(tt => tt.parentOrderId)
.ToDictionary(grp => gr.Key, grp => grp.Average(ta => ta.fillPrice));
makerTransactions.ForEach(mt => mt.fillPrice = priceDictionary[mt.orderId]);

Select from one list only those present in another and order by second list - linq

I have two array..
var data1 = new[] {
new { Product = "Product 1", Year = 2009, Sales = 1212 },
new { Product = "Product 2", Year = 2009, Sales = 522 },
new { Product = "Product 1", Year = 2010, Sales = 1337 },
new { Product = "Product 2", Year = 2011, Sales = 711 },
new { Product = "Product 2", Year = 2012, Sales = 2245 },
new { Product = "Product 3", Year = 2012, Sales = 1000 }
};
var data2 = new[] {
new { Product = "Product 1", Year = 2009, Sales = 1212 },
new { Product = "Product 1", Year = 2010, Sales = 1337 },
new { Product = "Product 2", Year = 2011, Sales = 711 },
new { Product = "Product 2", Year = 2012, Sales = 2245 }
};
I want to group data1 by Product and do a sum of the groups by Sales for only those products which are present in data2 and order them the same way as they are in data2. Please note that even if a product is present in data2, all years present in data1 for that product is not present in data2 (eg.{ Product = "Product 2", Year = 2009, Sales = 522 }) and so the grouping and sum will have to happen on data1.
To just do the grouping and sum the below should work..
data1.GroupBy(x=>x.Product)
.Select(x=>new {Product=x.Key,Total= x.Sum(s=>s.Sales)})
But how do I ensure I select only products in data2 and order the result by Product as in data2
I would take a different approach.
Since you want the final result to contain the products from the second list in the same order, I would start with getting Distinct products from the second list.
Although not stated explicitly in the documentation, the Distinct method (similar to GroupBy) yields the distinct elements in the order of the first occurrence of the unique element in the source, hence the result of the Distinct will be the products of the final result in the correct order.
Then I would use GroupJoin to correlate it with the first list, ending up with a quite efficient query:
var result = data2.Select(item => item.Product).Distinct()
.GroupJoin(data1, product => product, item => item.Product, (product, group) =>
new { Product = product, Sales = group.Sum(item => item.Sales) })
.ToList();
You need two things: First, you select the available products from your data2. For this, you can use the overload for Select which also gives the index of the matching element.
Second, you filter your data1 based on the products in data2 and perform the grouping afterwards. As a last step you add a new property CorrespondingIndex which matches the index of the product in data2. This index can be used for ordering your data1 list based on the ordering of products in data2.
var productsWithIndex = data2
.Select(x => x.Product)
.Distinct()
.Select((p, idx) => new {Product = p, Index = idx});
var filteredProducts = data1
.Where(x => productsWithIndex.Select(p => p.Product).Contains(x.Product))
.GroupBy(x => x.Product)
.Select(x => new
{
Product = x.Key,
Total = x.Sum(s => s.Sales),
CorrespondingIndex = productsWithIndex.Single(p => p.Product == x.Key).Index
})
.OrderBy(x => x.CorrespondingIndex);
Pehaps you don't even need to go in that much detail, as user1384848 went.
You might get along with something much simpler as this:
var result =
data1.Where(e => data2.Any(x => x.Product == e.Product))
.GroupBy(arg => arg.Product,
(name, products) => new {Product = name, Total = products.Sum(e => e.Sales)})
.OrderBy(d => d.Product);

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

c# - AutoMapper: how to copy fields from 2 different sources into 1 destination

I have a flat file with a bunch of records, let's say it's a sequence of 2 record types
--- Record1: ID;NAME;SURNAME
--- Record2: AGE;SEX;
Let's call R1 the class representing Record1 and R2 the class representing Record2
In this moment I have an array of R1 and another array of R2
If I have a POCO called Subject that has 5 fields, named exactly as the union of the fields of R1 and R2, how do I configure AutoMapper to do the magic for me?
Now I'm trying this:
var subjects = Mapper.Map<IEnumerable<R1>, List<Subject>>(arrayOfR1s);
Mapper.Map<IEnumerable<R2>, List<Subject>>(arrayOfR2s, subjects);
After the first mapping, I get an array of Subjects, in every element of the array the fields ID, SURNAME, NAME are correctly filled with values. AGE and SEX are left to NULL as expected.
But after the second mapping, all the fields from R1 (ID, NAME, SURNAME) are initialized to NULL and I only get fields from R2 (AGE and SEX).
How do I get the complete union of the fields?
Can someone point me to the right approach?
How about the straightforward dynamic mapping of joined (anonymously typed) objects?
Record1[] firstRecords = new[]
{
new Record1
{
ID = Guid.NewGuid(),
Name = "John", Surname = "Doe"
},
new Record1
{
ID = Guid.NewGuid(),
Name = "Jane", Surname = "Roe"
}
};
Record2[] secondRecords = new[]
{
new Record2 { Age = 20, Sex = Sex.Male },
new Record2 { Age = 20, Sex = Sex.Female }
};
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return Mapper.DynamicMap<Subject>(r);
})
.ToArray();
By the way, you can map these object without using AutoMapper, but using LINQ Select().
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return r;
})
.ToArray();
Update
If you need to copy a lot of properties, please take a look at the Value Injecter. InjectFrom() FTW!
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject();
r.InjectFrom(first).InjectFrom(second);
return r;
})
.ToArray();

Categories