How to quickly cut up and reassemble a C# List? - c#

The setup:
I have a session variable that carries a list of IDs, pipe-delimited. The IDs are related to views in my site, and related to a breadcrumb builder.
Session["breadCrumb"] = "1001|1002|1003|1004";
If I'm on the view that corresponds to 1002, I'd like to cut everything AFTER that id out of the session variable.
I'd thought to use something like:
var curView = "1002";
if (Session["breadCrumb"] != null) {
var crumb = Session["breadCrumb"].ToString().Split('|').ToList();
var viewExists = crumb.Any(c => c.Value == curView);
if (viewExists) {
//remove everything after that item in the array.
}
}
But I'm wide open to methodologies.

You could use TakeWhile to get back only the items from the splitted list that precede the currentView.
var curView = "1002";
if (Session["breadCrumb"] != null)
{
var crumb = Session["breadCrumb"].ToString().Split('|').ToList();
var viewExists = crumb.TakeWhile(c => c != curView).ToList();
viewExists.Add(curView);
string result = string.Join("|",viewExists);
}
While this approach works I think that also the previous answer (now wrongly deleted from Mr. Andrew Whitaker) was correct. Using IndexOf should be faster with less splitting, looping, joining strings. I suggest Mr Whitaker to undelete its answer.
EDIT
This is from the deleted answer from Mr.Whitaker.
I will repost here because I think that its approach is simpler and should give better perfomances, so future readers could see also this option.
var crumb = Session["breadCrumb"].ToString()
int index = crumb.IndexOf(curView);
if (index >= 0)
{
Session["breadCrumb"] = crumb.Substring(0, index + curView.Length);
}
If Andrew decide to undelete its answer I will be glad to remove this part. Just let me know.

You could just store a List<string> in the Session directly. This saves you from having to split/concat the string manually. I know this does not answer the question directly, but I believe it is a superior solution to that.
var curView = "1002";
var crumb = Session["breadCrumb"] as List<string>;
if (crumb != null) {
var viewExists = crumb.Any(c => c.Value == curView);
if (viewExists) {
// remove everything after that item in the array.
}
}

I almost regret this, but frankly I'd just go for a regular expression:
var result = Regex.Replace(input, "(?<=(\\||^)" + current + ")(?=\\||$).*", "");
This does not directly tell you if the current view existed in the input, but even though this is also possible with the regex in this particular instance another, dead simple test exists:
var viewExists = result.Length != current.Length;

Related

C# Mapping a list to numbered variables

Yes, I'm well aware Not to do this, but I have no choice. I'd agree that it's an XYZ issue, but since I can't update the service I have to use, it's out of my hands. I need some help to save some time, maybe learn something handy in the process.
I'm looking to map a list of models (items in this example) to what is essentially numbered variables of a service I'm posting to, in the example, that's the fields a part of new 'newUser'.
Additionally, there may not be always be X amount items in the list (On the right in the example), and yet I have a finite amount (say 10) of numbered variables from 'newUser' to map to (On the left in the example). So I'll have to perform a bunch of checks to avoid indexing a null value as well.
Current example:
if (items.Count >= 1 && !string.IsNullOrWhiteSpace(items[0].id))
{
newUser.itemId1 = items[0].id;
newUser.itemName1 = items[0].name;
newUser.itemDate1 = items[0].date;
newUser.itemBlah1 = items[0].blah;
}
else
{
// This isn't necessary, but this effectively what will happen
newUser.itemId1 = string.Empty;
newUser.itemName1 = string.Empty;
newUser.itemDate1 = string.Empty;
newUser.itemBlah1 = string.Empty;
}
if (items.Count >= 2 && !string.IsNullOrWhiteSpace(items[1].id))
{
newUser.itemId2 = items[1].id;
newUser.itemName2 = items[1].name;
newUser.itemDate2 = items[1].date;
newUser.itemBlah2 = items[1].blah;
}
// Removed the else to clean it up, but you get the idea.
// And so on, repeated many more times..
I looked into an example using Dictionary, but I'm unsure of how to map that to the model without just manually mapping all the variables.
PS: To all who come across this question, if you're implementing numbered variables in your API, please don't- it's wildly unnecessary and time consuming.
As an alternative to fiddling with the JSON, you could get down and dirty and use Reflection.
Given the following test data:
const int maxItemsToSend = 3;
class ItemToSend {
public string
itemId1, itemName1,
itemId2, itemName2,
itemId3, itemName3;
}
ItemToSend newUser = new();
record Item(string id, string name);
Item[] items = { new("1", "A"), new("2", "B") };
Using the rules you set forth in the question, we can loop through the projected fields as so:
// If `itemid1`,`itemId2`, etc are fields:
var fields = typeof(ItemToSend).GetFields();
// If they're properties, replace GetFields() with
// .GetProperties(BindingFlags.Instance | BindingFlags.Public);
for(var i = 1; i <= maxItemsToSend; i++){
// bounds check
var item = (items.Count() >= i && !string.IsNullOrWhiteSpace(items[i-1].id))
? items[i-1] : null;
// Use Reflection to find and set the fields
fields.FirstOrDefault(f => f.Name.Equals($"itemId{i}"))
?.SetValue(newUser, item?.id ?? string.Empty);
fields.FirstOrDefault(f => f.Name.Equals($"itemName{i}"))
?.SetValue(newUser, item?.name ?? string.Empty);
}
It's not pretty, but it works. Here's a fiddle.

