I currently have a statement which reads
if(Arrays.asList(results).contains("Word"));
and I want to add at least several more terms to the .contains parameter however I am under the impression that it is bad programming practice to have a large number of terms on one line..
My question is, is there a more suitable way to store all the values I want to have in the .contains parameters?
Thanks
You can use intersection of two lists:
String[] terms = {"Word", "Foo", "Bar"};
List<String> resultList = Arrays.asList(results);
resultList.retainAll(Arrays.asList(terms))
if(resultList.size() > 0)
{
/// Do something
}
To improve performance though, it's better to use the intersection of two HashSets:
String[] terms = {"Word", "Foo", "Bar"};
Set<String> termSet = new HashSet<String>(Arrays.asList(terms));
Set<String> resultsSet = new HashSet<String>(Arrays.asList(results));
resultsSet.retainAll(termSet);
if(resultsSet.size() > 0)
{
/// Do something
}
As a side note, the above code checks whether ANY of the terms appear in results. To check that ALL the terms appear in results, you simply make sure the intersection is the same size as your term list:
resultsSet.retainAll(termSet);
if(resultSet.size() == termSet.size())
You can utilize Android's java.util.Collections
class to help you with this. In particular, disjoint will be useful:
Returns whether the specified collections have no elements in common.
Here's a code sample that should get you started.
In your Activity or wherever you are checking to see if your results contain a word that you are looking for:
String[] results = {"dog", "cat"};
String[] wordsWeAreLookingFor = {"foo", "dog"};
boolean foundWordInResults = this.checkIfArrayContainsAnyStringsInAnotherArray(results, wordsWeAreLookingFor);
Log.d("MyActivity", "foundWordInResults:" + foundWordInResults);
Also in your the same class, or perhaps a utility class:
private boolean checkIfArrayContainsAnyStringsInAnotherArray(String[] results, String[] wordsWeAreLookingFor) {
List<String> resultsList = Arrays.asList(results);
List<String> wordsWeAreLookingForList = Arrays.asList(wordsWeAreLookingFor);
return !Collections.disjoint(resultsList, wordsWeAreLookingForList);
}
Note that this particular code sample will have contain true in foundWordInResults since "dog" is in both results and wordsWeAreLookingFor.
Why don't you just store your results in a HashSet? With a HashSet, you can benefit from hashing of the keys, and it will make your assertion much faster.
Arrays.asList(results).contains("Word") creates a temporary List object each time just to do linear search, it is not efficient use of memory and it's slow.
There's HashSet.containsAll(Collection collection) method you can use to do what you want, but again, it's not efficient use of memory if you want to create a temporary List of the parameters just to do an assertion.
I suggest the following:
HashSet hashSet = ....
public assertSomething(String[] params) {
for(String s : params) {
if(hashSet.contains(s)) {
// do something
break;
}
}
}
Related
Good day all, I have a list of Objects (Let's call them ContactObject for simplicity). This object contains 2 Strings, Name and Email.
This list of objects will number somewhere around 2000 in size. The goal here is to filter that list as the user types letters and display it on the screen (IE in a recyclerview) if they match. Ideally, It would filter where the objects with a not-null name would be above an object with a null name.
As of right now, the steps I am taking are:
1) Create 2 lists to start and get the String the user is typing to compare to
List<ContactObject> nameContactList = new ArrayList<>();
List<ContactObject> emailContactList = new ArrayList<>();
String compareTo; //Passed in as an argument
2) Loop though the master list of ContactObjects via an enhanced for loop
3) Get the name and email Strings
String name = contactObject.getName();
String email = contactObject.getEmail();
4) If the name matches, add it to the list. Intentionally skip this loop if the name is not null and it gets added to the list to prevent doubling.
if(name != null){
if(name.toLowerCase().contains(compareTo)){
nameContactList.add(contactObject);
continue;
}
}
if(email != null){
if(email.toLowerCase().contains(compareTo)){
emailContactList.add(contactObject);
}
}
5) Outside of the for loop now as the object lists are build, use a comparator to sort the ones with names (I do not care about sorting the ones with emails at the moment)
Collections.sort(nameContactList, new Comparator<ContactObject>() {
public int compare(ContactObject v1, ContactObject v2) {
String fName1, fName2;
try {
fName1 = v1.getName();
fName2 = v2.getName();
return fName1.compareTo(fName2);
} catch (Exception e) {
return -1;
}
}
});
6) Loop through the built lists (one sorted) and then add them to the master list that will be used to set into the adapter for the recyclerview:
for(ContactObject contactObject: nameContactList){
masterList.add(contactObject);
}
for(ContactObject contactObject: emailContactList){
masterList.add(contactObject);
}
7) And then we are all done.
Herein lies the problem, this code works just fine, but it is quite slow. When I am filtering through the list of 2000 in size, it can take 1-3 seconds each time the user types a letter.
My goal here is to emulate apps that allow you to search the contact list of the phone, but seem to always to it quicker than I am able to replicate.
Does anyone have any recommendations as to how I can speed this process up at all?
Is there some hidden Android secret I don't know of that only allows you to query a small section of the contacts in quicker succession?
I have a simple view with mapper that emits docs with some keys.
com.couchbase.lite.View view = database.getView(VIEW_NAME);
if (view.getMap() == null) {
Mapper map = new Mapper() {
#Override
public void map(Map<String, Object> document, Emitter emitter) {
if ("user".equals(document.get("type"))) {
emitter.emit(document.get("name"), document);
}
}
};
view.setMap(map, null);
}
having this View, i can than create Queries on it, with certain parameters like setKeys, startKey, endKey, setDescending, setDescending, setSkip and others as described in couchbase manual.
If i write
Query query = view.createQuery();
List<Object> keys = new ArrayList<>();
keys.add("User Name");
query.setKeys(keys);
that query would return all docs matching "User Name" key.
But I couldn't find a simple way to write queries that excludes (omits) docs with certain keys (like opposite to setKeys() function)
one hack was found in ToDoLite Example
The code looks like this:
public static Query getQuery(Database database, final String ignoreUserId) {
com.couchbase.lite.View view = database.getView(VIEW_NAME);
if (view.getMap() == null) {
Mapper map = new Mapper() {
#Override
public void map(Map<String, Object> document, Emitter emitter) {
if ("user".equals(document.get("type"))) {
if (ignoreUserId == null ||
(ignoreUserId != null &&
!ignoreUserId.equals(document.get("user_id")))) {
emitter.emit(document.get("name"), document);
}
}
}
};
view.setMap(map, null);
}
Query query = view.createQuery();
return query;
}
Note that the view will only exclude key ignoreUserId you passed to it during the first call, and will ignore all the others during next calls (because it will create view only once during the first call)
So you need to create new view for every key they want to omit.
But if you have a lot of keys you want to exclude or does it often, it would be inefficient and boilerplate.
Do you know any better solution or hack ?
Any help appreciated
Thanks in advance
CouchBase is not designed for the types of queries you want to issue here. It is designed to use map/reduce under the hood to identify specific documents with a range (down to a range of 1 document), not exclude specific documents. What you are asking is not going to be efficient in CouchBase. You are using the wrong technology if your goal is to do these kinds of queries efficiently.
If your hands are tied and you are locked into CouchBase Lite, though, you have to work within the types of queries you have. Excluding a particular value can be restated as including all other values. If you want to do that in CouchBase, you use range queries with ranges designed to not include the value you want to exclude.
Here is a brief conceptual example. Suppose your view has documents with keys of "A", "B", "C", "D", "F", and "X" and you want to issue a query whose result excludes document "D". You could get the result you desired by first issue a range query for "A"-"C" and then issuing a second range query for "E"-"Z". The two results combined would be everything excluding "D".
Of course, those are simple keys. The more complex your keys, the more complex your range endpoints become to exclude specific values. The more keys you want to exclude, the more queries you have to perform (you have to perform N + 1 queries for N excluded terms). You are probably going to have a more efficient system by querying for all values in the view and filtering them in code yourself.
I have these tables in an Android based application where I'm using OrmLite for the database management.
What I want to have an x number of array list depending on how many of the product type FOLDER I have.
So in this case I want to a list of products where the productId equals parentId.
So I want a list where
if(productType = FOLDER) {
if(productId = parentId){
//add product
}
}
Basically what I want to end up with, in this case three lists with each containing a list of products where parentId is the same for every product.
I've tried many things, and some works better than others, but a code I want to run actually throws a nullpointer.
DatabaseHelper dbHelper = getHelper();
List<Product> productsParents = null;
try {
Dao<Product, Integer> dao = dbHelper.getDao();
PreparedQuery<Product> prepQu = dao.queryBuilder().where()
.eq("parentId", dao.queryBuilder().selectColumns("productId").where()
.eq("productType", ProductType.FOLDER).prepare()).prepare();
productsParents = dao.query(prepQu);
} catch (SQLException e) {
...
}
This code isn't working because productParents returns null, and it does not do what I want, even though it's a slight hint. If someone know how to do this in code that would be sufficient also, or more likely a mix of java and ormlite.
Have you had a chance to RTFM around building queries? The ORMLite docs are pretty extensive:
http://ormlite.com/docs/query-builder
Your problem is that a prepared query cannot be an argument to the eq(...) method. Not sure where you saw an example of that form.
So there are a couple ways you can do this. The easiest way is to do a different query for each productType:
Where<Product, Integer> where = dao.queryBuilder().where();
where.eq("parentId", parentId).and().eq("productType", ProductType.FOLDER);
productsParents = where.query();
// then do another similar query again with ProductType.PRODUCT, ...
If you want to do just one query then you can get all products that match the parentId and then separate them using code:
Where<Product, Integer> where = dao.queryBuilder().where();
where.eq("parentId", parentId);
productsParents = where.query();
List<Product> productFolders = new ArrayList<Product>();
List<Product> productProducts = new ArrayList<Product>();
...
for (Product product : productsParents) {
if (product.getProductType() == ProductType.FOLDER) {
productFolders.add(product);
} else if (product.getProductType() == ProductType.PRODUCT) {
productProducts.add(product);
} else ...
}
I try to make a dictionary using Quick Search Box in Android. As shown in the SearchableDictionary tutorial, it loads all (999 definitions)data and uses them as matches to the input text to get the search suggestion. in my case, I have 26963 rows of data that need to be suggest while user input a word on QSB. therefore, I want to grab the char data one by one from the QSB, so that it will be efficiently load necessary suggestion. how can i do this?
here's the code i use...
bringit(200);
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
// from click on search results
//Dictionary.getInstance().ensureLoaded(getResources());
String word = intent.getDataString();
//if(word.length() > 3){bringit(10);}
Dictionary.Word theWord = Dictionary.getMatches(word).get(0);
launchWord(theWord);
finish();
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
//SearchManager.
//String bb =
mTextView.setText(getString(R.string.search_results, query));
WordAdapter wordAdapter = new WordAdapter(Dictionary.getMatches(query));
//letsCount(query);
mList.setAdapter(wordAdapter);
mList.setOnItemClickListener(wordAdapter);
}
Log.d("dict", intent.toString());
if (intent.getExtras() != null) {
Log.d("dict", intent.getExtras().keySet().toString());
}
}
private void letsCount(String query) {
// TODO Auto-generated method stub
for(int i=0; i<query.length(); i++){
definite[i] = query.charAt(i);
}
}
public void bringit(int sum) {
// TODO Auto-generated method stub
String[] ss = new String[10];
Log.d("dict", "loading words");
for(int i=1; i<=sum; i++){
KamusDbAdapter a = new KamusDbAdapter(getApplicationContext());
a.open();
Cursor x = a.quick(String.valueOf(i));startManagingCursor(x);
if(x.moveToFirst()){
ss[0] = x.getString(1);
ss[1] = x.getString(2);
}
Dictionary.addWord(ss[0].trim(), ss[1].trim());
Log.v("Debug",ss[0]+" "+ss[1]);
//onStop();
}
}
I use SQLite to collect data. and the other code is just same as the tutorial...
Retrieving a cursor is generally slow. You only want to retrieve one cursor which contains all the matching results.
You should perform the searching using SQL rather than fetching everything. A FULL_TEXT search is usually fastest for text matching, it is however slightly more complicated to implement than a simple LIKE, but I highly recommend you give it a try.
So you want to execute an SQL statement like:
SELECT * FROM my_table WHERE subject_column MATCH 'something'
See SQLite FTS Extension for more information. You can also use wild-cards to match part of a word.
In terms of search suggestions there is really no point returning more than around ~100 results since generally no users ever bother to scroll down that far, so you can further speed things up by adding a LIMIT 0, 100 to the end of your SQL statement.
If possible only start getting cursors once the user has entered more than X number of characters (usually 3 but in you're case this may not be appropriate). That way you're not performing searches that could potentially match thousands of items.
You seem to be leaving lots of cursors open until the application closes them even though you don't actually need them anymore: instead of calling startManagingCursor just make sure to call x.close() after your if (x.moveToFirst()) { ... } - this will free up memory faster.
On an unrelated note: please don't name your variables and methods things like ss or bringIt() as it makes code hard to read -- what is ss and what does bringIt() bring exactly?
You could have a look at the full text search extension in SQL Lite. Idea is to have a SQL query that fetches only the matching results, not all the results and then filter.
There is also a sample for the Android SDK: com/example/android/searchabledict/DictionaryDatabase
I have written some code for autocompletetextview in custom dialog box.When typing some text that text dynamically search into the hashmap.This hashmap is with large lines of text.It works.But slowly giving me result.
AutoCompleteTextView searchText = (AutoCompleteTextView)searchDialog.findViewById(R.id.searchText);
if(searchText.getText()!=null){
// searchString = searchText.getText().toString().trim();
String[] autoList = getAutoCompletWords(searchText.getText().toString().trim());
ArrayAdapter<String> adapter = new ArrayAdapter<String>(ctx,android.R.layout.simple_dropdown_item_1line,autoList);
searchText.setAdapter(adapter);
}
private String[] getAutoCompletWords(String text){
Set<String> wordsSet = new TreeSet<String>();
Pattern wordPattern = Pattern.compile("\\b"+text+"\\w+",Pattern.CASE_INSENSITIVE);
Matcher matcher = wordPattern.matcher(bookContentMap.values().toString());
while(matcher.find()){
wordsSet.add(matcher.group());
}
String[] wordsArray = wordsSet.toArray(new String[0]);
return wordsArray;
}
If I take thread for above code it is giving me thread handler exception.Please give me an idea for quick response of list on autocomplettext.
Rajendar Are
To know for sure which bits are fast and which are slow, you need to use Android's profiler. Here are two things worth investigating, they're probably the largest resource drain:
You're compiling a regular expression each time a key is pressed, this is very slow. A better option would be to populate a database and query it instead.
You're creating both a TreeSet and an Array each time a key is pressed, which probably hurts.
Converting bookContentMap's values to a String is probably quite processor intensive. Consider caching this value.