Lucene: Boolean OR in MultiFieldQueryParser - java

I have a database with 10 fields, and I need to construct a query that looks something like the following pseudo code:
theQuery = ((field1 == A) &&
(field2 == B) &&
(field3 == C) &&
(field4 == D) &&
(field5 == E) &&
(field6 == F) &&
(field7 == G) &&
((field8 == H) || (field9 == H) || (field10 == H)))
That is to say that I need fields 1-7 to definitely contain the corresponding supplied variable, and I need the variable H to definitely appear in at least one of fields 8-10.
I have been trying to use the MultiFieldQueryParser, but the problem that I have is that the BooleanClauses supplied are MUST, MUST_NOT and SHOULD, and we can set the default operator of the MultiFieldQueryParser to be either AND or OR.
When I try using AND and setting fields 1-7 with MUST and fields 8-10 with SHOULD, the query parser basically ignores fields 8-10 and gives me back anything that contains the specified data in fields 1-7.
I haven't yet tried setting the default operator to OR, because I'm guessing that the query will return results that contain one or more of the supplied variables in fields 1-10.
For those that wish to see code, my code is as follows:
ArrayList queries = new ArrayList();
ArrayList fields = new ArrayList();
ArrayList flags = new ArrayList();
if(varA != null && !varA.equals(""))
{
queries.Add(varA);
fields.Add("field1");
flags.Add(BooleanClause.Occur.Must);
}
//... The same for 2-7
if(varH != null && !varH.equals(""))
{
queries.Add(varA);
queries.Add(varA);
queries.Add(varA);
fields.Add("field8");
fields.Add("field9");
fields.Add("field10");
flags.Add(BooleanClause.Occur.Should);
flags.Add(BooleanClause.Occur.Should);
flags.Add(BooleanClause.Occur.Should);
}
Query q = MultiFieldQueryParser.parse(VERSION.LUCENE_34,
queries.toArray(),
fields.toArray(),
flags.toArray(),
theAnalyzer);
Obviously this is somewhat simplified as the ArrayLists don't neatly return me arrays of Strings and BooleanClause.Occurs, but you get the idea.
Does anyone know of a way of forming a multifield query, including both boolean ANDs and boolean ORs?
Thanks,
Rik

I don't really understand your notation, so it's hard to figure out what the problem is. But just use standard queries:
BooleanQuery topQuery = new BooleanQuery();
topQuery.add(new TermQuery(...), BooleanClause.Occur.Must);
etc.
Or just do it in text and let the parser parse it for you: +field1:A +field2:B ...

Related

Arrays.equals without length of arrays

