How to make .contains search for every string in an array? - android

I have this code :
String[] whereyoufromarray = {"where", "you", "from"};
for (String whereyoufromstring : whereyoufromarray)
{
if (value.contains(whereyoufromstring)) {
//statement
}
}
But I want that if to only execute the statement if "value" has all of the words included in the array, something like "where are you from?". Currently if value has ONLY one of the words in the array the statement is executed.
I can do this with if (value.contains("where") && value.contains("you") && value.contains ("from")) but this just seems unnecessarily long. There has to be a workaround using arrays that I am missing.
Well, what is it?
p.s.: sorry for poor grammar. i'm suffering from sleep deprivation.

String[] whereyoufromarray = {"where", "you", "from"};
boolean valueContainsAllWordsInArray = true;
for (String whereyoufromstring : whereyoufromarray) {
// If one word wasn't found, the search is over, break the loop
if(!valueContainsAllWordsInArray) break;
valueContainsAllWordsInArray = valueContainsAllWordsInArray &&
value.contains(whereyoufromstring);
}
// valueContainsAllWordsInArray is now assigned to true only if value contains
// ALL strings in the array

For a case like this, I typically implement a function just to make the test. Let's call it containsAll()
public static boolean containsAll(String[] strings, String test)
{
for (String str : strings)
if (!test.contains(str))
return false;
return true;
}
And now you just do
if (containsAll(whereyoufromarray, value))
//statement

String[] whereyoufromarray = {"where", "you", "from"};
int arrayLength = whereyoufromarray.length;
int itemCount = 0;
for(String whereyoufromstring : whereyoufromarray)
{
if(value.contains(whereyoufromstring))
{
itemCount++;
}
}
if (itemCount == arrayLength){
//do your thing here
}
rough idea. I don't have my IDE up to proof this, but basically you can set a counter to = the length of your known array, then check each value in the array to see if it contains a match..if it does, increment another counter. At the end, test your counter to see if it matches the length of your array, so in your example, if itemCount= 3, then all values matched. if it was 2, then one would be missing and your method wouldn't execute.

Related

Google Sheets API: How to find a row by value and update it's content

