Find and map at the same time - c#

I have a data structure where Modules contain Units and Units contain Sections, and from a list of modules, I want to find the first module that contains at least one unit that contains at least one section, and I want to do something with the module, unit and section.
I initially tried to use modules.Find() for this, but it only tells me what the first non-empty Module is, so I'd have to lookup the Unit twice:
var module = modules.Find(m => m.Units.Exists(u => u.Sections.Count > 0));
if (module == null)
{
throw new Exception("there are no non-empty modules");
}
var unit = module.Units.Find(u => u.Sections.Count > 0);
var section = unit.Sections.First();
doSomeStuff(module, unit, section);
I eventually wrote my own function to do this:
private Tuple<Module, Unit, Section> getFirstModuleWithVisibleSection(List<Module> modules)
{
foreach (var module in modules)
{
foreach (var unit in module.Units)
{
var section = unit.Sections.FirstOrDefault();
if (section != null)
{
return new Tuple<Module, Unit, Section>(module, unit, section);
}
}
}
return null;
}
...
var res = getFirstModuleWithVisibleSection(modules);
if (res == null)
{
throw new Exception("no visible modules");
}
var module = res.Item1;
var unit = res.Item2;
var section = res.Item3;
doSomething(module, unit, section);
This is efficient but it's way more verbose than I was hoping for.
I'm more used to OCaml, where I would use List.find_map, which is like find, except instead of returning true/false you return null or not-null, and it returns the first not-null. In C# it would look something like this:
var (module, unit, section) =
modules.FindMap(module =>
module.Units.FindMap(unit =>
{
var section = unit.Sections.FirstOrDefault();
if (section == null)
{
return null;
}
return (module, unit, section);
}));
Is there a way to do this in C#?

What about:
var query = from m in modules
from u in m.Units
let s = u.Sections.FirstOrDefault()
where s != null
select new
{
m,
u,
s
};
var item = query.FirstOrDefault();

Certainly not elegant but it may meet the need.
public Module FirstModuleWithAUnitWithASection(IEnumerable<Module> modules)
=> modules.Where(module => module.Units != null)
.Select(module => module.Units.Where(unit => unit.Sections != null)
.Select(unit => unit.Sections.Select(section => module)
.First()).First()).First();

Related

Having trouble with an ICollection/IEnumerable operation - won't remove an occurrence

I'm using a FastObjectListView to enter S/Ns of units to a Disposition (sold, RMA, etc) and I enter a constant for the first S/N - "(Enter Serial)"
I'm using this same model in another section of code (RMA) but I'm missing something when trying to do the same operation for Disposition.
public UnitHistory RemoveUnit(Unit unit)
{
if (unit == null)
{
return null;
}
IEnumerable<UnitHistory> seq = AssociatedUnits.Where(p => p.Unit.Equals(unit));
var unitHistories = seq.ToList();
if (unitHistories.Any())
{
List<UnitHistory> collection = new List<UnitHistory>();
collection.AddRange(AssociatedUnits);
collection.Remove(unitHistories[0]);
AssociatedUnits.Clear();
foreach (UnitHistory history in collection)
{
AssociatedUnits.Add(history);
}
unitHistories[0].Disposition = null;
DisassociatedUnits.Add(unitHistories[0]);
unitHistories[0].Unit.UnitHistory.Remove(unitHistories[0]);
return unitHistories[0];
}
return null;
}
The code won't remove unitHistories[0] from collection.
This model does work in the following code:
public RmaRepairNotes RemoveUnit(Unit unit)
{
if (unit == null)
{
return null;
}
IEnumerable<RmaRepairNotes> seq = AssociatedUnits.Where(p => p.Unit.Equals(unit));
var unitRmaHistories = seq.ToList();
if (unitRmaHistories.Any())
{
List<RmaRepairNotes> collection = new List<RmaRepairNotes>();
collection.AddRange(AssociatedUnits);
collection.Remove(unitRmaHistories[0]);
AssociatedUnits.Clear();
foreach (RmaRepairNotes note in collection)
{
AssociatedUnits.Add(note);
}
unitRmaHistories[0].Rma = null;
DisassociatedUnits.Add(unitRmaHistories[0]);
unitRmaHistories[0].Unit.RmaRepairNotes.Remove(unitRmaHistories[0]);
return unitRmaHistories[0];
}
return null;
}
AssociatedUnits is an ICollection in both classes.
EDIT - SOLUTION: I found a logic error in the Equals code of the UnitHistory class. Now it functions perfectly.
The UnitHistory Class had a logic error in the Equals function. Now that objects could be identified as being equal, the code functions perfectly.

IEnumerable failed to set element

