Change big collection into unique List of collections - c#

I have a big list of objects and in this object there is a category ID something like:
var list = new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
// and so on
};
So I am looking for making this complicated list more organized like list of lists of unique elements
something like:
var result = new List<List<Example>>
{
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }},
new Example {CatId = 3, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }},
new Example {CatId = 2, Value = new { }}
},
new List<Example>
{
new Example {CatId = 1, Value = new { }}
}
}
Problem is I do not what to use, group by will not fix my case, so how to do this in most efficient way.

So this is about partitioning, it's the sort of thing that is easy to do in a database query, but in c# you need to create some key with a partition number that you can then use to .GroupBy.
The partitioning itself is a grouping
var projected = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) );
This gives you records that look like:
{"Item":{"CatId":1,"Value":{}},"rn":1}
{"Item":{"CatId":1,"Value":{}},"rn":2}
{"Item":{"CatId":1,"Value":{}},"rn":3}
{"Item":{"CatId":2,"Value":{}},"rn":1}
{"Item":{"CatId":2,"Value":{}},"rn":2}
{"Item":{"CatId":3,"Value":{}},"rn":1}
As you can see that rn ("row number") value can be used to group by:
var result = projected.GroupBy(x => x.rn, x => x.Item);
This gives us:
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}},{"CatId":3,"Value":{}}]
[{"CatId":1,"Value":{}},{"CatId":2,"Value":{}}]
[{"CatId":1,"Value":{}}]
So, all in 1 go:
var result = list.GroupBy(x => x.CatId)
.SelectMany( g => g.Select( ( x, i ) => new { Item = x, rn = i + 1 } ) )
.GroupBy(x => x.rn, x => x.Item);
Live example: https://dotnetfiddle.net/AlTfk8

Related

Using Group by with x amount of elements

