How to split C# string into variable when timestamp is optional? - c#

I have an "id" string that I'm reading from a change feed in CosmosDB. Each record is versioned, so that whenever the record is updated, the old record will be versioned to include a timestamp, and the new record will be added.
Example:
id: productbase-001.11
GETS UPDATED
id: productbase-001.11 <- new record
id: productbase-001.11-2020-03-30 <- old record
What I want to do is get the type (productbase), then get the itemId (001.11) and get the timestamp (2020-03-30). I only need the timestamp because I want to exclude the old records from my processing logic further down.
foreach (ProcessedItem item in changes)
{
var convert = item.id.Split('-');
string itemType = convert[0];
string itemId = convert[1];
string timestamp = convert[2];
if (timestamp != null)
{
return;
}
else
{
[rest of my code]
}
}
Obviously have a problem will null refs, and also having the "-" as a delimiter will mean that I get "2020" and "03" and "30" all as separate items in the array. Also not sure how slow this will be if I have 3000 updates coming through.
If there is a better way to get these using SQL API, then I am all ears.

Use the String.Split overload that takes a count parameter
var convert = item.id.Split(new char[]{'-'}, 3);
string itemType = convert[0];
string itemId = convert[1];
string timestamp = (convert.Length == 3 ? convert[2] : null);
As we've specified a maximum of 3 items then the entire timestamp will be included as the last array item, if present.

There's a multiple ways that you could handle this. Here are a couple:
If you don't need the specific value of the timestamp, you could just check to see if convert has more than two items:
foreach (ProcessedItem item in changes)
{
var convert = item.id.Split('-');
string itemType = convert[0];
string itemId = convert[1];
if (convert.Length > 2)
{
return;
}
else
{
[rest of my code]
}
}
This is pretty non-invasive to the code that you've already written, but it wouldn't be super clear why you were doing this.
Another option would be to use a regular expression, something like:
^(?<Product>\w+)-(?<ItemNum>\d+\.\d+)-?(?<TimeStamp>\d{4}-\d{2}-\{2})?$
To me, using the named groups is very clear, but some might consider this solution to be overkill.
If it were me, I would also put all of this logic into it's own class (e.g. ProcessedItemId) or at least a static method, so that it could be reused elsewhere.

Related

How to extract from a list based on user input

