Finding common components using LINQ - c#

I am trying to find all the orders that have components in common and in addition the list of components in common:
Component Class:
public class Component
{
public Component(string name)
{
this.Name = name;
}
public string Name { get; private set; }
}
Order Class:
internal class Order
{
public Order(string name,List<Component> components)
{
this.Name = name;
this.Components = components;
}
public string Name { get; private set; }
public List<Component>Components { get; private set; }
}
For example:
var component1 = new Component("B1");
var component2 = new Component("B2");
var component3 = new Component("B3");
var component4 = new Component("B4");
var order1 = new Order("M1", new List<Component> { component2 });
var order2 = new Order("M2", new List<Component> { component1, component2, component3, component4 });
var order3 = new Order("M3", new List<Component> { component1, component2 });
var dependents = from cmp1 in order1.Components
join cmp2 in order2.Components
on cmp1.Name equals cmp2.Name
select new
{
order1Name = order1.Name,
order2Name = order2.Name,
ComponentName = cmp1.Name
};
var result = dependents.ToList();
The result is showing the correct information, the common component between order1 and order2 which is component2 ("B2").
How can I make it more general using orders list:
var orders = new List<Order> { order1, order2, order3 };
I would like to get as result, for each 2 orders a list of components in common instead of doing it for each possible pair.
I presume this is something like:
var allDependents =
runs.ForEach(order=>order.Components)
....
from cmp1 in order1.Components
join cmp2 in order2.Components
on cmp1.Name equals cmp2.Name
select new
{
order1Name = order1.Name,
order2Name = order2.Name,
ComponentName = cmp1.Name
};
Additional Info:
As per the following picture, we can see for each 2 orders, a list of components

Assuming all the names are unique you could do this.
var results = from o1 in orders
from c1 in o1.Components
from o2 in orders.SkipWhile(o => o.Name != o1.Name)
from c2 in o2.Components
where o1.Name != o2.Name && c1.Name == c2.Name
select new
{
Order1 = o1.Name,
Order2 = o2.Name,
Component = c1.Name
};
foreach(var r in results) Console.WriteLine(r);
It produces this output
{ Order1 = M1, Order2 = M2, Component = B2 }
{ Order1 = M1, Order2 = M3, Component = B2 }
{ Order1 = M2, Order2 = M3, Component = B1 }
{ Order1 = M2, Order2 = M3, Component = B2 }

One possible efficient way is to use semi-join with additional criteria like this:
var orders = new List<Order> { order1, order2, order3 };
var orderComponents = from order in orders
from component in order.Components
select new { order, component };
var dependents =
from e1 in orderComponents
join e2 in orderComponents on e1.component.Name equals e2.component.Name
where e2.order != e1.order && e2.order.Name.CompareTo(e1.order.Name) > 0
select new
{
order1Name = e1.order.Name,
order2Name = e2.order.Name,
ComponentName = e1.component.Name
};
The only detail to be mentioned is the criteria e2.order != e1.order && e2.order.Name.CompareTo(e1.order.Name) > 0. The first condition excludes the order from the left side while the second excludes duplicates like { M2, M1 }.

I'm more experienced with the Fluent Linq syntax, in which you can do what you want:
var orders = new[] { order1, order2, order3 };
var dependents = orders.SelectMany(order =>
orders
.Where(other => other.Name != order.Name)
.SelectMany(other => other.Components.Intersect(order.Components)
.Select(c => new { order, other, component = c }))
).ToList();

