Linq GroupBy include empty keys - c#

I've got some elements of a hierarchy. These Elements I want to group.
cuttingTools = machines.SelectMany(x0 => x0.Operations)
.SelectMany(x1 => x1.Tools)
.SelectMany(x2 => x2.CuttingTools)
.GroupBy(x => x.Tool.Operation.Machine) as IQueryable<IGrouping<T, CuttingTool>>;
But I've got one big problem. If a machine does not have any child operations, tools or cuttingtools the result does not contain the empty groups. For example:
Machine Tool
M1 T1
M1 T2
M1 T3
M2 T1
M2 T2
M3 Null
M1 [T1, T2, T3]
M2 [T1, T2]
M3 [None]
But currently, the M3 won't appear in the result. How can I include M3 even if it has no elements.

I believe you need to create some empty placeholder value for your empty elements. DefaultIfEmpty can do this. Note that in sample below I group directly by parent machine, without accessing tool internal properties (x.Tool.Operation.Machine in your case). If this is not possible in your scenario - you would need to somehow dynamically create and initialize default values with references to parent.
using System;
using System.Linq;
class Machine
{
public string MachineName;
public Operation[] Operations;
}
class Operation
{
public string OperationName;
public Tool[] Tools;
}
class Tool
{
public string ToolName;
public Tool(string name) { ToolName = name; }
}
class App
{
static void Main()
{
var machines = new[]
{
new Machine {
MachineName = "A", Operations = new[] { new Operation { OperationName = "O1", Tools = new[] { new Tool("T1") } } }
},
new Machine {
MachineName = "B", Operations = new[] { new Operation { OperationName = "O3", Tools = new Tool[0] } }
},
new Machine {
MachineName = "C", Operations = new Operation[0]
}
};
var defaultOp = new Operation() { Tools = new Tool[0] };
var defaultTool = new Tool("");
var res =
from m in machines
from o in m.Operations.DefaultIfEmpty(defaultOp)
from t in o.Tools.DefaultIfEmpty(defaultTool)
group t by m.MachineName;
foreach(var g in res)
{
Console.WriteLine(g.Key + "=" + string.Join(";", g.Select(x => x.ToolName)));
}
}
}
Output:
A=T1
B=
C=

Related

Get common records from list of list in linq

