Most efficient way of comparing long arrays of strings - android

I'm using the speech recognizer to get a voice input from the user, it returns an array of 5 strings which I pass to this method
public int analyzeTag(ArrayList<String> voiceResults,Editor editor, Context context){
for (String match : voiceResults) {
Log.d(TAG, match);
if (match.equalsIgnoreCase(context.getResources().getString(R.string.first_tag))){
editor.append(context.getResources().getString(R.string.first_tag));
return 1;
}
else if (match.equalsIgnoreCase(context.getResources().getString(R.string.second_tag))){
editor.append(context.getResources().getString(R.string.second_tag));
return 1;
}
//etc....(huge list of tags)
//Some tags might also have acceptable variations, example:
else if (match.equalsIgnoreCase("img") || match.equalsIgnoreCase("image")
{
editor.append("img"); //the string to append is always taken from the first variation
}
}
return 0;
}
This method compares the results with a list of tags, the tag list will be pretty big with hundreds of tags so I would like to find the most efficient way to do this operation.
I need help with:
1.Is my way of comparing results the most efficient? Is there a better way? (from the user experience perspective, I don't want users waiting a long time to get a result).
The voice input will be a big part of my app so this method will be called quite often
2.I have a long list of tags, obviously the if(), elseIf() route is gonna be quite repetitive, is there a way to iterate this? Considering the fact that some tags might have variations (even more than 1)and that the variation 1 ("img") will be the same for everyone, but other variations will be locale/language sensitive example: "image" for english users "immagini" for italian users etc.
Text appended to the editor will be always taken from the first variation

How about puting tags in a StringArray and then iterate though the array ?
String[] tags = context.getResources().getStringArray(R.array.tags);
for (String match : voiceResults) {
for (int index = 0; index < tags.length; index++ ) {
if (match.equalsIgnoreCase(tags[index]) {
editor.append(tags[index]);
}
}
}
Here's the doc on StringArray

Related

Arraylist - how to?

So I have this array List:
private ArrayList<String> output;
Which look like this normally once items are added looks like this:
["ready", "30 min", "5.2 mi", "stop"]
But sometimes the order can change on how the information comes in for example:
["30 min", "stop", "5.2 mi", "ready"]
What would be the best solution to get the number that is in front of mi and min since the ArrayList can change I can't use list.get(1) for example cause the index might be something different.
I have tried to use list.getIndexOf("mi"); but that didn't work, kept coming back as -1 which I would think cause it wasn't an exact match.
Use this .. to get no before "mi"
for(int i =0; i<list.size();i++)
{
if(list.get(i).contains("mi"))
{
String[] splitT =
list.get(i).split(" ");
System.out
.println("before mi is" + splitT[0]);}
}
Note splitT[0] is the answer what you want.. like this you can find for "min" too..
If I understood your question correctly, you want to get an element whith a certain pattern to further work with that information. In that case you could loop through the ArrayList and check for element whether or not it matches a certain regexp. Something like this:
for (String s : arraylist) {
if (s.matches("[0-9]+ mi(n)?")) //would match any number, followed by a whitespace and mi/min {
//do something
}
}
You can do something likes this.
for (String item:list){
if (item.contains("mi")&&!item.contains("min")){
Log.d("item",item.substring(0,3));
}
}
You can do similarly for "min".
You can use this code:
for (int i=0 ; i<output.size() ; i++){
if (output.get(i).endsWith("mi")) // index i contains "mi"
else if (output.get(i).endsWith("min")) //index i contains min not mi
}

RxJava or F# function [List<String> to Map<Integer, String>] where Integer is the position where String changes

I am trying to extract a Map of headers (position of the header + text in the header) from a list of inputs. I have mapped the inputs to the header (or group) they belong in. I need a function that's more or less like distinctor distinctUntilChangedbut where I can obtain the position where the text changed.
So far I have this:
Observable.from(inputs)
.map(this::getSectionTitle) // Maps the inputs into the Header string
.distinct();
which obviously return a list of the headers. In case it wasn't clear I need the list of headers linked to the position where they should be placed.
Alternatively I can also accept a function in F# of type List<String> -> Set<(Int*String)>for example.
Edit:
This is the function I want to move from impeartive to a functionl approach:
for (int i = 0; i < inputs.size(); i++) {
Input pu = inputs.get(i);
String header = getSectionTitle(pu);
if (!mHeaders.containsValue(header)) {
mHeaders.put(i + mHeaders.size(), header);
}
}
Edit 2:
Example of an input/output
["a","a","a","b","b","c"] -> [("a",0),("b",3),("c",5)]
You could use something like this
Observable.from(inputs)
.map(this::getSectionTitle) // Maps the inputs into the Header string
.zipWith(Observable.range(0, 1000), Pair::create) // This is a weak point of the solution as you should know max number of emissions as if it exceeds the number in range, it will stop emitting ..
.distinctUntilChanged(pair -> pair.first) // keySelector function tells the operator what is the value object upon which it should determine whether it was changed
.map(pair -> /* Map to your desired format */)
The pair corresponds to an item which has changed and the order number, in which it was emitted...
EDIT: Instead of Observable.range() you can use timer with scan operator. You should not rely on direct emissions from timer observable as there is no guarantee that it will be a continuous sequence, therefore a scan operator.
Observable.interval(1, TimeUnit.MILLISECONDS)
.scan(0L, (index, emittedValue) -> index++)

Separating the words after the last integer in a large String

I've seen many people do similar to this in order to get the last word of a String:
String test = "This is a sentence";
String lastWord = test.substring(test.lastIndexOf(" ")+1);
I would like to do similar but get the last few words after the last int, it can't be hard coded as the number could be anything and the amount of words after the last int could also be unlimited. I'm wondering whether there is a simple way to do this as I want to avoid using Patterns and Matchers again due to using them earlier on in this method to receive a similar effect.
Thanks in advance.
I would like to get the last few words after the last int.... as the number could be anything and the amount of words after the last int could also be unlimited.
Here's a possible suggestion. Using Array#split
String str = "This is 1 and 2 and 3 some more words .... foo bar baz";
String[] parts = str.split("\\d+(?!.*\\d)\\s+");
And now parts[1] holds all words after the last number in the string.
some more words .... foo bar baz
What about this one:
String test = "a string with a large number 1312398741 and some words";
String[] parts = test.split();
for (int i = 1; i < parts.length; i++)
{
try
{
Integer.parseInt(parts[i])
}
catch (Exception e)
{
// this part is not a number, so lets go on...
continue;
}
// when parsing succeeds, the number was reached and continue has
// not been called. Everything behind 'i' is what you are looking for
// DO YOUR STUFF with parts[i+1] to parts[parts.length] here
}

Parse a string and get certain values

I have a string like this:
_id:2 thread_id:189 address:0292 m_size:null person:0 date:1372494272447 date_sent:0 protocol:0 read:1 status:-1 type:1 reply_path_present:0 subject:null body:Ok. Reply message. service_center:051108 locked:0 sim_id:0 error_code:0 seen:1 _id:1 thread_id:189 address:292 m_size:null person:0 date:1372493695831 date_sent:0 protocol:null read:1 status:-1 type:2 reply_path_present:null subject:null body:Test message service_center:null locked:0 sim_id:0 error_code:0 seen:0
I want to retrieve only parts of this string, for example the address:0292 and the body:xyz from the entire string. I want all instances of these two from a very large String (above is just a sample). Let's assume its more than 20000 characters.
How can I achieve this?
Looks like every address is followed by m_size, so use the string.split() function, to split over the keyword address then select the string.substring() (from each string in the resulting array) until reaching the keyword m_size. And repeat the entire thing for the keywords body and service_center. I can't think of any other way.
You are right, it doesn't seem pretty. But it works :)
String[] splitString = string.split(" ");
for (int i = 0; i < splitString.length; i++) {
if (splitString[i].startsWith("body") || splitString[i].startsWith("address"))
Log.i(TAG, "Found: " + splitString[i]);
// Do whatever you need to do
}

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