Cartesian Product of an arbitrary number of objects [duplicate] - c#

This question already has answers here:
Is there a good LINQ way to do a cartesian product?
(3 answers)
Closed 4 years ago.
I'm looking to get the Cartesian Product of an arbitrary number of objects in c#. My situation is slightly unusual - my inputs are not lists of base types, but objects which have a property that's a list of base types.
My input and output objects are as follows:
public class Input
{
public string Label;
public List<int> Ids;
}
public class Result
{
public string Label;
public int Id;
}
Some sample input data:
var inputs = new List<Input>
{
new Input { Label = "List1", Ids = new List<int>{ 1, 2 } },
new Input { Label = "List2", Ids = new List<int>{ 2, 3 } },
new Input { Label = "List3", Ids = new List<int>{ 4 } }
};
And my expected output object:
var expectedResult = new List<List<Result>>
{
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
}
};
If I knew the number of items in 'inputs' in advance I could do this:
var knownInputResult =
from id1 in inputs[0].Ids
from id2 in inputs[1].Ids
from id3 in inputs[2].Ids
select
new List<Result>
{
new Result { Id = id1, Label = inputs[0].Label },
new Result { Id = id2, Label = inputs[1].Label },
new Result { Id = id3, Label = inputs[2].Label },
};
I'm struggling to adapt this to an arbitrary number of inputs - is there a possible way to do this?

I consider this duplicate of question linked in comments, but since it was reopened and you struggle to adapt that question to your case, here is how.
First grab function by Eric Lippert from duplicate question as is (how it works is explained there):
public static class Extensions {
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item })
);
}
}
Then flatten your input. Basically just attach corresponding label to each id:
var flatten = inputs.Select(c => c.Ids.Select(r => new Result {Label = c.Label, Id = r}));
Then run cartesian product and done:
// your expected result
var result = flatten.CartesianProduct().Select(r => r.ToList()).ToList();

I'm not proud of the amount of time I spent messing with this, but it works.
It's basically black magic, and I would replace it the first chance you get.
public static List<List<Result>> Permutate(IEnumerable<Input> inputs)
{
List<List<Result>> results = new List<List<Result>>();
var size = inputs.Select(inp => factorial_WhileLoop(inp.Ids.Count)).Aggregate((item, carry) => item + carry) - 1;
for (int i = 0; i < size; i++) results.Add(new List<Result>());
foreach (var input in inputs)
{
for (int j = 0; j < input.Ids.Count; j++)
{
for (int i = 0; i < (size / input.Ids.Count); i++)
{
var x = new Result() { Label = input.Label, Id = input.Ids[j] };
results[(input.Ids.Count * i) + j].Add(x);
}
}
}
return results;
}
public static int factorial_WhileLoop(int number)
{
var result = 1;
while (number != 1)
{
result = result * number;
number = number - 1;
}
return result;
}

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

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>.

Entity Framework query improved