I have a ViewModel that contains different elements inside different tables that I tend to assign to it by query.
My problem is that I can't do this with IEnumerable (in GetAll() below), it keeps returning me null for RoomCode but for a single item (in GetDeviceId() below) then it works fine.
public IEnumerable<DeviceViewModel> GetAll()
{
var result = deviceRepository.GetAll().Select(x => x.ToViewModel<DeviceViewModel>());
for(int i = 0; i < result.Count(); i++)
{
int? deviceID = result.ElementAt(i).DeviceId;
result.ElementAt(i).RoomCode = deviceRepository.GetRoomCode(deviceID);
}
return result;
}
public DeviceViewModel GetDeviceID(int deviceID)
{
var result = new DeviceViewModel();
var device = deviceRepository.Find(deviceID);
if (device != null)
{
result = device.ToViewModel<DeviceViewModel>();
result.RoomCode = deviceRepository.GetRoomCode(deviceID);
}
else
{
throw new BaseException(ErrorMessages.DEVICE_LIST_EMPTY);
}
return result;
}
public string GetRoomCode(int? deviceID)
{
string roomCode;
var roomDevice = dbContext.Set<RoomDevice>().FirstOrDefault(x => x.DeviceId == deviceID && x.IsActive == true);
if (roomDevice != null)
{
var room = dbContext.Set<Room>().Find(roomDevice.RoomId);
roomCode = room.RoomCode;
}
else
{
roomCode = "";
}
return roomCode;
}
First, you need to materialize the query to a collection in local memory. Otherwise, the ElementAt(i) will query the db and give back some kind of temporary object each time it is used, discarding any change you do.
var result = deviceRepository.GetAll()
.Select(x => x.ToViewModel<DeviceViewModel>())
.ToList(); // this will materialize the query to a list in memory
// Now modifications of elements in the result IEnumerable will be persisted.
You can then go on with the rest of the code.
Second (and probably optional), I also recommend for clarity to use foreach to enumerate the elements. That's the C# idiomatic way to loop through an IEnumerable:
foreach (var element in result)
{
int? deviceID = element.DeviceId;
element.RoomCode = deviceRepository.GetRoomCode(deviceID);
}

Capturing which left hand item in the null-coalescing operator successfully assigned variable