You could use a lookup (warning: untested code):
var lookup = orders
.SelectMany(ord => ord.Components.Select(cmp => new { Order = ord, Component = cmp)
.ToLookup(obj => obj.Component /* or obj.Component.Name, if you prefer */)
.Where(lkp => lkp.Count() > 1);
foreach(var orders in lookup)
{
// orders.Key is the component, orders is an Enumeration of orders containing that component.
}

If you implement IEquatable<T> on Component and on Order, and also override object.Equals() (as good practice), you can do something like:
var ordersA = new List<Order> { order1, order2, order3 };
var ordersB = new List<Order> { order2, order3 };
bool equal = ordersA.Equals(ordersB);
The 'equals' implementation on Component will also allow you to get common components by using set intersections like:
var compA = new List<Component> { c1, c2, c3};
var compB = new List<Component> { c2, c3};
var commonComponents = compA.Intersect(compB);
You could even use Intersect on lists of Order.

Related

How to retrieve data from dynamic table in mvc

I'm writing new api call for my android app, I want to retrieve data from dynamic table, I mean when user select "A" button from Andriod I want to retrieve data from "A" table and if "B" is click , retrieve data from "B" table. Someone help me to find the possible solution.
I want to replace with variable for "JBCTNRITEMs" from entities.JBCTNRITEMs (table-name)
var query = (from jbct in entities.JBCTNRITEMs
where jbid.Contains(jbct.jbid.ToString()) && boxid.Contains(jbct.TrackingNo.ToString())
select jbct).ToArray();
int id = 0;
if (query.Length > 0)
{
id = (query[0].id);
}
return id;
Here are the two different possibilities as an example
if (module.ToLower() == "module-a")
{
var imageinfo = (from jb in entities.TableA.AsEnumerable()
where scanID.Contains(jb.aid.ToString())
select jb).ToArray();
InsertGENIMAGE(userID, scanID, FilePath, imageinfo, "AIMAGE");
}
else if (module.ToLower() == "module-b")
{
var imageinfo = (from jb in entities.TableB.AsEnumerable()
where scanID.Contains(jb.bid.ToString())
select jb).ToArray();
InsertGENIMAGE(userID, scanID, FilePath, imageinfo, "BIMAGE");
}
Here, query stores what you are you trying to select. As long as you are trying to select same type or same anonymous type, it will work.
Here is a simple example:
class Test1
{
public int ID { get; set; }
public string Name { get; set; }
}
class Test2
{
public int ID { get; set; }
public string Name { get; set; }
}
var test1Lst = new List<Test1>
{
new Test1() { ID = 1, Name = "Jitendra" },
new Test1() { ID = 2, Name = "Jahnavi" }
};
var test2Lst = new List<Test2>
{
new Test2() { ID = 1, Name = "Aaditri" },
new Test2() { ID = 2, Name = "Pankaj" }
};
var test = false;
var query = test ? (from t in test1Lst select new { ID = t.ID, Name = t.Name }) : (from t in test2Lst select new { ID = t.ID, Name = t.Name });
// Put whatever condition you want to put here
query = query.Where(x => x.ID == 1);
foreach(var t1 in query)
{
Console.WriteLine(t1.ID + " " + t1.Name);
}
I guess in this case I would suggest to use a generic method :
private T GetMeTheFirstEntry<T>(Expression<Func<T, bool>> filter) where T : class
{
return entities.GetTable<T>().FirstOrDefault(filter);
}
The GetTable will allow you to interchange the tableA and tableB. You would call it the following way:
TableA tableA_entity = GetMeTheFirstEntry<TableA>(jb => scanID.Contains(jb.aid.ToString()));
TableB tableB_entity = GetMeTheFirstEntry<TableB>(jb => scanID.Contains(jb.bid.ToString()));
If the filtering was successfull, then the retrieved object will not be null and you can use it:
int a_id = tableA_entity.aid;

Get result from list inside list

I have a structure like
class a
{
public IList<b> bs{ get; set; }
public class b
{
public string r{ get; set; }
public IList<sl> sls{ get; set; }
public class sl
{
public string sn{ get; set; }
public string st{ get; set; }
}
}
}
the query is like if sn == "abc" then get r
I have done
a aobj = new a();
var aa = aobj.bs.Where(c => c.sl != null).Select(c => c).ToList(); // here I get `r = "qwerty", sls will have data like sn = "qwerty0", st= "1" ; sn = "asdf" , st="2"; sn = "zxc" st = "abc"; sn="me" , st = "abc"
var bb = aa.where(c => c.sl.Select(dr => dr.st.ToLower().Contains("abc"))); // I 'm here checking that `sn` contain abc or not
var cc = bb.Select(c => c.r).ToList(); // result
my expected output of query is "zxc", "me"
but I am getting all the list not only contains abc.. can anyone suggest me what should I do? I am partitioning this query to debug.
Thank you
You'll need to use the Any operator to check if an enumerable collection has an item that meets a criteria.
You can't use Select as that only projects an item, it isn't returning an predicate and as such has no function in a where clause.
Here is your (fixed for syntax errors) changed code:
var aa = aobj.bs.Where(c => c.sls != null).Select(c => c).ToList();
// use Any here
var bb = aa.Where(c => c.sls.Any(dr => dr.sn.ToLower().Contains("abc")));
var cc = bb.Select(c => c.r).ToList();
And here is the test set I used:
a aobj = new a();
aobj.bs = new List<b>();
aobj.bs.Add(new b {
r ="bar",
sls = new List<sl>{
new sl { sn="tets"},
new sl { sn="no"}
}
});
aobj.bs.Add(new b {
r ="foo",
sls = new List<sl>{
new sl { sn="no"},
new sl { sn="abc"}
}
});
aobj.bs.Add(new b {
r ="fubar",
sls = new List<sl>{
new sl { sn="no"},
new sl { sn="abc"}
}
});
This will output:
foo
fubar
If you combine all operators together you'll get:
var merged = aobj
.bs
.Where(c => c.sls != null
&& c.sls.Any(dr => dr.sn.ToLower().Contains("abc")))
.Select(c => c.r);
I think you can use a code like this:
var cc = a.bs
.Where(w => w.sls?.Any(s => s.st?.ToLower().Contains("abc") ?? false) ?? false)
.Select(c => c.r);

Pass aggregate function as parameter

I have a simple object:
public class Machine
{
public string Name { get; set; }
public int Power { get; set; }
public int Type { get; set; }
}
Then there's a class which holds a List of these objects:
public class Aggregations
{
private List<Machine> _machines;
public Aggregations()
{
_machines = new List<Machine>
{
new Machine { Name = "XLR1", Power = 111, Type = 1 },
new Machine { Name = "XLR2", Power = 222, Type = 1 },
new Machine { Name = "XLR3", Power = 333, Type = 1 },
new Machine { Name = "XLR4", Power = 444, Type = 1 },
new Machine { Name = "XLR5", Power = 555, Type = 2 },
new Machine { Name = "XLR6", Power = 666, Type = 2 }
};
}
// ...
}
There're two functions which return lists of machines with specific criteria:
public IEnumerable<Machine> MaxPower(IEnumerable<Machine> machines)
{
var maxPowerMachinesPerType = new List<Machine>();
var groups = machines.GroupBy(m => m.Type);
foreach (var g in groups)
{
var max = g.Max(m => m.Power);
var machine = g.First(m => m.Power == max);
maxPowerMachinesPerType.Add(machine);
}
return maxPowerMachinesPerType;
}
public IEnumerable<Machine> MinPower(IEnumerable<Machine> machines)
{
var minPowerMachinesPerType = new List<Machine>();
var groups = machines.GroupBy(m => m.Type);
foreach (var g in groups)
{
var min = g.Min(m => m.Power);
var machine = g.First(m => m.Power == min);
minPowerMachinesPerType.Add(machine);
}
return minPowerMachinesPerType;
}
}
As you can see, the two functions are almost equal. Only "max" and "min" differ.
These functions are called like this:
IEnumerable<Machine> maxPowerMachines = MaxPower(_machines);
IEnumerable<Machine> minPowerMachines = MinPower(_machines);
Because my actual program is slightly more complex and I although would like to call other aggregate functions, I would like to pass the aggregate-function to be used as a parameter: (Pseudo-code)
IEnumerable<Machine> maxPowerMachines = SuperFunction(_machines, m => m.Max);
IEnumerable<Machine> minPowerMachines = SuperFunction(_machines, m => m.Min);
IEnumerable<Machine> averagePowerMachines = SuperFunction(_machines, m => m.Average);
I hope you get the intent.
Since Min and Max have the same signature, i.e. they both take IEnumerable<T> and produce a T, you can do it like this:
public IEnumerable<Machine> SelectPower(
IEnumerable<Machine> machines
, Func<IEnumerable<int>,int> powerSelector
) {
var res = new List<Machine>();
var groups = machines.GroupBy(m => m.Type);
foreach (var g in groups) {
var targetPower = powerSelector(g.Select(m => m.Power));
var machine = g.First(m => m.Power == targetPower);
res.Add(machine);
}
return res;
}
Now you can call your method like this:
IEnumerable<Machine> maxPowerMachines = SuperFunction(_machines, m => m.Max());
IEnumerable<Machine> minPowerMachines = SuperFunction(_machines, m => m.Min());
Func<T> sounds like what you're looking for. The leading types <T> show the input, and the last <T> shows the return value, so you'd be looking for something like this:
Func<IEnumerable<Machine>, IEnumerable<Machine>> aggregateFunction = MaxPower;
//Now you can pass aggregateFunction around as a variable. You can call it like so:
var machines = Aggregations();
aggregateFunction.Invoke(machines);

Need help querying through 3 classes

I have 3 classes that are defined like this:
class Customer
{
public string Name;
public string City;
public Order[] Orders;
}
class Order
{
public int Quantity;
public Product Product;
}
class Product
{
public string ProdName;
public decimal Price;
}
And I want to use LINQ in C# to print out the names that bought a specific product which in this case is 'ProdName'. I can't find a solution in order to go through all these 3 classes that could give me the name based on the product name.
I have tried something like this but it seems it doesn;t work:
var query = from c in customers where c.Order[0].Product.ProdName.Contains("Milk")
select c.Name;
foreach(var item in query)
{
Console.WriteLine(item);
}
This is how I set up the values for each class:
static public List<Customer> GetCustomerList()
{
var customerList = new List<Customer>
{
new Customer {Name = "Johny", City = "London", Orders = new Order[3] },
new Customer {Name = "Morgan", City = "Copenhagen", Orders = new Order[4]},
new Customer {Name = "Rasmus", City = "Amsterdam", Orders = new Order[1] }
};
return customerList;
}
static public List<Order> GetOrderList()
{
var orderList = new List<Order>
{
new Order { Quantity = 10, Product = new Product()},
new Order { Quantity = 5, Product = new Product()},
new Order { Quantity = 2, Product = new Product()}
};
return orderList;
}
static public List<Product> GetProductList()
{
var productList = new List<Product>
{
new Product { Name = "Cookie, bread", Price = 50 },
new Product { Name = "Cookie, Bread, Milk", Price = 85},
new Product { Name = "bags", Price = 38}
};
return productList;
}
static void Main(string[] args)
{
List<Customer> customers = GetCustomerList();
List<Order> orders = GetOrderList();
List<Product> products = GetProductList();
}
How can I linq all 3 classes together in order to get right result? any hints, please?
You need to build real, related, test data. Some usable fake data setup might look like:
// Create a single instance of each Product that could be used
var egg = new Product { Name = "Eggs", Price = 2.0 };
var bread = new Product { Name = "Bread", Price = 3.0 };
var fooBars = new Product { Name = "FooBars", Price = 2.5 };
var customerList = new List<Customer>
{
new Customer { Name = "Johny", City = "London", Orders = new List<Order>
{
new Order { Quantity = 3, Product = bread },
new Order { Quantity = 1, Product = egg },
new Order { Quantity = 2, Product = fooBars }
}},
new Customer { Name = "Morgan", City = "Copenhagen", Orders = new List<Order>
{
new Order { Quantity = 30, Product = bread }
}},
new Customer { Name = "Rasmus", City = "Amsterdam", Orders = new List<Order>
{
new Order { Quantity = 12, Product = fooBars }
}}
};
Please note that I used List<Order> instead of Order[], but you could switch it back. I also opted for a Name property in Product as you showed in your example code, but which doesn't match your class definition.
Now you can query. Let's see who bought bread:
var whoBoughtBread = customerList
.Where(c => c.Orders.Any(o => o.Product == bread))
.Select(c => c.Name);
Or
var whoBoughtBread2 = customerList
.Where(c => c.Orders.Any(o => o.Product.Name == "Bread"))
.Select(c => c.Name);
With the data structure you have one query would be:
customerList.Where(c => c.Orders.Any(o => o.Product.ProdName == prodName));
but as mentioned in the comments you have potentials for both a null collection and null values within the collection. I would highly recommend making sure you have non-null collections with non-null elements to avoid having to inject null-checking into your query. Otherwise you'll have to do sometihng like:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o != null &&
o.Product != null &&
o.Product.ProdName == prodName));
With C# 6's null-propagation operator (?.) you can shorten it a little:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o?.Product != null &&
o.Product.ProdName == prodName));