I am working on an Android application that uses a Google Spreadsheet as a database.
The application should GET, APPEND and UPDATE values in a spreadsheet, using the Sheets API v4. The first two functions are working fine but I have difficulties updating a specific row. I need to find a row that has a specific value in it's first column ("Batch ID") and update all the cells in this row.
This is how my spreadsheet looks like.
Right now I am getting the row to be modified like this:
ValueRange response = this.mySheetsService.spreadsheets().
values().get(spreadsheetId, range).execute();
List<List<Object>> values = response.getValues();
String rangeToUpdate;
Log.i(TAG, "all values in range: " + values.toString());
int i = 0;
if (values != null) {
for (List row : values) {
i += 1;
if (row.get(0).equals(selectedBatchID)) {
Log.i(TAG, "IT'S A MATCH! i= " + i);
rangeToUpdate = "A" + (i + 1) + ":E" + (i + 1); //row to be updated
}
}
}
/*once I have the row that needs to be updated, I construct my new ValueRange requestbody and
*execute a values().update(spreadsheetId, rangeToUpdate , requestbody) request.
*/
This is actually working fine but I think it's an ugly solution and I am sure there is a better one out there.
I have read the Sheets API documentation and I got familiar with notions such as batchUpdateByDataFilter, DataFilterValueRange or DeveloperMetadata and I sense that I should use these features for what I'm trying to achieve but I couldn't put them together and I couldn't find any examples.
Can someone show me or help me understand how to use these Sheets V4 features?
Thank you.
I have exactly the same issue, and it seems that so far (March 2018) Sheets v4 API does not allow to search by value, returning cell address. The solution I found somewhere here on StackOverflow is to use a formula. The formula can be created in an arbitrary sheet each time you want to find the address by value, then you erase the formula. If you do not want to delete the formula every time, you many prefer to create in a safer place, like a hidden worksheet.
Create hidden worksheet LOOKUP_SHEET (spreadsheetId is your spreadsheet ID):
POST https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId:batchUpdate
{
"requests": [
{
"addSheet": {
"properties": {
"hidden": true,
"title": "LOOKUP_SHEET"
}
}
}
]
}
Create a formula in the A1 cell of the hidden worksheet that searches for "Search value" in MySheet1 sheet, and get back the row:
PUT https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values/LOOKUP_SHEET!A1?includeValuesInResponse=true&responseValueRenderOption=UNFORMATTED_VALUE&valueInputOption=USER_ENTERED&fields=updatedData
{
"range": "LOOKUP_SHEET!A1",
"values": [
 [
  "=MATCH("Search value", MySheet1!A:A, 0)"
 ]
]
}
The response will look like this:
{
"updatedData": {
 "range": "LOOKUP_SHEET!A1",
 "majorDimension": "ROWS",
 "values": [
  [
   3
  ]
 ]
}
}
By default, major dimension is ROWS. MATCH() returns relative row within column A, if no row IDs are provided then this position is effectively absolute. Or, you may want to use a more reliable call like =ROW(INDIRECT(ADDRESS(MATCH("Search value",A:A,0),1))). If the sheet has spaces in it, enclose it in single quotes. If you are searching for number, make sure you do not enclose it in quotes.
In the spreadsheets API we have the concept of developer metadata, that allow us to store information not visible to the end user that we can later on retrieve and use.
In this case the best approach is to assign the Batch ID as a metadata for a particular row. I will add the code based on the Javascript SDK.
const response = await sheets.spreadsheets.developerMetadata.search({
auth: jwtClient,
spreadsheetId,
requestBody: {
dataFilters: [
{
developerMetadataLookup: {
locationType: 'ROW',
metadataKey: 'batchId',
metadataValue: '$BATCH_ID'
}
}
]
}
});
if (response.matchedDeveloperMetadata) {
// There is a row with that id already present.
const { endIndex } = response.matchedDeveloperMetadata[0].developerMetadata.location.dimensionRange;
// Use endIndex to create the range to update the values range: `SheetName!A${endIndex}`,
await sheets.spreadsheets.values.update(
{
auth: jwtClient,
spreadsheetId,
range: `SheetName!A${endIndex}`,
valueInputOption: 'USER_ENTERED',
requestBody: {
majorDimension: 'ROWS',
values: [[]]
},
},
{}
);
} else {
// Append the value and create the metadata.
const appendResponse = await sheets.spreadsheets.values.append(
{
auth: jwtClient,
spreadsheetId,
range: 'SheetName!A1',
valueInputOption: 'USER_ENTERED',
requestBody: {
majorDimension: 'ROWS',
values: [[]]
},
},
{}
);
if (appendResponse.data.updates?.updatedRange) {
const updatedRange = appendResponse.data.updates?.updatedRange;
const [, range] = updatedRange.split('!');
const indexes = convertSheetNotation(range);
await sheets.spreadsheets.batchUpdate({ auth: jwtClient, spreadsheetId, requestBody: {
requests: [
{
createDeveloperMetadata: {
developerMetadata: {
location: {
dimensionRange: {
sheetId: 0,
startIndex: indexes[0],
endIndex: indexes[0] + 1,
dimension: "ROWS"
}
},
metadataKey: 'batchId',
metadataValue: '$BATCH_ID',
visibility: "DOCUMENT"
}
}
}
]
}});
}
}
We need to be careful of race conditions as we may end up with duplicated rows, let me know if that helps :)
I had the same requirement.
First:
Create a function that gets the index of targeted object from the sheet, like:
private int getRowIndex(TheObject obj, ValueRange response) {
List<List<Object>> values = response.getValues();
int rowIndex = -1;
int i = 0;
if (values != null) {
for (List row : values) {
i += 1;
if (row.get(1).equals(obj.getBatchId())) {
System.out.println("There is a match! i= " + i);
rowIndex = i;
}
}
}
return rowIndex;
}
Second:
Create the update method by passing the targeted object having your desired value "batch id" and others new values for the rest of fields.
public void updateObject(Object obj) throws IOException, GeneralSecurityException {
sheetsService = getSheetsService();
ValueRange response = sheetsService.spreadsheets().
values().get(SPREADSHEET_ID, "Sheet1").execute();
int rowIndex = this.getRowIndex(obj, response);
if (rowIndex != -1) {
List<ValueRange> oList = new ArrayList<>();
oList.add(new ValueRange().setRange("B" + rowIndex).setValues(Arrays.asList(
Arrays.<Object>asList(obj.getSomeProprty()))));
oList.add(new ValueRange().setRange("C" + rowIndex).setValues(Arrays.asList(
Arrays.<Object>asList(obj.getOtherProprty()))));
//... same for others properties of obj
BatchUpdateValuesRequest body = new BatchUpdateValuesRequest().setValueInputOption("USER_ENTERED").setData(oList);
BatchUpdateValuesResponse batchResponse;
batchResponse sheetsService.spreadsheets().values().batchUpdate(SPREADSHEET_ID, body).execute();
} else {
System.out.println("the obj dont exist in the sheet!");
}
}
Finally:
In your app you have to pass the tageted object to the update method:
TheObject obj = new Object();
obj.setBatchId = "some value";
Fill the obj with others values if you want.
Then call the method:
objectChanger.updateObject(obj);
All you need to do is to create a new array of strings from an array of arrays - so you can run the indexOf() method on this new array.
Since we know the method values.get returns array of the arrays such as
[
[""],
[""],
...
]
my approach was to a bit flattened this structure.
const data = await googleSheetsInstance.spreadsheets.values.get({
//here u have to insert auth, spreadsheetId and range
});
//here you will get that array of arrays
const allData: any[] = data.data.values;
//Now you have to find an index in the subarray of Primary Key (such as
//email or anything like that
const flattenedData = allData.map((someArray: any[]) => {
return someArray[2]; //My primary key is on the index 2 in the email
Array
});
So what you will get is a normal array of strings with primary keys so now you can easily call the indexOf() on the flattened array.
const index:number = flattenedData.indexOf("someuniquestring);
console.log(index);
And the index value will tell you the row. Do not forget spreadsheets start from 1 and indexes in Javascript start from 0.

how to check the text in first two buttons equals to the text in third button

Here is my code .This doesn't work
button1.getText() + button2.getText() == button3.getText()
Use equals to compare strings,
Try this,
String button1Text = button1.getText().toString();
String button2Text = button2.getText().toString();
String button3Text = button3.getText().toString();
if((button1Text + button2Text).equals(button3Text)){
// strings are equal
} else {
// strings are not equal
}
button[6].getText().toString().equals(button[0].getText().toString().concat(button[3].getText().toString()));
When you work with Strings in Java the operator == checks if the two objects refer to the same instance of an object.
While equals() checks if the two objects are actually equivalent, even if they are not the same instance.
try:
String button1Text = button1.getText().toString();
String button2Text = button2.getText().toString();
String button3Text = button3.getText().toString();
if (button1Text.equals(button3) && button2Text.equals(button3)) {
// do something...
} else {
// do something...
}

Sort by Phone Number

I used the following code snippet to sort by phone number:
class Item { String addr; /* phone number */ }
private int compareByAddr(Item objA, Item objB) {
if (objA.addr==null && objB.addr==null) {
return 0;
} else if (objA.addr==null && objB.addr!=null) {
return -1;
} else if (objA.addr!=null && objB.addr==null) {
return 1;
} else {
if (PhoneNumberUtils.compare(objA.addr, objB.addr)) {
return 0;
} // end if
return objA.addr.compareTo(objB.addr);
} // end if
} // end compareByAddr()
However I got an Exception:
E/AndroidRuntime(12157): java.lang.IllegalArgumentException:
Comparison method violates its general contract!
I've searched about it, and found out that it means my sorting algorithm is not transitive...
Does anyone has a better algorithm for sorting by phone number?
Problem is you have 2 String's which represent phone numbers and you want to compare them and sort them from big to small like there were numbers...
but comparing 2 String to each-other (like in your code snippet):
objA.addr.compareTo(objB.addr);
wont work :(
you can do this by manipulating the String to a number (and discarding all non digit from it)
and then compare the 2 like they were regular numbers ...
String phone1Str
String phone2Str
int phone1
int phone2
phone1Str= phone1Str.replaceAll("\\D+",""); //using reg-ex to get only digits
phone1= Integer.valueOf(phone1Str); //convert to int to compare numbers
phone2Str= phone2Str.replaceAll("\\D+","");
phone2= Integer.valueOf(phone2Str);
if (PhoneNumberUtils.compare(phone1Str,phone2Str)
//they are equal
else if (phone1>phone2)
//phone1 is the larger number !
else
//phone2 is the larger number !
hope this helps

Loop structure gives wrong result

I am trying to compare items out of my DB to the value of an EditText (user input). The answer can have multiple answers, seperated by a ','. I first put them into a stringarray and then compare them to the answer. The LevenshteinDistance checks if the answer is more or les good (http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance).
userAnswer = etUserAnswer.getText().toString().toLowerCase();
String[] answers = qAnswer.split(",");
for (String answer : answers) {
if (answer.equals(userAnswer)) {
Toast.makeText(getApplicationContext(), ("Answer Correct"),
Toast.LENGTH_SHORT).show();
tvMessage.setText("You smartass!");
} else {
Toast.makeText(getApplicationContext(), ("Wrong"),
Toast.LENGTH_SHORT).show();
points = points - 4;
String answerGood = answer.toLowerCase();
LevenshteinDistance lDistance = new LevenshteinDistance();
int comparisonCheck = lDistance.computeLevenshteinDistance(
userAnswer, answerGood);
if (comparisonCheck == 1) {
tvMessage.setText("Almost there, but not quite yet!");
} else if (comparisonCheck > 1) {
tvMessage.setText("Are you serious, totally wrong?!");
}
}
}
Suppose I am having the answers for a question in the DB as follows: tree,test,radio
I am having two problems:
1. When I type "radi" it gives me 'Almost there...' which is good. It should also give me this if I enter "tes", but instead it gives me the 'Are you serious,...' line. I guess it keeps comparing to the last one.
2. Every time I type in something which is not correct, I get -12 instead of -4. I suppose this is due to the fact I am having three answers and it loops three times.. but I don't know how I can make it count only once..
Anyone can help me on the way? Thanks!
Assuming you don't need to know the word which gives the least Levenshtein distance, you could modify your loop to find smallest distance only;
userAnswer = etUserAnswer.getText().toString().toLowerCase();
String[] answers = qAnswer.split(",");
LevenshteinDistance lDistance = new LevenshteinDistance();
int minDistance = lDistance.computeLevenshteinDistance(
userAnswer, answers[0].toLowerCase());
for (int i = 1; i < answers.length; ++i) {
minDistance = Math.min(minDistance, lDistance.computeLevenshteinDistance(
userAnswer, answers[i].toLowerCase()));
}
if (minDistance == 0) {
// Correct answer...
} else {
// Wrong answer...
points -= 4;
// etc etc...
}

IfElse or regex

Which one of these would be the best way to do this when you have very long IfElse?
if (text.contains("text"))
{
// do the thing
}
else if (text.contains("foo"))
{
// do the thing
}
else if (text.contains("bar"))
{
// do the thing
}else ...
Or
if (text.contains("text") || text.contains("foo") || ...)
{
// do the thing
}
Or maybe
Pattern pattern = Pattern.compile("(text)|(foo)|(bar)|...");
Matcher matcher = pattern.matcher(text);
if(matcher.find())
{
// do the thing
}
And I mean ONLY when you have to check a lot of these. Thanks!
I would personally use a set as I think it is easier to read and the contains will be efficient in O(1):
Set<String> keywords = new HashSet<String>();
keywords.add("text");
keywords.add("foo");
keywords.add("bar");
if(keywords.contains(text)) {
//do your thing
}
And if you like it compact, you can also write:
Set<String> keywords = new HashSet<String>(Arrays.asList("text", "foo", "bar"));
if(keywords.contains(text)) {
//do your thing
}
And finally, if you always use the same list, you can make keywords private static final instead of recreating it each time you run the method.
EDIT
Following a comment, it is true that what is above is equivalent to using a condition with text.equals("xxx"), not text.contains("xxx"). If you really meant to use contains, then you would have to iterate over the set and test each string, but it becomes an O(n) operation:
for (String key : keywords) {
if (text.contains(key)) {
//do your stuff
break;
}
}
Usually long If else statements are replaced with case statements, but this is not always possible. If I where to recommend, I would go for the second option, option 1 will give you a bunch of If else if else statements which do the same thing while for the third case, regular expressions tend to grow pretty large pretty fast.
Again depending on how much alot is, it could eventually be better to just throw all your strings in a data structure and iterate over it to see if the element is in it.
String[] storage = {
"text",
"foo",
"bar",
"more text"
};
for(int i=0; i < storage.length(); i++){
//Do Something
}
Does this help?

Categories

Resources