lazily building up an enumerable with breaks in between - c#

I have a collection of objects IEnumerable<object> obs.
I have another collection of objects IEnumerable<object> data.
For each ob in obs I need to find the first item in data that has the same value in a certain property as ob. For example I could be looking for the first item in data that has the same ToString() value as ob. When the first item where the property values match is found, I do something with the found data item and then I check the next ob in obs. If none is found, I throw an error.
Here is a naive approach:
foreach (object ob in obs)
{
foreach (object dataOb in data)
if (ob.ToString() == dataOb.ToString())
{
... // do something with dataOb
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
The disadvantage is that I calculate dataOb.ToString() every time, which is unnecessary.
I could cache it:
IDictionary<object, string> dataToDataStr = new Dictionary<object, string>();
foreach (object dataObj in data) // collect all ToString values in advance
dataToDataStr.Add(dataObj, dataObj.ToString());
foreach (object ob in obs)
{
foreach (object dataOb in dataToDataStr.Keys)
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
The disadvantage is that I calculate all ToString() values even though it might not be necessary. I might find all matching data objects in the first half of the data collection.
How can I build up the dataToDataStr dictionary (or any other enumerable data structure that lets me retrieve both the object and its only-once-calculated ToString value) lazily?
Here is code (mixed with pseudocode) of what I have in mind:
IDictionary<object, string> dataToDataStr = new Dictionary<object, string>();
object lastProcessedDataOb = null;
foreach (object ob in obs)
{
foreach (object dataOb in dataToDataStr.Keys)
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
foreach (object dataOb in data STARTING AFTER lastProcessedDataOb)
// if lastProcessedDataOb == null, start with the first entry of data
{
dataToDataStr.Add(dataOb, dataOb.ToString();
lastProcessedDataOb = dataOb;
if (ob.ToString() == dataToDataStr[dataOb])
{
... // do something with dataOb
goto ContinueOuter;
}
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}
I know it is easy if data was a LinkedList or any collection with indexed access (then I could store a linked list node or an index as lastProcessedDataOb), but it isn't - it is an IEnumerable. Maybe yield return can be used here?

If your collections are really large and you really don't want to evaluate ToString for each item of data, you could use the following approach:
Create cache of already calculated items
If certain item is found i cache - that's great, we have a match.
Otherwise - continue populating cache by iterating over data collection until we find a match. This can be efficiently be done with manually controlling **Enumerator** of data collection (instead of using foreach).
IEnumerable<object> obs;
IEnumerable<object> data;
Dictionary<string, object> dataCache = new Dictionary<string, object>();
var dataIterator = data.GetEnumerator();
foreach (var ob in obs)
{
var obText = ob.ToString();
object matchingDataItem = null;
if (!dataCache.TryGetValue(obText, out matchingDataItem))
{
while (dataIterator.MoveNext())
{
var currentData = dataIterator.Current;
var currentDataText = currentData.ToString();
if (!dataCache.ContainsKey(currentDataText)) // Handle the case when data collection contains duplicates
{
dataCache.Add(currentDataText, currentData);
if (currentDataText == obText)
{
matchingDataItem = currentData;
break;
}
}
}
}
if (matchingDataItem != null)
{
Console.WriteLine("Matching item found for " + obText);
}
else
{
throw new Exception("No matching data found.");
}
}
This way you can guarantee to iterate over data collection only to the point when all obs items are found and you won't evaluate ToString for each item more then once.
PS: I hope "ToString" is just for example and you have some complex calculations there, which is worth such complexities...

Totally forgot that LINQ uses lazy evaluation...
This should work (I use the new value tuple notation of C# 7):
IEnumerable<(object, string)> objStrPairs = data.Select(o => (o, o.ToString()));
foreach (object ob in obs)
{
foreach ((object, string) dataPair in objStrPairs)
if (ob.ToString() == objStrPairs.Item2)
{
... // do something with objStrPairs.Item1
goto ContinueOuter;
}
throw new Exception("No matching data found.");
ContinueOuter: ;
}

Related

How to use LINQ to filter property which is not null and get its value to run a method?

How do I create LINQ to call DoSomething() only once to reduce these duplicate codes? Note that there are some properties in nameValue which I don't need to do anything about it. DoSomething() is only applied for these 4 properties.
foreach (var nameValue in nameDetails.NameValue)
{
if (nameValue.FirstName != null)
{
DoSomething(nameValue.FirstName)
}
if (nameValue.MaidenName != null)
{
DoSomething(nameValue.MaidenName)
}
if (nameValue.MiddleName != null)
{
DoSomething(nameValue.MiddleName)
}
if (nameValue.Surname != null)
{
DoSomething(nameValue.Surname)
}
}
Move the condition into the function and you can write
foreach (var nameValue in nameDetails.NameValue)
{
DoSomethingMaybe(nameValue.FirstName);
DoSomethingMaybe(nameValue.MaidenName);
DoSomethingMaybe(nameValue.MiddleName);
DoSomethingMaybe(nameValue.Surname);
}
void DoSomethingMaybe(string value)
{
if (value != null)
{
DoSomething(value)
}
}
perfectly readable and no unnecessary overhead, no throwaway objects. Maybe not the answer you expect, but LINQ is not magic, that makes things better just be being there.
You may create an array of *names, then use SelectMany to flatten 2 dimensional array into IEnumerable<string> , e.g.:
var enumerable = nameDetails.NameValue.SelectMany(elem => new[]
{
elem.FirstName,
elem.MaidenName,
elem.MiddleName,
elem.Surname
}).Where(value => value != null);
foreach (var value in enumerable)
{
DoSomething(value);
}
My interpretation of your question is that you want to write DoSomething only once but still want to perform it more than once if more than one name part is not null.
You can refactor your code to this:
var names = nameDetails.NameValue
.SelectMany(nv => new string[] { nv.FirstName, nv.MaidenName, nv.MiddleName, nv.Surname })
.Where(name => name != null);
foreach (var name in names)
{
DoSomething(name);
}
This makes use of SelectMany which allows each item of the source enumerable to be mapped into multiple values which are then flattened into an enumerable of the mapped element value's type.

How to check an object of type List<Dictionary<string, string>> to set one value based off another value in the dictionary?

I have an object called reportData, which is holding the data for a report.
This object is of type
List<Dictionary<string, string>>
I need to add logic to manipulate reportData that when the value of the key "Type" is Withdrawal the value of the key "TransAmount" should have a minus sign before it.
I was thinking I can accomplish this with some linq but I am not having any success.
This is what I tried so far...
foreach (var kvp in reportData.SelectMany(m => m).Where(x => x.Value != null))
{
if (kvp.Value.Equals("Deposit"))
(
//Over here I need to set the value of the key "TransAmount" to have a minus sign before it, but I'm not sure how to go about it
)
}
Over here is a snapshot of the data that is being held in reportData. The showcased item in the list is of Type "Withdrawal", My code needs to deal with items in the list that are of Type "Deposit"
https://gyazo.com/a5183aa404e51672712d680dcd8ad6af
How about something like this ?
foreach (var dict in reportData)
{
var keys = new List<string>(dict.Keys);
foreach (string key in keys)
{
if (key == "Type")
{
if (dict[key] == "Deposit")
{
dict["TransAmount"] = "-" +dict["TransAmount"] ;
}
}
}
}
Try this https://dotnetfiddle.net/Ii0MR7
We only need one loop and one operation.
We take dictionaries one by one, in each dictionary we are looking for the specific key "Type" while at the same time trying to get its value to variable called type (that's precisely what TryGetValue does). It also returns true in case when element exists. At this point we only need to make sure that the value of it is the one we're looking for.
If yes - we get in to the code block and modify another value. If you're not familiar with $ string interpolation please check this article.
foreach (var dict in reportData)
{
if (dict.TryGetValue("Type", out var type)
&& type == "Withdrawal"
&& dict.TryGetValue("TransAmount", out var amt)
&& !string.IsNullOrEmpty(amt))
{
dict["TransAmount"] = $"-{dict["TransAmount"]}";
}
}
And yes you can do it with LINQ but it is not recommended, a good use-case for LINQ is data querying and for manipulating data it is better to use good old loops, nevertheless here is the code:
reportData = reportData.Select(d =>
{
if (d.TryGetValue("Type", out var type) && type == "Withdrawal")
{
d["TransAmount"] = $"-{d["TransAmount"]}";
}
return d;
}.ToList(); // this will create a new instance of List<Dictionary>

C# How do you loop through multiple dictionaries?

A standard dictionary would look like this:
public Dictionary<int, DictionarySetup> H = new Dictionary<int, DictionarySetup>()
{
{18000, new DictionarySetup { Some values }},
};
Ranging from A-T, all of these are in a class called DictionaryInit, right now I check the value if there's a match with this boolean:
public Boolean Dictionary_Test(Dictionary<int, DictionarySetup> AccountLexicon)
{
DictionarySetup ClassValues;
if (AccountLexicon.TryGetValue(MapKey, out ClassValues))
{
return true;
}
return false;
}
Now, I'm looking for a more efficient method to loop through each Dictionary and, if there's a match, get that particular dictionary for use in a subsequent method, this is what it looks like now in an if/else:
if(Dictionary_Test(theDictionary.C) == true)
{
Dictionary_Find(Account_String, rowindex, theBSDictionary.C, Cash_Value, txtCurrency.Text);
}
else if (Dictionary_Test(theDictionary.D) == true)
{
Dictionary_Find(Account_String, rowindex, theDictionary.D, Cash_Value, txtCurrency.Text); //Method that makes use of the dictionary values, above dictionary checks only if it exists
}
With dictionaries from A-T, that would be alot of if/else's! Is there a better way to do this? I've found one thread mentioning this same topic, by adding the dictionaries to a dictionary array[] then looping over it, but how do I get the name of the matching dictionary if a match is found or make my second method, Dictionary_Find, use the matching dictionary?
Another possible solution, you could use reflection to get each dictionary from A-T from the DictionaryInit class. Create an array that contains values A-T, loop through the array and use reflection to get the dictionary, and test that dictionary, if you find a match, return that dictionary and exit the loop. Something like:
var dicts = new[]{"A", "B", ......., "T"}
foreach (var dict in dicts)
{
var found = CheckDictionary(theDictionary, dict);
if (found != null)
{
Dictionary_Find(Account_String, rowindex, (Dictionary<int, DictionarySetup>)found, Cash_Value, txtCurrency.Text);
break;
}
}
public static object CheckDictionary(object dictClass, string dictName)
{
var theDictionary = dictClass.GetType().GetProperty(dictName).GetValue(dictClass, null);
return Dictionary_Test(theDictionary) ? theDictionary : null;
}
I've just quickly grabbed some code from a project I've done and modified it to suit but haven't tested it. Might need a few tweaks but hopefully gets you close!
// put dictionary A ~ T to a list of dictionary
List<Dictionary<int, DictionarySetup>> dictionaries = new List<Dictionary<int, DictionarySetup>>{A,B,C, ... , T}; // Replace ... with D,E,F, etc. until T
// iterate each dictionary and if found, exit the loop
foreach(var dict in dictionaries)
{
if(Dictionary_Test(dict))
{
Dictionary_Find(Account_String, rowindex, dict, Cash_Value, txtCurrency.Text);
break;
}
}

Can I rewrite this do-while loop as a foreach loop?

Is there a way to write this code more elegantly with a foreach loop? The "create a new entry" logic is thwarting me, because it needs to execute even if pendingEntries contains no items.
ItemDto itemToAdd; // an input parameter to the method
IEnumerator<Item> pendingEntries = existingPendingItems.GetEnumerator();
pendingEntries.MoveNext();
do // foreach entry
{
Item entry = pendingEntries.Current;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
}
else // create a new entry
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entry);
} while (pendingEntries.MoveNext());
This should be rewritten. I don't know what kind of collection you're using, but Current is undefined in your case since MoveNext could have returned false. As stated in the documentation:
Current is undefined under any of the following conditions:
The last call to MoveNext returned false, which indicates the end of the
collection.
Here is how I would rewrite it:
bool isEmpty = true;
foreach (Item entry in existingPendingItems)
{
ProcessEntry(entry, itemToAdd);
isEmpty = false;
}
if (isEmpty)
{
ProcessEntry(null, itemToAdd);
}
ProcessEntry contains the logic for a single entry, and is easily unit testable.
The algorithm is cleared to read.
The enumerable is still only enumerated once.
foreach (Item entry in existingPendingItems.DefaultIfEmpty())
{
Item entryToSave;
if (entry != null) // fold the itemToAdd into the existing entry
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
entryToSave = entry;
}
else // create a new entry
{
entryToSave = Mapper.Map<ItemDto, Item>(itemToAdd);
}
Save(entryToSave);
}
The key is the Enumerable.DefaultIfEmpty() call — this will return a sequence with a default (Item) item if the sequence is empty. This will be null for a reference type.
Edit: fixed bug mentioned by neotapir.
Personally I'd suggest something like this:
ItemDto itemToAdd; // an input parameter to the method
if (existingPendingItems.Any())
{
foreach(Item entry in existingPendingItems)
{
entry.Quantity += itemToAdd.Quantity
Save(entry);
}
}
else
{
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry);
}
I think this makes the intent of the code much clearer.
EDIT: Changed count to any as per suggestion. Also fixed the add quantity logic
I'd rewrite it as more standard while method. And you've forgot that IEnumerator<T> implements IDisposable, so you should dispose it.
foreach( Item entry in pendingEntries.Current)
{
if( entry != null)
entry.Quantity += itemToAdd.Quantity;
else
entry = Mapper.Map<ItemDto, Item>(itemToAdd);
Save(entry)
}
cant exactly test it without the items
var pendingEntries = existingPendingItems.Any()
? existingPendingItems
: new List<Item> { Mapper.Map<ItemDto, Item>(itemToAdd) };
foreach (var entry in pendingEntries)
{
entry.Quantity += itemToAdd.Quantity; // amongst other things
Save(entry);
}
The idea here is that you set yourself up for success before iterating. What are you going to iterate over? Either the existing entries, if there are any, or just a new entry otherwise.
By handling this up front, so you know you've got something with which to work, your loop stays very clean.

How to Remove unneccessary list using lambda and functional C# paradigm

Hello Functional C# Friends,
So this time i am trying to compact my code and write in more functional , lambda style, as well as i would like to avaoid creating unneccessary lists and classes and let compiler do the work for me. I did manage to convert some small piece of code in functional way but after that i dont have much idea as how to go about.
var errorList = new List<DataRow>();
IEnumerable<DataRow> resultRows = GetResultRows();
resultRows
.Filter(row => row.Field<string>("status").Equals("FAILURE", StringComparison.InvariantCultureIgnoreCase))
.ForEach(row => { errorList.Add(row); });
if (errorList.Count > 0)
{
var excludedBooks = new List<string>();
foreach (DataRow row in errorList)
{
if (ToUseBooksList.Contains((string)row["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
if (!excludedBooks.Contains((string)row["main_book"]))
{
excludedBooks.Add((string)row["main_book"]);
}
}
}
}
My Extension methods :
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
if (collection == null)
throw new ArgumentNullException("collection");
if (action == null)
throw new ArgumentNullException("action");
foreach (var item in collection)
action(item);
}
public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Predicate<T> func)
{
foreach (var item in source)
{
if (func(item))
yield return item;
}
}
I will highly appreciate if you can help me structing this code is more functional, lambda style.
Why on earth did you write you own Filter extension method when Where is available?
The ForEach extension method usually isn't worth the bother.
Eric Lippert has blogged about it, and his philosophical objection to it is that it looks like a side-effect free expression (like most Linq features) but it is actually a side-effecting imperative statement in disguise.
If you want to carry out an action for each item on a list, use the foreach statement. That's what it's for.
If you want to manipulate lists of actions, then you can do that, but then you want IEnumerable<Action>.
For the first part of your code, how about:
var errorList = GetResultRows().Where(row => row.Field<string>("status").Equals("FAILURE", StringComparison.InvariantCultureIgnoreCase)
.ToList();
You have a List<string> called excluded books. Use HashSet<string> instead, and you don't need to check if a string is already added to it:
var excludedBooks = new HashSet<string>();
foreach (DataRow row in errorList)
{
if (ToUseBooksList.Contains((string)row["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
excludedBooks.Add((string)row["main_book"]);
}
}
You can also filter the list with Where:
var excludedBooks = new HashSet<string>();
foreach (DataRow row in errorList.Where(r => ToUseBooksList.Contains((string)r["main_book"]))
{
BookCheckResults.AddRow(string.Format("Error for MainBook {0}, RiskType {1}",
row["main_book"], row["risk_type"]));
excludedBooks.Add((string)row["main_book"]);
}

Categories

Resources