Here's a list, think of it as rows and columns where rows are going down and columns are side ways. the column count will always be the same for all rows.
var dataValues = new List<List<string>>()
{
//row 1
new List<string>(){"A","12","X","P8" },
//row 2
new List<string>(){"B","13","Y","P7" },
//row 3
new List<string>(){"C","12","Y","P6" },
//row 4
new List<string>(){"A","14","X","P5" },
//....
new List<string>(){"D","15","Z","P4" },
new List<string>(){"A","13","X","P3" },
new List<string>(){"B","14","Y","P2" },
new List<string>(){"C","13","Z","P1" },
};
The user providers a list of indexes to group by.
var userParam= new List<int>() { 0, 2 };
my question is how do i dynamically group dataValues by the userParam where user param is n amount of index. In the example above it will gorup by the first column and the 3rd. However the index can change and the amount of indexes can change aswell
example
var userParam2 = new List<int>() { 0, 2};
var userParam3 = new List<int>() { 0};
var userParam4 = new List<int>() { 0,1,2};
i know how to group by when i know how many indexes there will be (the the case below it's 2 index parameters), however when it's dynamic (x amount) then i do not know how to do this
var result = dataValues.GroupBy(e => new { G1 = e[userParam2 [0]], G2 = e[userParam2 [1]] });
You could use a Custom Comparer to achieve this :
1 - Declaration of GroupByComparer that inherit from IEqualityComparer :
public class GroupByComparer : IEqualityComparer<List<string>>
{
private static List<int> _intList;
public GroupByComparer(List<int> intList)
{
_intList = intList;
}
public bool Equals(List<string> x, List<string> y)
{
foreach (int item in _intList)
{
if (x[item] != y[item])
return false;
}
return true;
}
public int GetHashCode(List<string> obj)
{
int hashCode = 0;
foreach (int item in _intList)
{
hashCode ^= obj[item].GetHashCode() + item;
}
return hashCode;
}
}
2 - Call group by with EqualityComparer like :
var userParam = new List<int>() { 0, 2 };
var result = dataValues.GroupBy(e => e, new GroupByComparer(userParam));
I hope you find this helpful.
I believe i have something but this looks slow please let me know if there is anyway better of doing this.
var userParams = new List<int>() { 0, 2 };
var dataValues = new List<List<string>>()
{
new List<string>(){"A","12","X","P8" },
new List<string>(){"B","13","Y","P7" },
new List<string>(){"C","12","Y","P6" },
new List<string>(){"A","14","X","P5" },
new List<string>(){"D","15","Z","P4" },
new List<string>(){"A","13","X","P3" },
new List<string>(){"B","14","Y","P2" },
new List<string>(){"C","13","Z","P1" },
};
var result = new List<(List<string> Key, List<List<string>> Values)>();
result.Add((new List<string>(), dataValues));
for (int index = 0; index < userParams.Count; index++)
{
var currentResult = new List<(List<string> Key, List<List<string>> Values)>();
foreach (var item in result)
{
foreach (var newGroup in item.Values.GroupBy(e => e[userParams[index]]))
{
var newKey = item.Key.ToList();
newKey.Add(newGroup.Key);
currentResult.Add((newKey, newGroup.ToList()));
}
}
result = currentResult;
}
foreach(var res in result)
{
Console.WriteLine($"Key: {string.Join(#"\", res.Key)}, Values: {string.Join(" | ", res.Values.Select(e=> string.Join(",",e)))}");
}
final result
Key: A\X, Values: A,12,X,P8 | A,14,X,P5 | A,13,X,P3
Key: B\Y, Values: B,13,Y,P7 | B,14,Y,P2
Key: C\Y, Values: C,12,Y,P6
Key: C\Z, Values: C,13,Z,P1
Key: D\Z, Values: D,15,Z,P4

LINQ merging 2 lists, keeping seqeunce and origin [duplicate]

This question already has answers here:
LINQ - Full Outer Join
(16 answers)
Closed 4 years ago.
Here I have 2 lists of same object type.
object = {id: xxx, ...} // attribute "id" is used to find the identical obj
List oldSet = [old1, old2, old3];
List newSet = [new2, new3, new4];
// old2 = {id= 2, result = 5, ...}
// new2 = {id= 2, result = 1, ...}
// expected result = {oldSet: old2; newSet: new2}
I want to merge both lists, also keeping the origin of which list it came from.
The expected result as below:
List mergedSet = [{old1, null}, {old2, new2}, {old3, new3}, {null, new4}];
I'm thinking to use LINQ C# for it, but stuck somewhere.
Kindly advise.
Thanks! :)
Here's some code that does what you want using Linq. It basically walks through all the old list, and adds pairs to the merged list by looking for matches from the new list (and adding null as the second item if no match was found). Then it walks through the remaining items in the new list and adds them with null for the first item. It selects a dynamic type with two properties: OldSet and NewSet, so you know where each item came from.
The merge code is simply:
var mergedSet = oldSet.Select(o =>
new {OldSet = o, NewSet = newSet.FirstOrDefault(n => n.id == o.id)})
.Concat(newSet.Where(n => oldSet.All(o => o.id != n.id)).Select(n =>
new {OldSet = (Item) null, NewSet = n}));
This is based on the following item class:
class Item
{
public int id { get; set; }
public string result { get; set; }
public override string ToString()
{
return $"{result}{id}";
}
}
We create our lists:
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
Run the merge code (very first snippet), and then display results:
foreach (var item in mergedSet)
{
Console.WriteLine($"{item.NewSet},{item.OldSet}");
}
Output
Try something like this :
List<string> oldSet = new List<string>() {"old1", "old2", "old3"};
List<string> newSet = new List<string>() {"new2", "new3", "new4"};
var results = oldSet.Select((x,i) => new { oldSet = x, newSet = newSet[i]}).ToList();
You can left join the two lists. I edited the answer as you actually need to left join twice, union, and apply a select distinct to get the cases where oldSet = null and no duplicates...
var mergedSet = (from o in oldSet
join n in newSet on o.id equals n.id into ns
from n in ns.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Union(from n in newSet
join o in oldSet on n.id equals o.id into os
from o in os.DefaultIfEmpty()
select new { OldSet = o, NewSet = n })
.Distinct();
Might be an overkill, but if you really want to use LINQ
List<Item> oldSet = new List<Item>
{
new Item {id = 1, result = "old"},
new Item {id = 2, result = "old"},
new Item {id = 3, result = "old"},
};
List<Item> newSet = new List<Item>
{
new Item {id = 2, result = "new"},
new Item {id = 3, result = "new"},
new Item {id = 4, result = "new"},
};
var resultL = oldSet.GroupJoin(
newSet,
o => o.id,
n => n.id,
(o,n) => new { Old = o, New = n })
.SelectMany(
n => n.New.DefaultIfEmpty(),
(o,n) => new Tuple<Item,Item>(o.Old,n));
var resultR= newSet.GroupJoin(
oldSet,
n => n.id,
o=> o.id,
(n,o) => new { Old = o, New = n })
.SelectMany(
o=> o.Old.DefaultIfEmpty(),
(n,o) => new Tuple<Item,Item>(o,n.New));
var result = resultL.Union(resultR).Distinct();
In this case, you have to use two GroupJoin and the Union the results.
Look at the following code:
var res1 = oldSet.GroupJoin(newSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = x, Y = yy }; });
var res2 = newSet.GroupJoin(oldSet, o => o, k => k, (x, y) => { var yy = y.FirstOrDefault(); return new { X = yy, Y = x }; });
var result = res1.Union(res2).ToList();// Your result is here