I have a list contains another list where I supposed to get the common elements.
Class model:
PlanInfo has shiftName, ilist of Plan.
Plan has start time, end time
public class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
public class PlanInfo
{
public string ShiftName { get; set; }
public IList<Plan> lstPlan { get; set; }
}
iList of PlanInfo contains
[“shift1”, [1000,1200]],
[“shift2”,[1000,1100]],
[“shift3”,[1000,1200]]
Expected output in this should be empty since 1000,1200 doesn’t exist in shift2
[“shift1”, [1000,1200]],
[“shift2”,[[1000,1200],[1000,1100]],
[“shift3”,[1000,1200]]
Should return [1000,1200] since it’s common in all lists.
I tried using intersect, but here IList<PlanInfo is not fixed length. it could have more than one records.
Kindly suggest me which LINQ query serve the above result
Hmm, If I understand the requirements: Given a list of PlanInfo, find any Plans common to all PlanInfo...
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.ShiftName, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
This assumes that a Plan can only be counted once within a PlanInfo. (No duplicate plans) This also assumes that the plan info references for the same start/end times are pointing to the same object instance. If not, then you cannot group on the Plan, you will need a unique key (Like a plan ID) to group on. If these are EF entities pulled from a DbContext then they will be the same reference.
First get the total # of plan infos. In your example this would return 3.
Next, for all plan infos, use SelectMany to fetch the Plans, but compose that down into the PlanInfo.ShiftName + the Plan. This flattens your one to many. Next group by the Plan so that we can count the # of PlanInfos that each Plan appears in. Any/all counts that match the total number of PlanInfos means a Plan that appears in all PlanInfos, Select the Key to get that grouped Plan(s) and that should have it.
Edit: adding an example...
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2 = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
private class Plan
{
public int Start { get; set; }
public int End { get; set; }
}
private class PlanInfo
{
public string Name { get; set; }
public List<Plan> Plans { get; set; }
}
That was the test I had run using these stub classes. In this case the test will return back 1 match, for the Plan 2 value.
To outline the issue with ensuring plan references for the same start/end times match: If the setup looked like this:
[Test]
public void TestPlanCheck()
{
var plan1 = new Plan { Start = 1, End = 2 };
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = new Plan { Start = 2, End = 3 };
var plan3 = new Plan { Start = 3, End = 4 };
var planInfos = new List<PlanInfo>
{
new PlanInfo{ Name = "Test1", Plans = new []{ plan1, plan2A}.ToList() },
new PlanInfo{Name = "Test2", Plans = new []{plan2B, plan3}.ToList()},
new PlanInfo{Name = "Test3", Plans = new []{ plan3, plan2B}.ToList() }
};
var totalPlanInfos = planInfos.Count();
var commonPlans = planInfos
.SelectMany(x => x.Plans
.Select(p => new { x.Name, Plan = p }))
.GroupBy(x => x.Plan)
.Where(x => x.Count() == totalPlanInfos)
.Select(x => x.Key)
.ToList();
}
In this case even though plan 2A and 2B have the same start/end time, the group by would not group them together because they represent 2 references to 2 objects. This though would be fine:
var plan2A = new Plan { Start = 2, End = 3 };
var plan2B = plan2A;
Both point to the same reference. If you do have different references for the same plan ranges, you would need a planID then group on a PlanId. Ideally though I would check why the references don't match because they should to avoid potential errors based on assumptions of equality.
One can use Aggregate with Intersect on PlanInfo.Plans like:
var plansCommon = planInfoList.Select(p => p.Plans)
.Aggregate<IEnumerable<Plan>>((p1, p2) =>
p1.Intersect(p2, new PlanComparer()))
.ToList();
// Implement IEqualityComparer
class PlanComparer : IEqualityComparer<Plan>
{
public bool Equals(Plan x, Plan y)
{
if (x.Start == y.Start &&
x.End == y.End)
return true;
return false;
}
public int GetHashCode(Plan obj)
{
return obj.Start.GetHashCode() ^ obj.End.GetHashCode();
}
}
The Intersect will recursively apply on Plans list of each PlanInfo to provide list of Plan common across all.

Use workflow to evaluate dynamic expression

I would like to pass an object and expression into a dynamically created workflow to mimic the Eval function found in many languages. Can anyone help me out with what I am doing wrong? The code below is a very simple example if taking in a Policy object, multiple its premium by 1.05, then return the result. It throws the exception:
Additional information: The following errors were encountered while processing the workflow tree:
'DynamicActivity': The private implementation of activity '1: DynamicActivity' has the following validation error: Value for a required activity argument 'To' was not supplied.
And the code:
using System.Activities;
using System.Activities.Statements;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Policy p = new Policy() { Premium = 100, Year = 2016 };
var inputPolicy = new InArgument<Policy>();
var theOutput = new OutArgument<object>();
Activity dynamicWorkflow = new DynamicActivity()
{
Properties =
{
new DynamicActivityProperty
{
Name="Policy",
Type=typeof(InArgument<Policy>),
Value=inputPolicy
}
},
Implementation = () => new Sequence()
{
Activities =
{
new Assign()
{
To = theOutput,
Value=new InArgument<string>() { Expression = "Policy.Premium * 1.05" }
}
}
}
};
WorkflowInvoker.Invoke(dynamicWorkflow);
}
}
public class Policy
{
public int Premium { get; set; }
public int Year { get; set; }
}
}
You can use Workflow Foundation to evaluate expressions, but it is far easier to use almost any other option.
The key issue at play with your code was that you were not trying to evaluate the expression (with either VisualBasicValue or CSharpValue). Assigning InArgument`1.Expression is an attempt to set the value - not to set the value to the result of an expression.
Keep in mind that compiling expressions is fairly slow (>10ms), but the resultant compiled expression can be cached for quick executions.
Using Workflow:
class Program
{
static void Main(string[] args)
{
// this is slow, only do this once per expression
var evaluator = new PolicyExpressionEvaluator("Policy.Premium * 1.05");
// this is fairly fast
var policy1 = new Policy() { Premium = 100, Year = 2016 };
var result1 = evaluator.Evaluate(policy1);
var policy2 = new Policy() { Premium = 150, Year = 2016 };
var result2 = evaluator.Evaluate(policy2);
Console.WriteLine($"Policy 1: {result1}, Policy 2: {result2}");
}
}
public class Policy
{
public double Premium, Year;
}
class PolicyExpressionEvaluator
{
const string
ParamName = "Policy",
ResultName = "result";
public PolicyExpressionEvaluator(string expression)
{
var paramVariable = new Variable<Policy>(ParamName);
var resultVariable = new Variable<double>(ResultName);
var daRoot = new DynamicActivity()
{
Name = "DemoExpressionActivity",
Properties =
{
new DynamicActivityProperty() { Name = ParamName, Type = typeof(InArgument<Policy>) },
new DynamicActivityProperty() { Name = ResultName, Type = typeof(OutArgument<double>) }
},
Implementation = () => new Assign<double>()
{
To = new ArgumentReference<double>() { ArgumentName = ResultName },
Value = new InArgument<double>(new CSharpValue<double>(expression))
}
};
CSharpExpressionTools.CompileExpressions(daRoot, typeof(Policy).Assembly);
this.Activity = daRoot;
}
public DynamicActivity Activity { get; }
public double Evaluate(Policy p)
{
var results = WorkflowInvoker.Invoke(this.Activity,
new Dictionary<string, object>() { { ParamName, p } });
return (double)results[ResultName];
}
}
internal static class CSharpExpressionTools
{
public static void CompileExpressions(DynamicActivity dynamicActivity, params Assembly[] references)
{
// See https://learn.microsoft.com/en-us/dotnet/framework/windows-workflow-foundation/csharp-expressions
string activityName = dynamicActivity.Name;
string activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
string activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
TextExpressionCompilerSettings settings = new TextExpressionCompilerSettings
{
Activity = dynamicActivity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// add assembly references
TextExpression.SetReferencesForImplementation(dynamicActivity, references.Select(a => (AssemblyReference)a).ToList());
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
if (results.HasErrors)
{
throw new Exception("Compilation failed.");
}
// attach compilation result to live activity
var compiledExpression = (ICompiledExpressionRoot)Activator.CreateInstance(results.ResultType, new object[] { dynamicActivity });
CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(dynamicActivity, compiledExpression);
}
}
Compare to the equivalent Roslyn code - most of which is fluff that is not really needed:
public class PolicyEvaluatorGlobals
{
public Policy Policy { get; }
public PolicyEvaluatorGlobals(Policy p)
{
this.Policy = p;
}
}
internal class PolicyExpressionEvaluator
{
private readonly ScriptRunner<double> EvaluateInternal;
public PolicyExpressionEvaluator(string expression)
{
var usings = new[]
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks"
};
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location))
.ToArray();
var options = ScriptOptions.Default
.AddImports(usings)
.AddReferences(references);
this.EvaluateInternal = CSharpScript.Create<double>(expression, options, globalsType: typeof(PolicyEvaluatorGlobals))
.CreateDelegate();
}
internal double Evaluate(Policy policy)
{
return EvaluateInternal(new PolicyEvaluatorGlobals(policy)).Result;
}
}
Roslyn is fully documented, and has the helpful Scripting API Samples page with examples.

variables not working inside Lambdas in Mono.Csharp script

thows Mono.Csharp.InternalError exception. InnerException basically says object reference not set. Any idea folks? code used:
using System;
using System.IO;
using Mono.CSharp;
using System.Reflection;
using System.Collections.Generic;
namespace TestMonoCSharp
{
public class testmodel
{
public string a {get;set;}
public double b {get;set;}
}
class MainClass
{
public static void Main (string[] args)
{
var tw = new StreamWriter(new MemoryStream());
tw.AutoFlush = true;
CompilerContext c = new CompilerContext(new CompilerSettings(), new StreamReportPrinter(tw));
var csc = new Evaluator(c);
csc.ReferenceAssembly(Assembly.GetExecutingAssembly());
csc.Run("using System;");
csc.Run("using System.Linq;");
csc.Run("using System.Collections.Generic;");
csc.Run("using TestMonoCSharp;");
var query = #"new System.Func<IEnumerable<testmodel>, IEnumerable<testmodel>>((pos) =>
{
var avg = pos.Average(x=>x.b);
//return pos.Where(x=>x.b < 3 ).ToArray(); //works
return pos.Where(x=>x.b < avg ).ToArray(); //doesn't work
});";
var list = new List<testmodel> () {new testmodel{ a = "a", b = 3}, new testmodel{ a = "a", b = 2} };
var func = csc.Evaluate(query) as Func<IEnumerable<testmodel>, IEnumerable<testmodel>>;
var val = func(list);
}
}
}
You are trying to evaluate a Delegate...
new System.Func<IEnumerable<string>, IEnumerable<string>>((pos) => {
var avg = pos.Average(x=>x.Length);
return pos.Where(x=>x.Length < avg ).ToArray(); //doesn't work
});
This causes an internal System.NullReferenceException in the mcs compiler as nothing can dynamically evaluated by that statement's execution as there is no context for it.
Cut/Paste this into Mono's csharp repl:
public class testmodel
{
public string a {get;set;}
public double b {get;set;}
}
testmodel[] list = {new testmodel{a="1",b=1}, new testmodel{a="22",b=2}, new testmodel{a="333",b=3}, new testmodel{a="4444",b=4}, new testmodel{a="55555", b=5}}
var averageEvaluator = new System.Func<IEnumerable<testmodel>, IEnumerable<testmodel>>((pos) => {
var avg = pos.Average(x=>x.b);
return pos.Where(x=>x.b < avg ).ToArray();
})
var results = averageEvaluator(list)
foreach(var x in results){ print(x.a);}

How to: Select Distinct objects<T> from multiple Lists<T> with 1 or more properties of same type

So this is a design question that has vexed me for hours, and I have to reach out to the group for an assist. I have a collection with thousands of shared entities, and need to retrieve a distinct list of managers from three different properties contained in two lists, two managers in the Stores list, and one manager from warehouse collection.
To simplify the problem, I've written a simple console program that highlights the challenge. I tossed it together, so yes, I know it's inefficient, but it demonstrates the problem:
public class Program
{
static void Main(string[] args)
{
DistributionGroup d = new DistributionGroup();
Console.WriteLine("====Store Managers====");
foreach(Manager m in d.Stores.Select(m => m.StoreManager).Distinct())
{
Console.WriteLine("{0}:{1}", m.Id, m.Name);
}
Console.WriteLine("=====Inv. Managers=====");
foreach (Manager m in d.Stores.Select(m => m.InventoryManager).Distinct())
{
Console.WriteLine("{0}:{1}", m.Id, m.Name);
}
Console.WriteLine("===Warehouse Managers===");
foreach (Manager m in d.Warehouses.Select(m => m.WarehouseManager).Distinct())
{
Console.WriteLine("{0}:{1}", m.Id, m.Name);
}
}
}
public class DistributionGroup
{
private Manager m1 = new Manager(1, "Bob Wilson");
private Manager m2 = new Manager(2, "Chris Warren");
private Manager m3 = new Manager(3, "Mike Olsen");
private Manager m4 = new Manager(4, "Aaron Erikson");
public List<RetailStore> Stores;
public List<Warehouse> Warehouses;
public DistributionGroup()
{
RetailStore s1 = new RetailStore(1, m1, m1);
RetailStore s2 = new RetailStore(2, m1, m3);
RetailStore s3 = new RetailStore(3, m2, m2);
Warehouse w1 = new Warehouse(1, m4);
Warehouse w2 = new Warehouse(2, m2);
Stores = new List<RetailStore>();
Stores.Add(s1);
Stores.Add(s2);
Stores.Add(s3);
Warehouses = new List<Warehouse>();
Warehouses.Add(w1);
Warehouses.Add(w2);
}
}
public class Manager
{
public int Id;
public string Name;
public Manager(int id, string name)
{
Id = id; Name = name;
}
}
public class RetailStore
{
public int Id;
public Manager StoreManager;
public Manager InventoryManager;
public RetailStore(int id, Manager mgr, Manager inventoryMgr)
{
Id = id;
StoreManager = mgr;
InventoryManager = inventoryMgr;
}
}
public class Warehouse
{
public int Id;
public Manager WarehouseManager;
public Warehouse(int id, Manager mgr)
{
Id = id;
WarehouseManager = mgr;
}
}
What I need to do is generate a distinct list of Managers from all three properties:
RetailStore.StoreManager
RetailStore.InventoryManager
Warehouse.WarehouseManager
So following the example, the console output would simply be:
1:Bob Wilson
2:Chris Warren
3:Mike Olsen
4:Aaron Erikson
I've been trying to figure out the syntax, but LINQ is not a strength, and I'm hoping the group can help me out.
Seems like you need to use Enumerable.Union
var allManagers = d.Stores.Select(m => m.StoreManager).Union(d.Stores.Select(m => m.InventoryManager))
.Union(d.Warehouses.Select(m => m.WarehouseManager));
foreach (Manager m in allManagers)
{
Console.WriteLine("{0}:{1}", m.Id, m.Name);
}
Note that Union returns unique values from input sequences so no need to call Distinct.

Combine the results of two columns in a select into one array with LINQ?

Basically I'm looking to select both string columns and put it all into a single array of strings. Right now I'm having to do two selects and combine the results. It isn't a huge deal, I just think it looks awkward. Any suggestions on how to accomplish the same goal with one linq statement? Here is a test case I'm using to mess around:
[TestFixture]
public class test {
public class Values {
public string Present { get; set; }
public string Previous { get; set; }
public bool Flag { get; set; }
}
[Test]
public void test1() {
var list = new List<Values> {
new Values { Present = "present1", Previous = "previous1", Flag = false },
new Values { Present = "present2", Previous = "previous2", Flag = false },
new Values { Present = "present3", Previous = "previous3", Flag = true },
new Values { Present = "present4", Previous = "previous4", Flag = true }
};
var r1 = list.Where(c => c.Flag).Select(c => c.Present);
var r2 = list.Where(c => c.Flag).Select(c => c.Previous);
var combined = r1.Concat(r2);
Assert.AreEqual(4, combined.Count());
}
}
An alternative solution with using SelectMany (it keeps the duplicates):
var combined = list.Where(c => c.Flag)
.SelectMany(c => new[] { c.Present, c.Previous });
Assert.AreEqual(4, combined.Count());
Does the final ordering of the strings in the list matter? If not, it seems like this would be the clearest way to accomplish this:
var strings = new List<String>();
foreach (var value in list.Where(c => c.Flag))
{
strings.Add(value.Present);
strings.Add(value.Previous);
}

Categories

Resources