I'm trying to make an android synchronization between client and ASP.NET MVC server. The logic is simple, my next method receives a data dictionary, where key = idGroup and value = LastMessageIdKnown, in the end I should get the next messages for each group what Id is higher than the LastMessageIdKnown (the value of my dictionary).
Right now I am iterating the map, for each key I do a query to my SQL database but this is inefficient, if I got N keys you can imagine what implying.
This is my current method
public Dictionary<int, List<Messages>> SynchronizedChatMessages(Dictionary<int, int> data)
{
Dictionary<int, List<Messages>> result = new Dictionary<int, List<Messages>>();
foreach(int item in data.Keys){
var idMessage= data[item];
var listMessages= _context.Messages.Where(x => x.Grupo_ID == item && x.ID > idMessage).ToList();
result.Add(item,listMessages);
}
return result;
}
How can I improve this query to get all what I need in an only and optimal way?
Thank you.
Here's an attempt that uses Predicates to make it so that there is only one Where against the whole collection of messages.
Note that I mocked this up without a database, so I am passing a List into the SynchronizedChatMessages function, whereas you have the context available.
What remains to be proven is that this way of doing things only generates one query to the database (since I did it in objects only). The whole program is further, below, but first, just the function showing use of predicates to achieve firing the Where only once.
public static Dictionary<int, List<Message>> SynchronizedChatMessages(List<Message> messages, Dictionary<int, int> data)
{
List<Predicate<Message>> predList = new List<Predicate<Message>>();
//Built of list of indivIdual predicates
foreach (var x in data)
{
var IdMessage = x.Key;
var lastMessageId = x.Value;
Predicate<Message> pred = m => m.IdGroup.Id == IdMessage && m.Id > lastMessageId;
predList.Add(pred);
}
//compose the predicates
Predicate<Message> compositePredicate = m =>
{
bool ret = false;
foreach (var pred in predList)
{
//If any of the predicates is true, the composite predicate is true (OR)
if (pred.Invoke(m) == true) { ret = true; break; }
}
return ret;
};
//do the query
var messagesFound = messages.Where(m => compositePredicate.Invoke(m)).ToList();
//get the individual distinct IdGroupIds
var IdGroupIds = messagesFound.Select(x => x.IdGroup.Id).ToList().Distinct().ToList();
//Create dictionary to return
Dictionary<int, List<Message>> result = new Dictionary<int, List<Message>>();
foreach (int i in IdGroupIds)
{
result.Add(i, messagesFound.Where(m => m.IdGroup.Id == i).ToList());
}
return result;
}
Here is the whole thing:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication20
{
public class Program
{
public class Message
{
public int Id { get; set; }
public IdGroup IdGroup { get; set; }
}
public class IdGroup
{
public int Id { get; set; }
public List<Message> Messages { get; set; }
}
public static Dictionary<int, List<Message>> SynchronizedChatMessages(List<Message> messages, Dictionary<int, int> data)
{
List<Predicate<Message>> predList = new List<Predicate<Message>>();
//Built of list of indivIdual predicates
foreach (var x in data)
{
var IdMessage = x.Key;
var lastMessageId = x.Value;
Predicate<Message> pred = m => m.IdGroup.Id == IdMessage && m.Id > lastMessageId;
predList.Add(pred);
}
//compose the predicates
Predicate<Message> compositePredicate = m =>
{
bool ret = false;
foreach (var pred in predList)
{
//If any of the predicates is true, the composite predicate is true (OR)
if (pred.Invoke(m) == true) { ret = true; break; }
}
return ret;
};
//do the query
var messagesFound = messages.Where(m => compositePredicate.Invoke(m)).ToList();
//get the individual distinct IdGroupIds
var IdGroupIds = messagesFound.Select(x => x.IdGroup.Id).ToList().Distinct().ToList();
//Create dictionary to return
Dictionary<int, List<Message>> result = new Dictionary<int, List<Message>>();
foreach (int i in IdGroupIds)
{
result.Add(i, messagesFound.Where(m => m.IdGroup.Id == i).ToList());
}
return result;
}
public static void Main(string[] args)
{
var item1 = new IdGroup { Id = 2, Messages = new List<Message>() };
var item2 = new IdGroup { Id = 45, Messages = new List<Message>() };
var item3 = new IdGroup { Id = 36, Messages = new List<Message>() };
var item4 = new IdGroup { Id = 8, Messages = new List<Message>() };
var message1 = new Message { Id = 3, IdGroup = item1 };
var message2 = new Message { Id = 7, IdGroup = item1 };
var message3 = new Message { Id = 9, IdGroup = item1 };
item1.Messages.Add(message1);
item1.Messages.Add(message2);
item1.Messages.Add(message3);
var message4 = new Message { Id = 4, IdGroup = item2 };
var message5 = new Message { Id = 10, IdGroup = item2 };
var message6 = new Message { Id = 76, IdGroup = item2 };
item2.Messages.Add(message4);
item2.Messages.Add(message5);
item2.Messages.Add(message6);
var message7 = new Message { Id = 6, IdGroup = item3 };
var message8 = new Message { Id = 32, IdGroup = item3 };
item3.Messages.Add(message7);
item3.Messages.Add(message8);
var message9 = new Message { Id = 11, IdGroup = item4 };
var message10 = new Message { Id = 16, IdGroup = item4 };
var message11 = new Message { Id = 19, IdGroup = item4 };
var message12 = new Message { Id = 77, IdGroup = item4 };
item4.Messages.Add(message9);
item4.Messages.Add(message10);
item4.Messages.Add(message11);
item4.Messages.Add(message12);
List<IdGroup> items = new List<IdGroup> { item1, item2, item3, item4 };
List<Message> messages = new List<Message> { message1, message2, message3, message4, message5, message6,message7, message8, message9, message10, message11, message12};
Dictionary<int, int> lastMessagesPerItem = new Dictionary<int, int> { { 2, 3 }, { 45, 10 }, { 36, 6 }, { 8, 11 } };
var result = SynchronizedChatMessages(messages, lastMessagesPerItem);
var discard = Console.ReadKey();
}
}
}
Well it would be nice if this would work, but I doubt it that can be translated to a SQL statement in one go:
var toInsert =
from msg in _context.Messages
group msg by msg.Grupo_ID into g
where data.Keys.Contains(g.Key)
select new {
Item = g.Key,
Messages = g.Where(x => x.ID > data[g.Key])
};
I don't think the second Where clause x => x.ID > data[g.Key] can be translated.
So you may need to do this in two passes, like this:
// This is a single SQL query.
var groups =
from msg in _context.Messages
group msg by msg.Grupo_ID into g
where data.Keys.Contains(g.Key)
select new {
Item = g.Key,
// ordering helps us when we do the in-memory part.
Messages = g.OrderByDescending(x => x.ID).ToList()
};
// This iterates the result set in memory
foreach (var g in groups)
result.Add(
g.Item,
// input is ordered, we stop when an item is <= data[g.Item].
g.Messages.TakeWhile(m => m.ID > data[g.Item]).ToList())