comparison of the list in the list

I have a list of lists:
List<Product> productList = new List<Product>()
{
new Product()
{
Id = 1,
Model = "Phone",
TypeProd = new CheckTypes
{
ChTypes = new List<CHType>
{
new CHType
{
Id = 8,
IdName = "261"
},
new CHType
{
Id = 9 ,
IdName = "149"
}
}
}
},
new Product
{
Id = 1,
Model = "Printer",
TypeProd = new CheckTypes
{
ChTypes = new List<CHType>
{
new CHType
{
Id = 8,
IdName = null
},
new CHType
{
Id = 8,
IdName = "261"
}
}
}
}
};
And I want to get the first item of this list by comparing the IdName elements with a string[]:
string[] arrStr = new string[] { "261", "149" };
How can I do this better? Tried using foreach and by creating a temporary object that takes an array value and then uses intersect to compare.
You could do it pretty simply with LINQ:
var product = productList
.FindAll(x => x.TypeProd.ChTypes
.All(y => arrString.Contains(y.IdName));
This will give you all products whose TypeProd.ChTypes elements are all in arrString.
For faster performance, you may want to turn arrString into a HashSet<string>.

"select" query using "PetaPoco" ORM

I tried something like the following, but it didn't work.
var _records = new string[] {"SqlServer", "IIS" };
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN rs", new { rs = _records });
and also i have tried another way like the following, but same problem
var _records = new string[] {"SqlServer", "IIS" };
var query = PetaPoco.Sql.Builder.Select("*").From("tblRecords").Where("RecordName IN (#rs)",new { rs = _records });
var result = db.Query<EntityRecords>(query);
The first one should be
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN (#rs)", new { rs = _records });
or
var result = db.Fetch<EntityRecords>(#" select * from tblRecords where RecordName IN (#0)", _records);
The second one I'm not too sure about because the following tests pass
[Fact]
public void Append_GivenArrayAndValue_ShouldBeValid()
{
// Simple collection parameter expansion
_sql = Sql.Builder.Append("#0 IN (#1) #2", 20, new int[] { 1, 2, 3 }, 30);
_sql.SQL.ShouldBe("#0 IN (#1,#2,#3) #4");
_sql.Arguments.Length.ShouldBe(5);
_sql.Arguments[0].ShouldBe(20);
_sql.Arguments[1].ShouldBe(1);
_sql.Arguments[2].ShouldBe(2);
_sql.Arguments[3].ShouldBe(3);
_sql.Arguments[4].ShouldBe(30);
}
[Fact]
public void Append_GivenArrayAndNamedValue_ShouldBeValid1()
{
// Simple collection parameter expansion
_sql = Sql.Builder.Append("#p1 IN (#p2) #p3", new { p1 = 20 }, new { p2 = new int[] { 1, 2, 3 }}, new { p3 = 30 });
_sql.SQL.ShouldBe("#0 IN (#1,#2,#3) #4");
_sql.Arguments.Length.ShouldBe(5);
_sql.Arguments[0].ShouldBe(20);
_sql.Arguments[1].ShouldBe(1);
_sql.Arguments[2].ShouldBe(2);
_sql.Arguments[3].ShouldBe(3);
_sql.Arguments[4].ShouldBe(30);
}

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.

Categories