Returning the specific string Any() is finding

What i am trying to do here is search in a few .txt files for certain string that may exist in the HTML of a webpage, the code is working fine:
Code:
var b_c = File.ReadAllLines(#"LogicFiles\Blogs\blogs.txt");
var f_t = File.ReadAllLines(#"LogicFiles\Forums\forums.txt");
if (b_c.Any(html.Contains))
{
platform_type = "BLOG";
}
if (f_t.Any(html.Contains))
{
platform_type = "FORUM";
}
The code works as intended, what i'm trying to do now is return the specific string that is found, 1 text file is 20 strings long, it would be good if i could see the string that was marked as found in the HTML, i cannot think of a way to do this, ior is it even possible with the code as it is? any help would be appreciated.
How about doing
var found = b_c.FirstOrDefault(html.Contains);
if(found != null)
{
Console.WriteLine(found);
}
Why don't you try with .FirstOrDefault(a => a.Contains("your string"))?
In the example below you can do anything you like with the variables.
Also I strongly suggest you to read more about naming conventions in C#. You can check https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions for example.
var b_c = File.ReadAllLines(#"LogicFiles\Blogs\blogs.txt");
var f_t = File.ReadAllLines(#"LogicFiles\Forums\forums.txt");
var blog = b_c.FirstOrDefault(html.Contains);
if (blog != null)
{
platform_type = "BLOG";
}
var forum = f_t.FirstOrDefault(html.Contains);
if (forum != null)
{
platform_type = "FORUM";
}

Why does this EF upsert logic result in deletions instead of updates?

The following code results in deletions instead of updates.
My question is: is this a bug in the way I'm coding against Entity Framework or should I suspect something else?
Update: I got this working, but I'm leaving the question now with both the original and the working versions in hopes that I can learn something I didn't understand about EF.
In this, the original non working code, when the database is fresh, all the additions of SearchDailySummary object succeed, but on the second time through, when my code was supposedly going to perform the update, the net result is a once again empty table in the database, i.e. this logic manages to be the equiv. of removing each entity.
//Logger.Info("Upserting SearchDailySummaries..");
using (var db = new ClientPortalContext())
{
foreach (var item in items)
{
var campaignName = item["campaign"];
var pk1 = db.SearchCampaigns.Single(c => c.SearchCampaignName == campaignName).SearchCampaignId;
var pk2 = DateTime.Parse(item["day"].Replace('-', '/'));
var source = new SearchDailySummary
{
SearchCampaignId = pk1,
Date = pk2,
Revenue = decimal.Parse(item["totalConvValue"]),
Cost = decimal.Parse(item["cost"]),
Orders = int.Parse(item["conv1PerClick"]),
Clicks = int.Parse(item["clicks"]),
Impressions = int.Parse(item["impressions"]),
CurrencyId = item["currency"] == "USD" ? 1 : -1 // NOTE: non USD (if exists) -1 for now
};
var target = db.Set<SearchDailySummary>().Find(pk1, pk2) ?? new SearchDailySummary();
if (db.Entry(target).State == EntityState.Detached)
{
db.SearchDailySummaries.Add(target);
addedCount++;
}
else
{
// TODO?: compare source and target and change the entity state to unchanged if no diff
updatedCount++;
}
AutoMapper.Mapper.Map(source, target);
itemCount++;
}
Logger.Info("Saving {0} SearchDailySummaries ({1} updates, {2} additions)", itemCount, updatedCount, addedCount);
db.SaveChanges();
}
Here is the working version (although I'm not 100% it's optimized, it's working reliably and performing fine as long as I batch it out in groups of 500 or less items in a shot - after that it slows down exponentially but I think that just may be a different question/subject)...
//Logger.Info("Upserting SearchDailySummaries..");
using (var db = new ClientPortalContext())
{
foreach (var item in items)
{
var campaignName = item["campaign"];
var pk1 = db.SearchCampaigns.Single(c => c.SearchCampaignName == campaignName).SearchCampaignId;
var pk2 = DateTime.Parse(item["day"].Replace('-', '/'));
var source = new SearchDailySummary
{
SearchCampaignId = pk1,
Date = pk2,
Revenue = decimal.Parse(item["totalConvValue"]),
Cost = decimal.Parse(item["cost"]),
Orders = int.Parse(item["conv1PerClick"]),
Clicks = int.Parse(item["clicks"]),
Impressions = int.Parse(item["impressions"]),
CurrencyId = item["currency"] == "USD" ? 1 : -1 // NOTE: non USD (if exists) -1 for now
};
var target = db.Set<SearchDailySummary>().Find(pk1, pk2);
if (target == null)
{
db.SearchDailySummaries.Add(source);
addedCount++;
}
else
{
AutoMapper.Mapper.Map(source, target);
db.Entry(target).State = EntityState.Modified;
updatedCount++;
}
itemCount++;
}
Logger.Info("Saving {0} SearchDailySummaries ({1} updates, {2} additions)", itemCount, updatedCount, addedCount);
db.SaveChanges();
}
The thing that keeps popping up in my mind is that maybe the Entry(entity) or Find(pk) method has some side effects? I should probably be consulting the documentation but any advice is appreciated..
It's a slight assumption on my part (without looking into your models/entities), but have a look at what's going on within this block (see if the objects being attached here are related to the deletions):
if (db.Entry(target).State == EntityState.Detached)
{
db.SearchDailySummaries.Add(target);
addedCount++;
}
Your detached object won't be able to use its navigation properties to locate its related objects; you'll be re-attaching an object in a potentially conflicting state (without the correct relationships).
You haven't mentioned what is being deleted above, so I may be way off. Just off out, so this is a little rushed, hope there's something useful in there.

List.Find() issue

I am wondering why I have this problem with my list.
I am trying to retrieve the object for the previous index, so -1;
Is that the good way to do ?
thePreviousList.Find(previousItem => previousItem.Id - 1);
No, the best way is to use the index:
var item = thePreviousList[indexofItem];
var previousItem = thePreviousList[indexofItem -1];
But probably i've misunderstood your requirement.
If you want to find an item by it's ID(assuming that it has this property):
var idToFind=4711;
var thisIndex = thePreviousList.Where((i, index) => i.ID==idToFind)
.Select((i, index) => index).FirstOrDefault();
if(thisIndex > 0)
{
var previousItem = thePreviousList[thisIndex - 1];
}
Find
Works with predicates (msdn)
It sounds like you want:
if (currentItem.ID != 0)
{
var previousItem = list[currentItem.ID - 1];
// Use previous item...
}
else
{
// This was the first item in the list.
}
No need to go over the whole list, if you're sure that the ID matches the index in the list.
Judging by the comment you made on Tim's answer, you want something like the following:
thePreviousList.ElementAt(thePreviousList.Count - 2);
Note that the -2 is because your indexing (used by ElementAt) is 0 based (that justifies -1) and then you want the penultimate element (another -1).

Searching a list of strings in C#

So I want to use one of these LINQ functions with this List<string> I have.
Here's the setup:
List<string> all = FillList();
string temp = "something";
string found;
int index;
I want to find the string in all that matches temp when both are lower cased with ToLower(). Then I'll use the found string to find it's index and remove it from the list.
How can I do this with LINQ?
I get the feeling that you don't care so much about comparing the lowercase versions as you do about just performing a case-insensitive match. If so:
var listEntry = all.Where(entry =>
string.Equals(entry, temp, StringComparison.CurrentCultureIgnoreCase))
.FirstOrDefault();
if (listEntry != null) all.Remove(listEntry);
OK, I see my imperative solution is not getting any love, so here is a LINQ solution that is probably less efficient, but still avoids searching through the list two times (which is a problem in the accepted answer):
var all = new List<string>(new [] { "aaa", "BBB", "Something", "ccc" });
const string temp = "something";
var found = all
.Select((element, index) => new {element, index})
.FirstOrDefault(pair => StringComparer.InvariantCultureIgnoreCase.Equals(temp, pair.element));
if (found != null)
all.RemoveAt(found.index);
You could also do this (which is probably more performant than the above, since it does not create new object for each element):
var index = all
.TakeWhile(element => !StringComparer.InvariantCultureIgnoreCase.Equals(temp, element))
.Count();
if (index < all.Count)
all.RemoveAt(index);
I want to add to previous answers... why don't you just do it like this :
string temp = "something";
List<string> all = FillList().Where(x => x.ToLower() != temp.ToLower());
Then you have the list without those items in the first place.
all.Remove(all.FirstOrDefault(
s => s.Equals(temp,StringComparison.InvariantCultureIgnoreCase)));
Use the tool best suited for the job. In this case a simple piece of procedural code seems more appropriate than LINQ:
var all = new List<string>(new [] { "aaa", "BBB", "Something", "ccc" });
const string temp = "something";
var cmp = StringComparer.InvariantCultureIgnoreCase; // Or another comparer of you choosing.
for (int index = 0; index < all.Count; ++index) {
string found = all[index];
if (cmp.Equals(temp, found)) {
all.RemoveAt(index);
// Do whatever is it you want to do with 'found'.
break;
}
}
This is probably as fast as you can get, because:
Comparison it done in place - there is no creation of temporary uppercase (or lowercase) strings just for comparison purposes.
Element is searched only once (O(index)).
Element is removed in place without constructing a new list (O(all.Count-index)).
No delegates are used.
Straight for tends to be faster than foreach.
It can also be adapted fairly easily should you want to handle duplicates.

Categories

Resources