How can I group by a list of elements?

I run into this issue again and again: how can I group a list of objects by a containing list of other objects?
I have a list of objects of type A and each of these objects has an property (lets call it ListProp) which is a list also. ListProp has elements of the type B. There are multiple elements of type A with identically B-objects in ListProp, but the ListProp property reference differs from element to element. How can I group these A-objects the fastest way, where the B-objects in ListProp are identically?
Sample code:
class Program
{
static void Main(string[] args)
{
var exampleList = new List<A>
{
// Should be in first group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in first group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in second group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in third group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 0 }}
}}
};
// Doesn't work because the reference of ListProp is always different
var groupedExampleList = exampleList.GroupBy(x => x.ListProp);
}
}
class C
{
public int Number { get; set; }
public override bool Equals(object o)
{
if (o is C)
return Number.Equals(((C)o).Number);
else
return false;
}
}
class B
{
public C Prop { get; set; }
}
class A
{
public IList<B> ListProp { get; set; }
}
You can implement IEqualityComparer<List<B>> and use it in the other GroupBy overload.
public class ListOfBEqualityComparer : IEqualityComparer<List<B>>
{
public bool Equals(List<B> x, List<B> y)
{
// you can also implement IEqualityComparer<B> and use the overload
return x.SequenceEqual(y);
}
public int GetHashCode(List<B> obj)
{
//implementation of List<T> may not work for your situation
return obj.GetHashCode();
}
}
Then you can use the overload
var groupedExampleList = exampleList.GroupBy(x => x.ListProp,
new ListOfBEqualityComparer());
Try this:
GroupBy(x => String.Join(",", x.ListProp));
It will group by 0,1; 0,1; 0,1; 0,1,1; 0,1 accordingly.
I would approach this the following way:
Associate each child element (in ListProp property) with its parent
Group the parents by children
Project the results
var data = exampleList.SelectMany(a=>a.ListProp.Select(x=>new{Key = x.Prop.Number, Value = a}))
.GroupBy(x=>x.Key)
.Select(g=>new {Number = g.Key, Items = g.ToList()});

Categories