I have two arrays with different length, but same elements. For example
A1 = {1,2,3,null,null}
A2 = {1,2,3}
Arrays.equals gives me false, because arrays have different length. Are there any method in java that will compare only elements in method?
I don't want to use .toString
I'm trying to make compare method in my own generic stack realization.
No, because its a weird request. null does not mean 'not here', null means 'unknown / unset', that's why it throws exceptions when you interact with it: You're asking "hey, thing that has not been set yet, are you X", and there is no way to answer such a question.
That doesn't mean your code is wrong, just, you can stop looking for existing implementations. Weird requests generally aren't catered to by the core libraries (or any other). You also may want to change your mindset on null. Programming in java is a lot less aggravating if at all times a NullPointerException is a good thing. In other words, avoid using null as having any semantic meaning. If you ever write if (x == null || x.isEmpty()) you are doing it wrong. Instead, where-ever 'x' is coming from, it should hold, or be updated to ASAP, the empty string instead. So, if reading in external data (e.g. you marshalled some JSON into an object), do a 'clean' step that replaces all null values that have semantic meaning with an object that actually represents it, and for methods that return stuff, always return an object that represents what you are returning - only return null if you WANT to convey the notion that there is no result (i.e. that's not the same as 'an empty result', i.e. if any code acts like there was a result, you want it to crash).
In other words, I doubt you are asking the right question. But in case you are, you have two broad options.
First make null-less arrays then compare those as normal
One option is to make new arrays that have nulls stripped. Something like:
#SuppressWarnings("unchecked")
<T> T[] stripNulls(T[] in) {
Class<?> componentType = in.getClass().getComponentType();
return (T[]) Arrays.stream(in)
.filter(x -> x != null)
.toArray(len -> java.lang.reflect.Array.newInstance(componentType, len));
}
// which you can then use; you don't need generics for a compare,
// it wouldn't add anything at all.
boolean compare(Object[] a, Object[] b) {
return Arrays.equals(stripNulls(a), stripNulls(b));
}
Just compare in place
If it's performance sensitive that's suboptimal. A better approach would involve a little more coding:
boolean compare(Object[] a, Object[] b) {
Object ae = null, be = null;
int ai = 0, bi = 0, al = a.length, bl = b.length;
while (true) {
/* set `ae` and `be` to the next non-null element */
while (ae == null && ai < al) ae = a[ai++];
while (be == null && bi < bl) be = b[bi++];
/* Have we hit the end? */
if (ai == al && bi == bl) return true;
/* If one is at the end, but the other isn't... */
if (ai == al || bi == bl) return false;
/* check if the 2 current elements are equal */
if (!ae.equals(be)) return false;
}
}
Not a native Java Developer, but maybe this helps you?
boolean arraysEqual = Arrays.equals(Arrays.stream(a1).filter(n => n != null).toArray(), Arrays.stream(a2).filter(n => n != null).toArray())

How to use a ternary operator to convert a String that can sometimes be null into an integer in Java?

I am using Talend to filter out some rows from an excel file and they don't allow block statements. Everything has to be simple logic or using the ternary operator. So the problem is that the code/logic I need will be used across every cell in the column, BUT some of the cells are null, some are Strings and the rest are Strings that represent integers.
My logic needs to be this:
Return true if and only if PlanName == null || PlanName == 0 but as you can tell, it will fail when it tries to run this on a cell that contains the null or the cell that contains a String that isn't a number.
Is it possible to have this logic in java without the try-catch or block statements? This is what I have right now:
input_row.PlanName == null || Integer.parseInt(input_row.PlanName) == 0
Thanks!
Edit: Basically, I just need to write logic that does this:
Return true if input_row.PlanName == null OR if input_row.PlanName == 0
This needs to be done without using block-statements or try-catches because I am using Talend. So I can only use logical operators like && and || and I can use ternary operators as well.
In your situation, i'll go for routines : reusable bunch of code, handy for this kind of rules that would be hard to implement without if/else etc.
You can create two Routines in Talend, with static methods that you would be able to use in a tMap or a tJavaRow.
First Routine to know if your plan is a numeric or not :
public static boolean isNumeric(String strNum) {
if (strNum == null) {
return false;
}
try {
double d = Double.parseDouble(strNum);
} catch (NumberFormatException nfe) {
return false;
}
return true;
}
Then another routine like :
public static boolean correctPlanName(String planName) {
if(Relational.ISNULL(planName)){
return false;
}
else{
if(!isNumeric(planName)){
return false;
}
else {
return true;
}
}
}
Then you call Routines.correctPlanName(input_row.planName) in tMap/tJavaRow.
It should do the trick.
You can use a regular expression to check if the String only contains digits, then check if num == 0.
input_row.PlanName == null || (input_row.PlanName != null && input_row.PlanName.matches("\\d+") && Integer.parseInt(input_row.PlanName) == 0)
Edit: Probably overkill but to cover other cases e.g. floating point types, numbers prefixed with +/-, you could also do:
input_row.PlanName != null && input_row.PlanName.matches("[-+]?\\d*\\.?\\d+") && Double.parseDouble(input_row.PlanName) == 0)

Generic object comparator field by field

