I am not sure whether I can ask such kind of questions here. Apologizes if this is an improper question.
We have a functionality which searches some items based on the user input. The problem is the user can enter multiple criteria. The possible combinations are
name:some name
type:some type
domain: some domain
name:some name [ & | ] domain:some domain
name:some name [ & | ] type:some type
type:some type [ & | ] domain:some domain
name:some name [ & | ] type:some type [ & | ] domain:some domain
The 'domain' and 'type' are attributes of type X and 'name' is an attribute of type Y.
I have implemented different methods which actually retrieves all data for different combinations. The basic methods are
Set<Item> getItemsForAName(String name)
Set<Item> getItemsForADomain(String domain)
Set<Item> getItemsForAType(String type)
Other methods will use these 3 methods internally and will return the result.
The problem I am facing is now I have to write some logic which will actually call appropriate method based on the search string.
After various logics finally I decided to use regular expressions. So I wrote some if else statements like this
//I have the search string separated and put in a map already
if(searchString.matches("name:[\\w\\s-]+")) {
Set<item> items = getItemsForAName(map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+")) {
Set<Item> items = getItemsForAType(map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+")) {
Set<Item> items = getItemsForAType(map.get("domain"));
} else if (searchString.matches("domain:[\\w\\s-]+&type:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndType(map.get("domain"), map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndName(map.get("domain"), map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForATypeAndName(map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|type:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrType(map.get("domain"), map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrName(map.get("domain"), map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForATypeOrName(map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+&type:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndTypeAndName(map.get("domain"), map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|type:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrTypeOrName(map.get("domain"), map.get("type"), map.get("name"));
}
I suppose that this is a stupid logic. I have even tried some other ways to break this logic. Other logic I wrote is while constructing the map I store the separators even. I use a LinkedHashMap for this because I need the insertion order to make it a bit simple. Then I write some if else statements like this
if(!map.get("name").isEmpty() && !map.get("type").isEmpty() && map.get("domain").isEmpty() && !map.get("separator1").equals("&") && map.get("separator2").equals("&")) {
Set<Item> items = getItemsForADomainAndTypeAndName(map.get("domain"), ap.get("type"), map.get("name"));
} else if .....
However I dint choose the second procedure because I need the insertion order I am using LinkedHashMap so may become a problem in future and I need to even put the separators in the map. So I decided to go with RegEx way. The problem is regular expressions take more time I read somewhere. I am not able to take a decision or I am not sure whether there is an even better approach.
Can anyone please suggest a simple solution? Thank you all in advance.
Perhaps try using an overloaded search function (if your language supports this) that will call the appropriate function based on an if/else ladder that checks whether certain values are not null.
Related
Suppose I have a recipe called Garlic parmesan butter. I need to return an object when the appropriate name has been found.
Now in a simple ad-hoc solution I can search in the following way:
class SearchRecipe {
late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
for (int i = 0; i < Store.instance.getAllRecipes().length; i++) {
if (suggestion == Store.instance.getAllRecipes()[i].recipeName) {
return Store.instance.getAllRecipes()[i];
}
}
return recipe;
}
}
But I need a simple way where if the user types in Garlic butter I need to return the object associated with the Garlic Paremesan butter.
How can I do it?
Edit: I should've clarified that I'm working with a List of objects. So the Store.instance.getAllRecipes() basically returns a List<RecipeModel>.
Update 1: This is what I've written:
class SearchRecipe {
//late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
List<RecipeModel> results = [];
suggestion!.split(' ').forEach((s) {
results.addAll(Store.instance
.getAllRecipes()
.where((element) => element.recipeName!.contains(s)));
});
results = results.toSet().toList();
for (int i = 0; i < results.length; i++) {
return results[i];
}
return results[0];
}
}
String search = 'Garlic butter';
List<String> list = [
'Garlic Paremesan butter',
'Butter',
'Garlic',
'Butter Garlic',
'Paremesan',
'Stackoverflow'
];
List<String> results = [];
search.split(' ').forEach((s) {
results.addAll(list.where((element) => element.contains(s)));
});
// Avoid repeated values
results = results.toSet().toList();
Split the user input at spaces. Then you have a list. You can check the list and depending on your preference implement a variety of behaviors.
You can match if all words in the list are found. You can return if at least one of the words is matched.
You could give preference to more contained words or you could check the order of words.
In either case you would also not check for equality but use a function like includes / contains to check whether the searched word is part of the name.
(Checking the order could be done by remembering which words you already identified and only searching after the words that were found. In your example you would find ‘garlic’ and after that you would just look through ‘paremesan Butter’ and try to find ‘butter’)
first split the input text
var string = "Hello world!";
string.split(" ");
// ['Hello', 'world!'];
then iterate over each word in above array and check whether the above
Store.instance.getAllRecipes()[i].recipeName contains above word.
if(str.equalsIgnoreCase(str))
{
//remaining code
}
try contains method.
.contains()
I have a list view that can be sorted, searched and filtered. From that list view the user can edit items in multiple steps. Finally after editing and reviewing the changes the user goes back to the list. Now I want the list to use the same sorting, search term and filters that the user set before and show the correct results.
How can multiple paramters (sorting, search, filter) be stored and reused when showing the list action?
Possible unsatisfactory ways that I thought of:
pass through all the needed parameters. Does work hardly if there are multiple actions involved between the two list action calls
save the parameters in the session object. This seems to require a lot of code to handle multiple parameters (check if parameter was passed to action, store new value, if parameter was not passed, get old parameter from session, handle empty string parameters):
Long longParameter
if(params.containsKey('longParameter')) {
longParameter = params.getLong('longParameter')
session.setAttribute('longParameter', longParameter)
} else {
longParameter = session.getAttribute('longParameter') as Long
params['longParameter'] = longParameter
}
If you want to make it more generic you could use an Interceptor instead.
This could perhaps be generalized like this:
class SessionParamInterceptor {
SessionParamInterceptor() {
matchAll() // You could match only controllers that are relevant.
}
static final List<String> sessionParams = ['myParam','otherParam','coolParam']
boolean before() {
sessionParams.each {
// If the request contains param, then set it in session
if (params.containsKey(it)) {
session[it] = params[it]
} else {
// Else, get the value from session (it will be null, if not present)
params[it] = session[it]
}
}
true
}
}
The static sessionParams holds the parameters you want to store/retrieve from session.
If the params contains an element from the list, it is stored in session under the same name. If not, it is taken from session (given that it exists).
In your controller, you can now just access params.getLong('theParam') like you always would. You could also use Grails parameter conversion:
def myAction(Long theParam) {
}
Lots of LOC saved.
I use the session as well. Here is a sample that you may adapt to your needs:
def list() {
if (request.method == 'GET' && !request.queryString) {
if (session[controllerName]) {
// Recall params from memory
params.putAll(session[controllerName])
}
} else {
// Save params to memory and redirect to get clean URL
session[controllerName] = extractParams(params)
redirect(action: actionName)
return
}
// Do your actions here...
}
def extractParams(params) {
def ret = [:]
params.each { entry ->
if (entry.key.startsWith("filter_") || entry.key == "max" || entry.key == "offset" || entry.key == "sort" || entry.key == "order") {
ret[entry.key] = entry.value
}
}
return ret
}
Using session is your best bet. Just save the preference when preferred. I mean, when user sorts, or filter, just save that information in the session, for that particular <controller>.<action>, before returning the page. Next time, check the session, if it has anything related to that <controller>.<action>, apply those; otherwise render the default page.
You might like to use some Interceptor for this, as suggested by sbglasius, here.
I hope you're getting my point.
Using Orchard CMS, I am dealing with a record and a part proxy, but cannot figure out how to save it into the DB. In fact, I confess I don't even know how to get the items I'm trying to save into this paradigm. I was originally using enum's for choices:
MyEmum.cs:
public enum Choices { Choice1, Choice2, Choice3, Choice4 }
MyRecord.cs:
public virtual string MyProperty { get; set; }
MyPart.cs:
public IEnumerable<string> MyProperty
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyProperty)) return new string[] { };
return Record
.MyProperty
.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set { Record.MyProperty = value == null ? null : String.Join(",", value); }
}
Now, in my service class, I tried something like:
public MyPart Create(MyPartRecord record)
{
MyPart part = Services.ContentManager.Create<MyPart>("My");
...
part.MyProperty = record.MyProperty; //getting error here
...
return part;
}
However, I am getting the following error: Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable<string>'
Essentially, I am trying to save choices from a checkboxlist (one or more selections) as a comma-separated list in the DB.
And this doesn't even get me over the problem of how do I use the enum. Any thoughts?
For some background:
I understand that the appropriate way to handle this relationship would be to create a separate table and use IList<MyEnum>. However, this is a simple list that I do not intend to manipulate with edits (in fact, no driver is used in this scenario as I handle this on the front-end with a controller and routes). I am just capturing data and redisplaying it in the Admin view for statistical/historical purposes. I may even consider getting rid of the Part (considering the following post: Bertrand's Blog Post.
It should be:
part.MyProperty = new[] {"foo", "bar"};
for example. The part's setter will store the value on the record's property as a comma-separated string, which will get persisted into the DB.
If you want to use enum values, you should use the Parse and ToString APIs that .NET provide on Enum.
SLIGHT UPDATE BELOW
I am trying to use the [Description] data annotation attribute with enums in order to display a friendly name. I've searched around a lot and cannot get anything implemented. Right now I have code that will display an enum as a string (using an extension), but I am not liking ThisIsAnEnum as an enum name (which is spaced out by the string extension) and it prohibits me from having longer names (which I need to maintain) such as for a radio button item. My goal is to have longer descriptions for radio button items without having to write really long enums. An extension/helper will probably be the right way to go, but I need to "fit" it into the code I am using, which is where I failed using the many examples out there.
The code I am using is generic, in that depending upon some logic either a radio button list, check box list, drop down list, select list or regular text boxes are displayed. For multi-item lists enum's are used, and the enum name is what is displayed (after using the string extension).
Here is the particular code that displays the enum:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = name.ProperCase(),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
ProperCase is the class that changes the enum to something readable.
I found something that almost worked:
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
in which case I changed code from Text = name.ProperCase(), to Text = name.GetEnumDescription(...) but if I put value in the parenthesis I get a "does not exist in the current context" message (which I tried fixing but just made the problem worse). If I leave it blank I get the "No overload for ... takes 0 arguments" (again, understandable - but I don't know how to fix). And if I put name in the parenthesis the code compiles but upon viewing the page I get the "Object reference not set..." error on this line:
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes
(typeof(DescriptionAttribute), false);
I've spent a lot of time on this and know that my stumbling block is the
Text = name.ProperCase(),
code. Any ideas/help? Thanks in advance.
UPDATE:
If I do:
Text = GetEnumDescription(selectedValue),
I actually DO get the [Description] text, however, it just displays for the first enum. So, if I have 5 enums all with different [Description]'s the code just repeats the [Description] for the first enum 5 times instead of displaying differently for each. I hope that makes sense and gets to narrow down the problem.
I'd recommend you the Display attribute:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>(T selectedValue = default(T)) where T : struct
{
return
from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = name == selectedValue.ToString()
};
}
public static string GetEnumDescription(string value, Type enumType)
{
var fi = enumType.GetField(value.ToString());
var display = fi
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
return display.Name;
}
return value;
}
and then you could have:
public enum Foo
{
[Display(Name = "value 1")]
Value1,
Value2,
[Display(Name = "value 3")]
Value3
}
And now you could have:
var foo = Foo.Value2;
var values = GetItemsFromEnum(foo);
Also notice that I have modified the Selected clause in the LINQ expression as yours is not correct.
This being said, personally I would recommend you staying away from enums on your view models as they don't play nicely with what's built-in ASP.NET MVC and you will have to reinvent most of the things.
I have a couple of tables with similar relationship structure to the standard Order, OrderLine tables.
When creating a data context, it gives the Order class an OrderLines property that should be populated with OrderLine objects for that particular Order object.
Sure, by default it will delay load the stuff in the OrderLine property but that should be fairly transparent right?
Ok, here is the problem I have: I'm getting an empty list when I go MyOrder.OrderLines but when I go myDataContext.OrderLines.Where(line => line.OrderId == 1) I get the right list.
public void B()
{
var dbContext = new Adis.CA.Repository.Database.CaDataContext(
"<connectionString>");
dbContext.Connection.Open();
dbContext.Transaction = dbContext.Connection.BeginTransaction();
try
{
//!!!Edit: Imortant to note that the order with orderID=1 already exists
//!!!in the database
//just add some new order lines to make sure there are some
var NewOrderLines = new List<OrderLines>()
{
new OrderLine() { OrderID=1, LineID=300 },
new OrderLine() { OrderID=1, LineID=301 },
new OrderLine() { OrderID=1, LineID=302 },
new OrderLine() { OrderID=1, LineID=303 }
};
dbContext.OrderLines.InsertAllOnSubmit(NewOrderLines);
dbContext.SubmitChanges();
//this will give me the 4 rows I just inserted
var orderLinesDirect = dbContext.OrderLines
.Where(orderLine => orderLine.OrderID == 1);
var order = dbContext.Orders.Where(order => order.OrderID == 1);
//this will be an empty list
var orderLinesThroughOrder = order.OrderLines;
}
catch (System.Data.SqlClient.SqlException e)
{
dbContext.Transaction.Rollback();
throw;
}
finally
{
dbContext.Transaction.Rollback();
dbContext.Dispose();
dbContext = null;
}
}
So as far as I can see, I'm not doing anything particularly strange but I would think that orderLinesDirect and orderLinesThroughOrder would give me the same result set.
Can anyone tell me why it doesn't?
You're just adding OrderLines; not any actual Orders. So the Where on dbContext.Orders returns an empty list.
How you can still find the property OrderLines on order I don't understand, so I may be goofing up here.
[Edit]
Could you update the example to show actual types, especially of the order variable? Imo, it shoud be an IQueryable<Order>, but it's strange that you can .OrderLines into that. Try adding a First() or FirstOrDefault() after the Where.