I'm using the ?? operator to try and assign an object based on the best match found in a list.
I have various matching rules but have simplified this for the example:
objectImTryingToSet =
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking numerous things*/) ?? //rule1
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking different numerous things*/) ?? //rule2
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking again different numerous things*/); //rule3
For debugging purposes, I would like to store a string logging which rule was the one which successfully assigned objectImTryingToSet as I have a bug wherby in one scenario the object is being assigned when it shouldn't be and its a real headache manually trying to sift through all these rules to find out where the incorrect assignment lies.
So I basically want, pseudo:
string ruleThatMatched = null;
objectImTryingToSet =
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking numerous things*/) ?? if (objectImTryingToSet != null) { ruleThatMatched = "rule1" } //rule1
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking different numerous things*/) ?? if (objectImTryingToSet != null) { ruleThatMatched = "rule2" } //rule2
MyListOfPotentialMatches.FirstOrDefault(*/lamda checking again different numerous things*/); if (objectImTryingToSet != null) { ruleThatMatched = "rule3"} //rule3
//tried all the rules and still no match
if (objectImTryingToSet == null)
{
ruleThatMatched = "no rule managed to find a match";
}
Is this even possible using the ?? operator?
You can do it like this:
var res =
MyListOfPotentialMatches.Select(v => new {r=1, v}).FirstOrDefault(/*lamda checking numerous things*/) ??
MyListOfPotentialMatches.Select(v => new {r=2, v}).FirstOrDefault(/*lamda checking different numerous things*/) ??
MyListOfPotentialMatches.Select(v => new {r=3, v}).FirstOrDefault(/*lamda checking again different numerous things*/);
if (res != null) {
var ruleNumber = res.r;
objectImTryingToSet = res.v;
}
The key is Select which pairs up the result with a hard-coded rule number.
Note that you could do it without ?? operator, too:
var firstChoice = MyListOfPotentialMatches.Select(v => new {r=1, v}).Where(/*lamda checking numerous things*/);
var secondChoice = MyListOfPotentialMatches.Select(v => new {r=2, v}).Where(/*lamda checking different numerous things*/);
var thirdChoice = MyListOfPotentialMatches.Select(v => new {r=3, v}).Where(/*lamda checking again different numerous things*/);
var res = firstChoice.Concat(secondChoice).Concat(thirdChoice).FirstOrDefault();
if (res != null) {
var ruleNumber = res.r;
objectImTryingToSet = res.v;
}
I made a custom Extension method that wraps up your kind of logic. It's not pretty, but you can call this instead of the standard FirstOrDefault. It takes an extra string as an out parameter, and another string as the debugging message you want.
public static T GetFirstWithMessage<T>(this IEnumerable<T> collection,
Func<T, bool> matchFunc,
out string outputString,
string message)
{
var match = collection.FirstOrDefault(matchFunc);
outputString = match == null ? null : message;
return match;
}
Then you can chain these together with something like this.
string matchedRule;
var matchedFruit = fruits.GetFirstWithMessage(f => f.Count < 1, out matchedRule, "Out of stock")
?? fruits.GetFirstWithMessage(f => f.Name.Length > 10, out matchedRule, "Long name")
?? fruits.GetFirstWithMessage(f => !f.IsFresh, out matchedRule, "Rotten Fruit")
?? fruits.GetFirstWithMessage(f => f.Count > 24, out matchedRule, "Big group");
A demo
(Edited so that it looks closer to your pseudo code, but you'll have to fill in some blanks, because I don't know the type of your object)
string ruleThatMatched = null;
Func<string, TypeOfObjectImTryingToSet, TypeOfObjectImTryingToSet> getAndTrackRule =
(ruleText, obj) =>
{
ruleThatMatched = ruleText;
return obj;
};
var objectImTryingToSet =
getAndTrackRule("rule1", MyListOfPotentialMatches.FirstOrDefault(*/lamda checking numerous things*/)) ??
getAndTrackRule("rule2", MyListOfPotentialMatches.FirstOrDefault(*/lamda checking different numerous things*/)) ??
getAndTrackRule("rule3", MyListOfPotentialMatches.FirstOrDefault(*/lamda checking again different numerous things*/));
if (objectImTryingToSet == null)
{
Console.WriteLine("no rule managed to find a match");
}
else
{
Console.WriteLine(string.Format("Final value {0} found by applying rule {1}", objectImTryingToSet, ruleThatMatched));
}
you should create 3 checking functions with out argument.
function bool check1(YourObject obj, out string ruleMatched)
{
ruleMatched = "rule1";
return /* checking numerous things */;
}
// same for each checking
string ruleMatched;
objectImTryingToSet =
MyListOfPotentialMatches.FirstOrDefault(x => check1(x, out ruleMatched)) ?? //rule1
MyListOfPotentialMatches.FirstOrDefault(x => check2(x, out ruleMatched)) ?? //rule2
MyListOfPotentialMatches.FirstOrDefault(x => check3(x, out ruleMatched)); //rule3
Are you looking for something like this?
static void Main(string[] args)
{
List<int?> intList = new List<int?>() { 3, 4};
Func<int?, bool> rule = null;
var value = intList.FirstOrDefault(rule = (i => i == 1))
?? intList.FirstOrDefault(rule = (i => i == 2))
?? intList.FirstOrDefault(rule = (i => i == 3))
?? intList.FirstOrDefault(rule = (i => i == 4));
}
As a note, this is only for exploring what you can do with the language. I wouldn't recommend actually doing this in production code. This is harder to read than it needs to be.
Here's another approach you can take
class Program
{
static void Main(string[] args)
{
List<int?> intList = new List<int?>() { 3, 4};
List<RuleModel> Rules = new List<RuleModel>();
Rules.Add(new RuleModel{Name = "Rule1", Rule = (i => i == 1)});
Rules.Add(new RuleModel{Name = "Rule2", Rule = (i => i == 2)});
Rules.Add(new RuleModel{Name = "Rule3", Rule = (i => i == 3)});
Rules.Add(new RuleModel{Name = "Rule4", Rule = (i => i == 4)});
int? valueToSet = null;
var ruleUsed = Rules.FirstOrDefault(r => (valueToSet = intList.FirstOrDefault(r.Rule)) != null);
}
}
public class RuleModel
{
public Func<int?, bool> Rule { get; set; }
public string Name { get; set; }
}

Finding all identifiers containing part of the token

