For the purpose of building a database system I am using a simple builder to generate selection query based on user choices. It has a couple of booleans and then it progresses as follows
StringBuilder builder = new StringBuilder();
builder.append("SELECT ");
if(addOpen)
builder.append("Open ");
if(addHigh)
builder.append("High ");
if(addLow)
builder.append("Low ");
if(addSettle)
builder.append("Settle ");
builder.append("FROM " + tableName);
Now, my problem is trivial - I need to include commas but if I include a comma there must be a value coming after it, so I cannot do Open, or Open, Close, etc. Is there a neat solution to this trivial, yet surprisingly hard for me problem?
Are you looking for something like Apache Commons' StringUtils.join() methods? I.e:
Collection<String> selections = Arrays.asList("Open", "Low");
String clause = StringUtils.join(selections, ',');
Then just
String sql = "SELECT " + clause + " FROM " + TableName;
1) the typical case is you know a priori how many items you have. So you just loop "n-1", then don't append a comma to the last item.
2) One possible solution:
ArrayList<string> items = new ArrayList<String>();
if(addOpen);
items.add("Open ");
if(addHigh)
items.add("High ");
if(addLow)
items.add("Low ");
if(addSettle)
items.add("Settle ");
StringBuilder builder = new StringBuilder();
int i=0;
for (i=0; i < items.size() - 1; i++) {
builder.append(items[i] + ",");
}
builder.append(items[i] + "FROM " + tableName);
...
There's a couple of ways. The first, which would be my first choice, is don't build your SQL statement at all, and just don't display the fields.
The second is, build the string, and just remove the last comma.
The third is to put each field name into an array, and loop through the array, not putting the trailing comma on the last element.
There is a common trick: Select always a constant, which you aren't interested in:
builder.append ("SELECT 1 ");
if (addOpen)
builder.append (", Open ");
if addHigh)
builder.append (", High ");
if (addLow)
builder.append (", Low ");
if (addSettle)
builder.append (", Settle ");
builder.append ("FROM " + tableName);
An alternative approach works in the other direction, with trailing commas:
builder.append ("SELECT ");
if (addOpen)
builder.append ("Open, ");
if (addHigh)
builder.append ("High, ");
if (addLow)
builder.append ("Low, ");
if (addSettle)
builder.append ("Settle, ");
builder.append ("1 FROM " + tableName);
The trick I use (in a generic semi-untyped pseudo-code) is:
pad = "" # Empty string
builder = "SELECT ";
if (addOpen)
builder += pad + "Open"; pad = ", ";
if (addHigh)
builder += pad + "High"; pad = ", ";
if (addLow)
builder += pad + "Low"; pad = ", ";
if (addSettle)
builder += pad + "Settle"; pad = ", ";
builder += "FROM " + TableName;
An alternative I've seen is to always include the comma (or comma space) after the terms, but trim the last two characters off the value before adding the FROM clause. Your choice...the 'pad' technique works even if you're doing output and can't undo an append.
In your situation I use a solution similarto that suggested by Ryan Stewart, but I prefer Google Guava instead of Apache Commons. I prefer this library because I feel that Apache libraries are to gigantic and interlinked.
Here below is how I would build your SQL string:
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.List;
public class SqlJoiner {
public String buildSql(
boolean addOpen,
boolean addHigh,
boolean addLow,
boolean addSettle,
String tableName
) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(tableName));
Preconditions.checkArgument(addOpen | addHigh | addLow | addSettle );
final List<String> clauses = Lists.newArrayList();
if (addOpen) clauses.add("Open");
if (addHigh) clauses.add("High");
if (addLow) clauses.add("Low");
if (addSettle) clauses.add("Settle");
StringBuilder builder = new StringBuilder();
builder.append("SELECT ");
builder.append(Joiner.on(',').join(clauses));
builder.append(" FROM " + tableName);
return builder.toString();
}
}
The precondition at the start of the method body are meant to be sure that at least one of the boolean option is always true, and that tableName is not null or empty. Always check for precondition of your code, it will save you lot of trouble when you'll make mistakes using your code in the future!
Related
I'm dynamically creating a string
StringBuilder preparedStatement = new StringBuilder();
states.keySet().forEach(key -> preparedStatement
.append(key)
.append(" = ")
.append(":")
.append(key)
.append(" AND "));
I want to remove the last AND at the end of the loop. Is this possible?
Yes. You know how wide the " AND " is, so just use StringBuilder#delete:
preparedStatement.delete(preparedStatement.length() - " AND ".length(),
preparedStatement.length()
);
Or, if you're about to turn it into a String anyway, use StringBuilder#substring:
String result = preparedStatement.substring(0,
preparedStatement.length()
- " AND ".length()
);
Actually I think that StringBuilder does not support deleting in general case. It was designed to build a string.
I reccomend you just not to add " AND " to the builder:
StringBuilder buf = new StringBuilder();
if(someCheck)
buf.add(" AND ");
This is the best choice, because to delete this part, you have to do a string copy.
I am trying to write to a text document with a specific format. Here's what I have right now.
String line = "";
double totalCost = 0;
Node curr = summary.head.next;
while(curr!=summary.tail)
{
line += [an assortment of strings and variables] +"\r";
totalCost += PRICELIST.get(curr.itemName)*curr.count;
curr = curr.next;
}
write.printf("%s" + "%n", line);
This is what the part adding onto line actually looks like.
"Item's name: " + curr.itemName + ", Cost per item: " + NumberFormat.getCurrencyInstance().format(PRICELIST.get(curr.itemName)) +
", Quantity: " + curr.count + ", Cost: " + NumberFormat.getCurrencyInstance().format(PRICELIST.get(curr.itemName)*curr.count) + "\r";
I've tried that with a newline character too. Before I had it working when the print statement was inside the loop meaning it only wrote one line at a time. I want to do it this way because I will have multiple threads writing to this file and this way any thread will not hold the lock for as long.
If using Java 7 or later you can use System.lineSeparator()
Use System.getProperty("line.separator") instead of "\r"
Cache ir for efficiency though.
First of all don't use
while(..){
result += newString
..
}
inside loop. This is very inefficient especially for long texts because each time you call
result += newString
you are creating new String which needs to copy content of result and append to it newStrint. So the more text you processed so far, the more it has to copy so it becomes slower.
Instead use
StringBuilder sb = new StringBuilder();
while(..){
sb.append(newString);
}
result = sb.toString.
which in your case should be something more like
sb.append("Item's name: ").append(curr.itemName)
.append(", Cost per item: ").append(NumberFormat.getCurrencyInstance().format(PRICELIST.get(curr.itemName)))
.append(", Quantity: ").append(curr.count )
.append(", Cost: ").append(NumberFormat.getCurrencyInstance().format(PRICELIST.get(curr.itemName) * curr.count))
.append(System.lineSeparator());
Also instead of
write.printf("%s" + "%n", line);
you should use simpler version, which is
write.println(line);
which automatically add line separator based on OS.
You can also try to use \n\r in combination. This helped in one of my projects.
I am making an application where I will be fetching tweets and storing them in a database. I will have a column for the complete text of the tweet and another where only the words of the tweet will remain (I need the words to calculate which words were most used later).
How I currently do it is by using 6 different .replaceAll() functions which some of them might be triggered twice. For example I will have a for loop to remove every "hashtag" using replaceAll().
The problem is that I will be editing as many as thousands of tweets that I fetch every few minutes and I think that the way I am doing it will not be too efficient.
What my requirements are in this order (also written in comments down bellow):
Delete all usernames mentioned
Delete all RT (retweets flags)
Delete all hashtags mentioned
Replace all break lines with spaces
Replace all double spaces with single spaces
Delete all special characters except spaces
Here is a Short and Compilable Example:
public class StringTest {
public static void main(String args[]) {
String text = "RT #AshStewart09: Vote for Lady Gaga for \"Best Fans\""
+ " at iHeart Awards\n"
+ "\n"
+ "RT!!\n"
+ "\n"
+ "My vote for #FanArmy goes to #LittleMonsters #iHeartAwards"
+ " htt…";
String[] hashtags = {"#FanArmy", "#LittleMonsters", "#iHeartAwards"};
System.out.println("Before: " + text + "\n");
// Delete all usernames mentioned (may run multiple times)
text = text.replaceAll("#AshStewart09", "");
System.out.println("First Phase: " + text + "\n");
// Delete all RT (retweets flags)
text = text.replaceAll("RT", "");
System.out.println("Second Phase: " + text + "\n");
// Delete all hashtags mentioned
for (String hashtag : hashtags) {
text = text.replaceAll(hashtag, "");
}
System.out.println("Third Phase: " + text + "\n");
// Replace all break lines with spaces
text = text.replaceAll("\n", " ");
System.out.println("Fourth Phase: " + text + "\n");
// Replace all double spaces with single spaces
text = text.replaceAll(" +", " ");
System.out.println("Fifth Phase: " + text + "\n");
// Delete all special characters except spaces
text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();
System.out.println("Finaly: " + text);
}
}
Relying on replaceAll is probably the biggest performance killer as it compiles the regex again and again. The use of regexes for everything is probably the second most significant problem.
Assuming all usernames start with #, I'd replace
// Delete all usernames mentioned (may run multiple times)
text = text.replaceAll("#AshStewart09", "");
by a loop copying everything until it founds a #, then checking if the following chars match any of the listed usernames and possibly skipping them. For this lookup you could use a trie. A simpler method would be a replaceAll-like loop for the regex #\w+ together with a HashMap lookup.
// Delete all RT (retweets flags)
text = text.replaceAll("RT", "");
Here,
private static final Pattern RT_PATTERN = Pattern.compile("RT");
is a sure win. All the following parts could be handled similarly. Instead of
// Delete all special characters except spaces
text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();
you could use Guava's CharMatcher. The method removeFrom does exactly what you did, but collapseFrom or trimAndCollapseFrom might be better.
According to the now closed question, it all boils down to
tweet = tweet.replaceAll("#\\w+|#\\w+|\\bRT\\b", "")
.replaceAll("\n", " ")
.replaceAll("[^\\p{L}\\p{N} ]+", " ")
.replaceAll(" +", " ")
.trim();
The second line seems to be redundant as the third one does remove \n too. Changing the first line's replacement to " " doesn't change the outcome an allows to aggregate the replacements.
tweet = tweet.replaceAll("#\\w*|#\\w*|\\bRT\\b|[^##\\p{L}\\p{N} ]+", " ")
.replaceAll(" +", " ")
.trim();
I've changed the usernames and hashtags part to eating also lone # or #, so that it doesn't need to be consumed by the special chars part. This is necessary for corrent processing of strings like !#AshStewart09.
For maximum performance, you surely need a precompiled pattern. I'd also re-suggest to use Guava's CharMatcher for the second part. Guava is huge (2 MB I guess), but you surely find more useful things there. So in the end you can get
private static final Pattern PATTERN =
Pattern.compile("#\\w*|#\\w*|\\bRT\\b|[^##\\p{L}\\p{N} ]+");
private static final CharMatcher CHAR_MATCHER = CharMacher.is(" ");
tweet = PATTERN.matcher(tweet).replaceAll(" ");
tweet = CHAR_MATCHER.trimAndCollapseFrom(tweet, " ");
You can inline all of the things that are being replaced with nothing into one call to replace all and everything that is replaced with a space into one call like so (also using a regex to find the hashtags and usernames as this seems easier):
text = text.replaceAll("#\w+|#\w+|RT", "");
text = text.replaceAll("\n| +", " ");
text = text.replaceAll("[^a-zA-Z0-9 ]+", "").trim();
I need to insert a space after every given character in a string.
For example "abc.def..."
Needs to become "abc. def. . . "
So in this case the given character is the dot.
My search on google brought no answer to that question
I really should go and get some serious regex knowledge.
EDIT : ----------------------------------------------------------
String test = "0:;1:;";
test.replaceAll( "\\:", ": " );
System.out.println(test);
// output: 0:;1:;
// so didnt do anything
SOLUTION: -------------------------------------------------------
String test = "0:;1:;";
**test =** test.replaceAll( "\\:", ": " );
System.out.println(test);
You could use String.replaceAll():
String input = "abc.def...";
String result = input.replaceAll( "\\.", ". " );
// result will be "abc. def. . . "
Edit:
String test = "0:;1:;";
result = test.replaceAll( ":", ": " );
// result will be "0: ;1: ;" (test is still unmodified)
Edit:
As said in other answers, String.replace() is all you need for this simple substitution. Only if it's a regular expression (like you said in your question), you have to use String.replaceAll().
You can use replace.
text = text.replace(".", ". ");
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#replace%28java.lang.CharSequence,%20java.lang.CharSequence%29
If you want a simple brute force technique. The following code will do it.
String input = "abc.def...";
StringBuilder output = new StringBuilder();
for(int i = 0; i < input.length; i++){
char c = input.getCharAt(i);
output.append(c);
output.append(" ");
}
return output.toString();
Problem solved.
I have two methods in my class.
private void retrieveDetails(){
List<String> details = File.getCredentials();
username = details.get(0);
pw = details.get(1);
}
private void checkCredentials() throws IOException {
retrieveDetails();
System.out.println("\nPlease enter USERNAME: ");
String usersName = scan.next();
System.out.println("\nPlease enter PASSWORD: ");
String usersPW = scan.next();
System.out.println("\nEntered details: " + usersName + ", " + usersPW);
System.out.println("\nSystems details: " + userName + ", " + pw);
if (usersName.equals(username) && usersPW.equals(pw)) {
doWork();
} else {
System.out.println("Incorrect credentials");
}
}
I thought I came up with a solution by moving the following up to where my strings are initialized.
List<String> creds = File.getCredentials();
I created a System.out statement to check if the details coming from retrieveDetails() match those entered by the users. They do match - but when the system goes to the else clause instead of executing doWork();
If what is printed is the same then try trimming before comparing. E.g.:
if (usersName.trim().equals(username.trim()) && usersPW.trim().equals(pw.trim())) {
When i have similar problem i do this simple trick:
Print the size of the strings you are comparing because sometimes you have characters like \n or \r which are not visible when you print the string.
First of all, it seems like you have a typo in sysout statement below.
System.out.println("\nEntered details: " + usersName + ", " + usersPW);
System.out.println("\nSystems details: " + userName + ", " + pw); //Should be username
Secondly, you might wanna trim the strings for better string comparison.
Sometimes strings read from file or console can contain unwanted and hard-to-catch empty strings like spaces and tabs. These can be removed by calling .trim() method on strings.
Thus, try using the following code instead:
if (usersName.trim().equals(username.trim()) && usersPW.trim().equals(pw.trim())) {
}
usersName.equals(username) && usersPW.equals(pw).
I have faced these problem also, These kind of equality always tricky, Try to trim the strings that you are going to compare, as well as if you can compare these strings based on their length.
if (usersName.trim().equalsIgnoreCase(username.trim()) && usersPW.trim().equalsIgnoreCase(pw.trim()))
or
if (usersName.trim().length()==username.trim().length && usersPW.trim().length()==pw.trim().length))