The code contains two methods.
The Main which prompts the user for input and prints a sublist based on said user input.
The Extract method passes query from user input and adds all indices to dbQueryList to be extracted from dbListing and printed as query.
How does one to add to a List based on user input?
The primary issue is the if statement which contains the condition of
i.Substring(0, query.Length) = query. This is meant to test the condition 'if part of the query exists in any index in dbListing, add elements to dbQueryList '.
I originally wrote this in Python and it worked perfectly fine. I'm learning C# and not sure how to change that if condition. I considered changing the code and use LINQ in the foreach loop but not entirely clear on how to implement that.
Looking forward to community feedback! :)
//**************************************************
// File Name: autocomplete.cs
// Version: 1.0
// Description: Create a method that functions like an autocomplete
// API and truncates search to 5 results.
// Last Modified: 12/19/2018
//**************************************************
using System;
using System.Collections.Generic;
namespace autocomplete
{
class Program
{
private static string[] database;
private static string input;
private static string query;
static void Main(string[] args)
{
// user input to pass as query
Console.Write("Enter your query: ");
string query = Console.ReadLine();
// dynamic list comprised of 'database' array
List<string> dbListing = new List<string>();
string[] database = new string[] { "abracadara", "al", "alice", "alicia", "allen", "alter","altercation", "bob", "element", "ello", "eve", "evening", "event", "eventually", "mallory" };
dbListing.AddRange(database);
// write results based on user query
Console.WriteLine("Your results: " + Extract(Program.query));
// keep console window open after displaying results
Console.ReadLine();
}
// extract method passing query, return dbQueryList as query
public static List<string> Extract(string query)
{
// empty list is initiated
List<string> dbQueryList = new List<string>();
// foreach assesses all strings in database in main
// then, appends all indices of list equal to given query
foreach (string i in database)
{
// compares query (from index 0 to length of) to all strings in database
if (i.Substring(0, query.Length) = query)
{
// add to list above based on query
dbQueryList.Add(i);
}
// if statement truncates dbQueryList to 5 results
if (dbQueryList.Capacity >= 5)
break;
}
return dbQueryList;
}
}
UPDATE: 1/3/2019 18:30
I made the following changes to the Extract(query) and it worked!
foreach (string i in database)
{
// compares query (from index 0 to length of) to all strings in database
if (i.StartsWith(query))
{
// add to list above based on query
dbQueryList.Add(i);
Console.WriteLine(i);
}
// if statement truncates dbQueryList to 5 results
if (dbQueryList.Capacity >= 5)
break;
}
return dbQueryList;
Very excited that I got this to work! Please let me know if there are any further feedback about how to improve and clean this code if necessary! Cheers, everyone!
The problem is you are using = instead of == in the if statement.
In C# = operator is for assignment so what you are doing is trying to assign query to the expression on the left side, which is not possible. Instead use == operator which is for comparison.
Also, there is a more suitable method - use i.StartsWith(query) to check if the string starts with the given query. The current solution would work as long as i is not shorter than query.Length, in which case it would throw an exception.
if (i.StartsWith(query))
{
...

How to do this kind of search in ASP.net MVC?

I have an ASP.NET MVC web application.
The SQL table has one column ProdNum and it contains data such as 4892-34-456-2311.
The user needs a form to search the database that includes this field.
The problem is that the user wants to have 4 separate fields in the UI razor view whereas each field should match with the 4 parts of data above between -.
For example ProdNum1, ProdNum2, ProdNum3 and ProdNum4 field should match with 4892, 34, 456, 2311.
Since the entire search form contains many fields including these 4 fields, the search logic is based on a predicate which is inherited from the PredicateBuilder class.
Something like this:
...other field to be filtered
if (!string.IsNullOrEmpty(ProdNum1) {
predicate = predicate.And(
t => t.ProdNum.toString().Split('-')[0].Contains(ProdNum1).ToList();
...other fields to be filtered
But the above code has run-time error:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities`
Does anybody know how to resolve this issue?
Thanks a lot for all responses, finally, I found an easy way to resolve it.
instead of rebuilding models and change the database tables, I just add extra space in the search strings to match the search criteria. since the data format always is: 4892-34-456-2311, so I use Startwith(PODNum1) to search first field, and use Contains("-" + PODNum2 + "-") to search second and third strings (replace PODNum1 to PODNum3), and use EndWith("-" + PODNum4) to search 4th string. This way, I don't need to change anything else, it is simple.
Again, thanks a lot for all responses, much appreciated.
If i understand this correct,you have one column which u want to act like 4 different column ? This isn't worth it...For that,you need to Split each rows column data,create a class to handle the splitted data and finally use a `List .Thats a useless workaround.I rather suggest u to use 4 columns instead.
But if you still want to go with your existing applied method,you first need to Split as i mentioned earlier.For that,here's an example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
string part1 = datareader(1).toString.Split("-")(0);///the 1st part of your column data
string part2 = datareader(1).toString.Split("-")(1);///the 2nd part of your column data
}
}
Now,as mentioned in the comments,you can rather a class to handle all the data.For example,let's call it mydata
public class mydata {
public string part1;
public string part2;
public string part3;
public string part4;
}
Now,within the While loop of the SqlDatareader,declare a new instance of this class and pass the values to it.An example :
public void test()
{
SqlDataReader datareader = new SqlDataReader;
while (datareader.read)
{
Mydata alldata = new Mydata;
alldata.Part1 = datareader(1).toString.Split("-")(0);
alldata.Part2 = datareader(1).toString.Split("-")(1);
}
}
Create a list of the class in class-level
public class MyForm
{
List<MyData> storedData = new List<MyData>;
}
Within the while loop of the SqlDatareader,add this at the end :
storedData.Add(allData);
So finally, u have a list of all the splitted data..So write your filtering logic easily :)
As already mentioned in a comment, the error means that accessing data via index (see [0]) is not supported when translating your expression to SQL. Split('-') is also not supported hence you have to resort to the supported functions Substring() and IndexOf(startIndex).
You could do something like the following to first transform the string into 4 number strings ...
.Select(t => new {
t.ProdNum,
FirstNumber = t.ProdNum.Substring(0, t.ProdNum.IndexOf("-")),
Remainder = t.ProdNum.Substring(t.ProdNum.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
SecondNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
Remainder = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
.Select(t => new {
t.ProdNum,
t.FirstNumber,
t.SecondNumber,
ThirdNumber = t.Remainder.Substring(0, t.Remainder.IndexOf("-")),
FourthNumber = t.Remainder.Substring(t.Remainder.IndexOf("-") + 1)
})
... and then you could simply write something like
if (!string.IsNullOrEmpty(ProdNum3) {
predicate = predicate.And(
t => t.ThirdNumber.Contains(ProdNum3)

Linq OrderBy not sorting correctly 100% of the time

I'm using the Linq OrderBy() function to sort a generic list of Sitecore items by display name, then build a string of pipe-delimited guids, which is then inserted into a Sitecore field. The display name is a model number of a product, generally around 10 digits. At first it seemed like this worked 100% of the time, but the client found a problem with it...
This is one example that we have found so far. The code somehow thinks IC-30R-LH comes after IC-30RID-LH, but the opposite should be true.
I put this into an online alphabetizer like this one and it was able to get it right...
I did try adding StringComparer.InvariantCultureIgnoreCase as a second parameter to the OrderBy() but it did not help.
Here's the code... Let me know if you have any ideas. Note that I am not running this OrderBy() call inside of a loop, at any scope.
private string GetAlphabetizedGuidString(Item i, Field f)
{
List<Item> items = new List<Item>();
StringBuilder scGuidBuilder = new StringBuilder();
if (i != null && f != null)
{
foreach (ID guid in ((MultilistField)f).TargetIDs)
{
Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
if (target != null && !string.IsNullOrEmpty(target.DisplayName)) items.Add(target);
}
// Sort it by item name.
items = items.OrderBy(o => o.DisplayName, StringComparer.InvariantCultureIgnoreCase).ToList();
// Build a string of pipe-delimited guids.
foreach (Item item in items)
{
scGuidBuilder.Append(item.ID);
scGuidBuilder.Append("|");
}
// Return string which is a list of guids.
return scGuidBuilder.ToString().TrimEnd('|');
}
return string.Empty;
}
I was able to reproduce your problem with the following code:
var strings = new string[] { "IC-30RID-LH", "IC-30RID-RH", "IC-30R-LH", "IC-30R-RH"};
var sorted = strings.OrderBy(s => s);
I was also able to get the desired sort order by adding a comparer to the sort.
var sorted = strings.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
That forces a character-by-character (technically byte-by-byte) comparison of the two strings, which puts the '-' (45) before the 'I' (73).

How to sort Generic.List<> by comparing with 1 string

I have generic list already filled with values, I need to sort items, that first value should appear same as query string. Is it possible to compare and sort this values, I'm trying with no success.
Bit updating my question.
For example I have Id: 1,2,3,4,5, and I opened page with Id=5, so I need on my searchResult, to pass 4 as first item in the list.
This code for me worked fine and showing 5 as first item, but I need to force for example p2.ObjectId pass as string value just for example "4294", and object with this Id should appear first:
Sort(searchresult);
static void Sort(List<CollectionsItem> list)
{
list.Sort((p1, p2) => string.Compare(p2.ObjectId, p1.ObjectId, true));
}
Doesn't sound like you want a sort at all.
If you want to put X at the top it's just
var item = MyList[MyList.IndexOf(Target)];
MyList.Remove(item);
MyList.Insert(item,0);
If you want Target at the top and the rest sorted by objectid
Find it, remove it, sort the rest and insert it at zero...
It sounds like you want to compare the value of the property ObjectId as a number instead of a string. Here is my suggestion:
static void Sort(List<CollectionsItem> list)
{
list.Sort((p1, p2) =>
{
if (int.Parse(p1.ObjectId) == int.Parse(p2.ObjectId))
return 0;
else if (int.Parse(p2.ObjectId) > int.Parse(p1.ObjectId))
return 1;
else
return -1;
});
}

Cache only parts of an object

I'm trying to achieve a super-fast search, and decided to rely heavily on caching to achieve this. The order of events is as follows;
1) Cache what can be cached (from entire database, around 3000 items)
2) When a search is performed, pull the entire result set out of the cache
3) Filter that result set based on the search criteria. Give each search result a "relevance" score.
4) Send the filtered results down to the database via xml to get the bits that can't be cached (e.g. prices)
5) Display the final results
This is all working and going at lightning speed, but in order to achieve (3) I've given each result a "relevance" score. This is just a member integer on each search result object. I iterate through the entire result set and update this score accordingly, then order-by it at the end.
The problem I am having is that the "relevance" member is retaining this value from search to search. I assume this is because what I am updating is a reference to the search results in the cache, rather than a new object, so updating it also updates the cached version. What I'm looking for is a tidy solution to get around this. What I've come up with so far is either;
a) Clone the cache when i get it.
b) Create a seperate dictionary to store relevances in and match them up at the end
Am I missing a really obvious and clean solution or should i go down one of these routes? I'm using C# and .net.
Hopefully it should be obvious from the description what I'm getting at, here's some code anyway; this first one is the iteration through the cached results in order to do the filtering;
private List<QuickSearchResult> performFiltering(string keywords, string regions, List<QuickSearchResult> cachedSearchResults)
{
List<QuickSearchResult> filteredItems = new List<QuickSearchResult>();
string upperedKeywords = keywords.ToUpper();
string[] keywordsArray = upperedKeywords.Split(' ');
string[] regionsArray = regions.Split(',');
foreach (var item in cachedSearchResults)
{
//Check for keywords
if (keywordsArray != null)
{
if (!item.ContainsKeyword(upperedKeywords, keywordsArray))
continue;
}
//Check for regions
if (regionsArray != null)
{
if (!item.IsInRegion(regionsArray))
continue;
}
filteredItems.Add(item);
}
return filteredItems.OrderBy(t=> t.Relevance).Take(_maxSearchResults).ToList<QuickSearchResult>();
}
and here is an example of the "IsInRegion" method of the QuickSearchResult object;
public bool IsInRegion(string[] regions)
{
int relevanceScore = 0;
foreach (var region in regions)
{
int parsedRegion = 0;
if (int.TryParse(region, out parsedRegion))
{
foreach (var thisItemsRegion in this.Regions)
{
if (thisItemsRegion.ID == parsedRegion)
relevanceScore += 10;
}
}
}
Relevance += relevanceScore;
return relevanceScore > 0;
}
And basically if i search for "london" i get a score of "10" the first time, "20" the second time...
If you use the NetDataContractSerializer to serialize your objects in the cache, you could use a [DataMember] attribute to control what gets serialized and what doesn't. For instance, you could store your temporarary calculated relevance value in a field that is not serialized.

Categories