I know I can get a string from resources using
Resources.GetIdentifier(token, "string", ctx.ApplicationContext.PackageName)
(sorry, this is in C#, it's part of a Xamarin.Android project).
I know that if my elements are called foo_1, foo_2, foo_3, then I can iterate and grab the strings using something like
var myList = new List<string>();
for(var i = 0; i < 4; ++i)
{
var id = AppContent.GetIdentifier(token + i.ToString(), "string", "package_name");
if (id != 0)
myList.Add(AppContext.GetString(id));
}
My issue is that my token names all begin with "posn." (the posn can denote the position of anything, so you can have "posn.left_arm" and "posn.brokenose"). I want to be able to add to the list of posn elements, so I can't really store a list of the parts after the period. I can't use a string-array for this either (specific reason means I can't do this).
Is there a way that I can use something akin to "posn.*" in the getidentifer call to return the ids?
You can use some reflection foo to get what you want. It is not pretty at all but it works. The reflection stuff is based on https://gist.github.com/atsushieno/4e66da6e492dfb6c1dd0
private List<string> _stringNames;
private IEnumerable<int> GetIdentifiers(string contains)
{
if (_stringNames == null)
{
var eass = Assembly.GetExecutingAssembly();
Func<Assembly, Type> f = ass =>
ass.GetCustomAttributes(typeof(ResourceDesignerAttribute), true)
.OfType<ResourceDesignerAttribute>()
.Where(ca => ca.IsApplication)
.Select(ca => ass.GetType(ca.FullName))
.FirstOrDefault(ty => ty != null);
var t = f(eass) ??
AppDomain.CurrentDomain.GetAssemblies().Select(ass => f(ass)).FirstOrDefault(ty => ty != null);
if (t != null)
{
var strings = t.GetNestedTypes().FirstOrDefault(n => n.Name == "String");
if (strings != null)
{
var fields = strings.GetFields();
_stringNames = new List<string>();
foreach (var field in fields)
{
_stringNames.Add(field.Name);
}
}
}
}
if (_stringNames != null)
{
var names = _stringNames.Where(s => s.Contains(contains));
foreach (var name in names)
{
yield return Resources.GetIdentifier(name, "string", ComponentName.PackageName);
}
}
}
Then somewhere in your Activity you could do:
var ids = GetIdentifiers("action").ToList();
That will give you all the String Resources, which contain the string action.

Search by multiple fields

I'm working on a project and implemented a search by multiple fields in MVC, using LINQ like so:
public ActionResult SearchResult(SearchViewModel model)
{
List<Requisition> emptyList = new List<Requisition>();
if (model.RequisitionID > 0
|| model.Department > 0
|| model.Status > 0
|| model.RequisitionedBy != null)
{
var results = db.Requisitions.Where(x => x.RequisitionId > 0);
results = ProcessSearchInput(model, results);
return PartialView(results.ToList());
}
return PartialView(emptyList);
}
Helper:
private static IQueryable<Requisition> ProcessSearchInput(SearchViewModel model, IQueryable<Requisition> results)
{
if (model.Department > 0)
results = results.Where(x => x.Department == model.Department);
if (model.RequisitionedBy != null)
results = results.Where(x => x.Requisitioned_By.Contains(model.RequisitionedBy));
if (model.Status > 0)
results = results.Where(x => x.Status.Contains(model.Status.ToString()));
return results;
}
This code works fine.
However, if I add an extra search field to the form, I would also need to add a separate if statement in the controller.
With the current approach, the ProcessSearchInput method will contain too many if statements.
Is there a better way to handle a search with multiple fields?
Your current approach violates the open closed principle. The solution is to create a dynamic filter like in this example. However that is a complicated solution that is worth only if you are going to add more and more filters along the way. If not then don't bother.
I agree with previous comments: your current solution is probably the way to go.
In the real world, you'll soon have to implement filters like 'all customers having either a billing- or a shipping-address in New York', and more complicated stuff. By then, all clever generic stuff will just be in the way.
However, if you promise never to use this in production code:
you can save a lot of typing by using a query by example, where you specify the filter as an instance of the type your source contains:
var example = new Requisition { Department = 8, Requisitioned_By ="john" };
var result = db.Requisitions.FilterByExample(example);
This is a simple implementation:
public static class FilterByExampleHelper
{
public static IQueryable<T> FilterByExample<T>(this IQueryable<T> source, T example) where T : class
{
foreach (var property in typeof(T).GetProperties(BindingFlags.Public|BindingFlags.Instance).Where(p => p.CanRead))
{
ConstantExpression valueEx = null;
var propertyType = property.PropertyType;
if (propertyType.IsValueType)
{
var value = property.GetValue(example);
if (value != null &&
!value.Equals(Activator.CreateInstance(propertyType)))
{
valueEx = Expression.Constant(value, propertyType);
}
}
if (propertyType == typeof(string))
{
var value = property.GetValue(example) as string;
if (!string.IsNullOrEmpty(value))
{
valueEx = Expression.Constant(value);
}
}
if (valueEx == null)
{
continue;
}
var parameterEx = Expression.Parameter(typeof(T));
var propertyEx = Expression.Property(parameterEx, property);
var equalsEx = Expression.Equal(propertyEx, valueEx);
var lambdaEx = Expression.Lambda(equalsEx, parameterEx) as Expression<Func<T, bool>>;
source = source.Where(lambdaEx);
}
return source;
}
}

Categories