Can someone help me ordering this list?

I am not the best programmer, so need some help to order this list. I had a few stabs at it, but still getting some cases which are wrong.
Essentially the list is the following:
#, ID, PreceedingID
A, 1 , 0
B, 2 , 3
C, 3 , 1
D, 4 , 2
I want to order it so that the list follows the preceeding id. The first item will always have the preceeding ID of 0.
#, ID, PreceedingID
A, 1 , 0
C, 3 , 1
B, 2 , 3
D, 4 , 2
Do you think you can help?
Thanks!
How about:
var data = new[] {
new Row{ Name = "A", ID = 1, PreceedingID = 0},
new Row{ Name = "B", ID = 2, PreceedingID = 3},
new Row{ Name = "C", ID = 3, PreceedingID = 1},
new Row{ Name = "D", ID = 4, PreceedingID = 2},
};
var byLastId = data.ToDictionary(x => x.PreceedingID);
var newList = new List<Row>(data.Length);
int lastId = 0;
Row next;
while (byLastId.TryGetValue(lastId, out next))
{
byLastId.Remove(lastId); // removal avoids infinite loops
newList.Add(next);
lastId = next.ID;
}
After this, newList has the data in the desired order.
In the above, Row is:
class Row
{
public string Name { get; set; }
public int ID { get; set; }
public int PreceedingID { get; set; }
}
But obviously substitute for your own type.
You can use for example dictionary to sort it:
Dictionary<..> d = new Dictionary<..>()
foreach(var el in list){
d[el.PreceedingID] = el; //put data to dict by PreecedingID
}
List<..> result = new List<..>();
int prec = 0; //get first ID
for(int i = 0; i < list.Length; ++i){
var actEl = d[prec]; //get next element
prec = actEl.ID; //change prec id
result.Add(actEl); //put element into result list
}

LINQ C# - Combining multiple groups

LINQ Groupby query creates a new group for each unique key. I would like to combine multiple groups into a single group based on the key value.
e.g.
var customersData = new[]
{
new { id = 1, company = "ABC" },
new { id = 2, company = "AAA" },
new { id = 3, company = "ABCD" },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company;
Let's say I want ABC, AAA, and ABCD in the same group, and XYZ, X.Y.Z. in the same group.
Is there anyway to achieve this through LINQ queries?
You will need to use the overload of GroupBy that takes an IEqualityComparer.
var groups = customersData.GroupBy(k => k.company, new KeyComparer());
where KeyComparer could look like
public class KeyComparer : IEqualityComparer
{
public bool Equals(string x, string y)
{
// put your comparison logic here
}
public int GetHashCode(string obj)
{
// same comparison logic here
}
}
You can comparer the strings any way you like in the Equals method of KeyComparer.
EDIT:
You also need to make sure that the implementation of GetHashCode obeys the same rules as the Equals method. For example if you just removed the "." and replaced with "" as in other answers you need to do it in both methods like this
public class KeyComparer : IEqualityComparer
{
public bool Equals(string x, string y)
{
return x.Replace(".", "") == y.Replace(".", "");
}
public int GetHashCode(string obj)
{
return obj.Replace(".", "").GetHashCode();
}
}
I am assuming the following:
You meant to have quotes surrounding the company "names" (as below).
Your problem is simply solved by removing the '.'s from each company name.
If these assumptions are correct, the solution is simply the following:
var customersData = new[] {
new { id = 1, company = "ABC" },
new { id = 2, company = "A.B.C." },
new { id = 3, company = "A.B.C." },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company.Replace(".", "");
If these assumptions are not correct, please clarify and I can help work closer to a solution.
var groups = from d in customersData
group d by d.company.Replace(".", "");
public void int GetId(Company c)
{
int result = //whatever you want
return result;
}
then later:
var groups = from d in customersData
group d by GetId(d.company);
I think this is what you want:
var customersData = new[]
{
new { id = 1, company = "ABC" },
new { id = 2, company = "AAA" },
new { id = 3, company = "ABCD" },
new { id = 4, company = "XYZ" },
new { id = 5, company = "X.Y.Z." },
new { id = 6, company = "QQQ" },
};
var groups = from d in customersData
group d by d.company[0];
foreach (var group in groups)
{
Console.WriteLine("Group " + group.Key);
foreach (var item in group)
{
Console.WriteLine("Item " + item.company);
}
}
Console.ReadLine();

Categories