Just for fun I wanted to try to implement a field by field, generic object comparator and this is what I did :
private Boolean isEqualFiedByField(Object o1, Object o2){
if (o1 == null || o2 == null || o1.getClass() != o2.getClass())
return false;
ObjectMapper mapper = new ObjectMapper();
Boolean result = true;
Map map1 = mapper.convertValue(o1, Map.class);
Map map2 = mapper.convertValue(o2, Map.class);
for (Object field : map1.keySet()) {
String fieldName = field.toString();
if (map1.get(fieldName) != null && map2.get(fieldName) != null)
result &= map1.get(fieldName).toString().equals(map2.get(fieldName).toString());
else
result &= (map2.get(fieldName) == map1.get(fieldName));
}
return result;
}
Is there anyway to improve this code ? Make it cleaner, faster or treat edges cases I forgot ?
Your current code uses ObjectMapper, you could also do this using reflection and not depend on any library. Not sure that's better, but something to consider.
I always put braces around blocks, even one-liners. You might later want to add a line to your if block and forget to add the braces.
You chose to handle the case with two null arguments by returning false. Is that a deliberate decision? You might want to put some JavaDoc on your method explaining this.
I think you could split your method into at least 3 parts, already indicated by empty lines in your current code. These parts do different things so could be handled in separate methods.
You are calling map1.get(fieldName) three times in your code (also map2). I would call it only once and assign the value to a local variable.
If you can get ObjectMapper (I don't know the class) to return a Map<String, Object> you can avoid all the toString calls later in the code.

Converting If Condition to HashSet Object

Can any one help me how to write the below code in "if Condition" to Set object.
if (!(Aaa.HELLO.equals(city.getcityMethod()) ||
Aaa.BANGALORE.equals(city.getcityMethod()))
|| !((Bbb.MYSORE.equals(city.getcityTypeInformation().getCitypurspose()))
|| (CityPurpose.RETRIED.equals(city.getCityTypeInformation().getCitypurspose(()))
|| (CityPurpose.SOCIAL.equals(getcityMethod.getCityTypeInformation().getCitypurspose()))
|| (CityPurpose.COMPANY.equals(getcityMethod.getCityTypeInformation().getCitypurspose()))))
Some like this:
CONVERTING =unmodifiable::< SET>(HashSet( //here using if condition logic);
So that CONVERTING can be used like this:
if( CONVERTING) { // some logic}
Well, put the data you want in relevant sets of the proper types, and then check these sets contain the passed data:
Set<CityMethod> methods=new HashSet<>();
methods.add(Aaa.HELLO);
methods.add(Aaa.BANGALORE);
Set<CityPurpose> purposes=new HashSet<>();
purposes.add(Bbb.MYSORE);
purposes.add(CityPurpose.RETRIED);
...
boolean converting=methods.contains(city.getcityMethod()) ||
purposes.contains(cirty.getcityTypeInformation().getcityPurpose());
if (converting) {
...
}

Check if a list within the hashmap contains 2 values and only those 2 values

How would I check if this hashmap contains 2 specific values if it contains anything else beside those 2 values it cancels the events. (2 values) per player the string is the identifer aka the players uniqueid. So if the player has 3 or more values in the hashmap it cancels the event.
HashMap<String, List<Material>> result = new HashMap<>();
List<Material> values = new ArrayList<>();
To check whether the list contains two specific Material enum's and only those two, you could check whether the list contains two elements (whether the length of the list is 2) and then compare each of those two elements to see whether they are the correct types. Example incomplete code:
if (result.containsKey(PLAYER_NAME) && result.get(PLAYER_NAME).size() == 2) {
List<Material> list = result.get(PLAYER_NAME);
Material first = list.get(0);
Material second = list.get(1);
if (first == Material.FIRST && second == Material.SECOND || first == Material.SECOND && second == Material.FIRST) {
//Don't cancel the event
}
}
PLAYER_NAME is the String name of the player, Material.FIRST and Material.SECOND are the two Materials you're checking for. Not sure if I correctly understood your question though.
What I understood from the question, I wrote down in code.
The compareTo() method compares two Material objects and returns 0 if they are matching. Assumption-variable result is accessible to the method.
public boolean continueEvent(String playerId, List<Material> values)
{
List<Material> mapValues=result.get(playerId);
if( mapValues.size()!=2){
return false;
}
else{
if((mapValues.get(0).compareTo(values.get(0)==0 && mapValues.get(1).compareTo(values.get(1)==0) || (mapValues.get(0).compareTo(values.get(1)==0 && mapValues.get(1).compareTo(values.get(0)==0)){
return true;
else
return false;
}
}
}
Note: Code mentioned above is illustration of logic. I haven't executed it. There may be syntactic error.

Categories