Related
Context:
I wish to have a fixed string lenght since I'm formatting an output file, I built 2 functions that should be applied to string based on my string length.
First function: if you want a string X char long, but you got one which is X-Y, this adds spaces 'till desired length is reached, in this particular case, Y. This seems correct, it works
public String formatSpace(String s, int desiredlength){
while (s.length()<desiredlength){
s+=" ";
}
return s;
}
Second function: if you want a string X char long but you got one which is X+Y, this "removes" char until desired length is reached, in this particular case, Y. This seems to be wrong.
public String truncString(String s, int desiredlength){
return s.substr(0,s.length()-desiredlenght);
}
Error:
I apply these two based on string length that I test in another part of code:
[...]//here i built my class
int maxlen = 60;
[...] //here there is more code but it just collects data and I already tested fields
if (field.length()<maxlen){
field = formatSpace(field,maxlen);
}else if (field.length()>maxlen){
field = truncString(field,maxlen);
}
[...] //here i put string on file
Error I get is about string index being negative, I don't know why, I tried code on paper (yes, I know it's dumb) but it works there
Why second function is not working?
Also, it would be better to make one function which format my string, how should I make it?
Solution:
Thanks to everyone who commented, I solved my problem with this single function I wrote, I don't even test string anymore, if they fit my length they're ok, else I format them:
private String formatString(String s, int length) {
while (s.length() < length) {
s += " ";
}
return s.substring(0, length);
}
Second argument in substring function is the length of your new String. Why do you have a substraction ?
This should work :
public String truncString(String s, int desiredlength){
return s.substr(0,desiredlenght);
}
I usually use something like:
private static String spaces(int width) {
// May be more efficient ways of doing this.
return String.join("", Collections.nCopies(width, " "));
}
private static String fixedWidth(String s, int width, boolean padLeft) {
String spaces = spaces(Math.max(0,width-s.length()));
return (padLeft ? spaces + s : s + spaces).substring(0, width);
}
public void test(String[] args) {
String[] tests = {"Hello", "Loooooooooooooong!!!"};
for ( String t: tests) {
System.out.println(fixedWidth(t,10,false));
System.out.println(fixedWidth(t,10,true));
}
}
Or you can try this approach for a simple one line solution.
String test="type something here";
int desiredLength=15;
System.out.println(String.format("%1$-"+desiredLength+"s", test.substring(0, desiredLength)));
The idea is => substring first to the desired length (this will happen if string is longer) and then use String format to fill to desired length with spaces ( %1$-10s is the format for left aligned string filled to 10 spaces).
You have a typo. Inside truncString you write desiredlenght instead of desiredlength.
The problem in the second function is a logic problem. Imagine you're desired length is 4 and you introduce a String which length is 6. If you do the String length - desired length you're going to obtain a String which length is 2.
A way to fix this problem is to return directly the substr: return s.substr(0,desiredLenght)
So I want to match credit card numbers and mask them in 6*4 format. So that only first 6 and last 4 characters will be visible. The characters between will be '*'. I tried to figure it out with a MASK like;
private static final String MASK = "$1***$3";
matcher.replaceAll(MASK);
But could not find out the way to give me back equal length of stars in the middle as the group $2.
Then I implemented the below code and it works.
But what i want to ask if there is a shorter or easier way to do this. Anyone knows it?
private static final String HIDING_MASK = "**********";
private static final String REGEX = "\\b([0-9]{6})([0-9]{3,9})([0-9]{4})\\b";
private static final int groupToReplace = 2;
private String formatMessage(String message) throws NotMatchedException {
Matcher m = Pattern.compile(REGEX).matcher(message);
if (!m.find()) throw new NotMatchedException();
else {
StringBuilder maskedMessage = new StringBuilder(message);
do {
maskedMessage.replace(m.start(groupToReplace), m.end(groupToReplace),
HIDING_MASK.substring(0, (m.end(groupToReplace) - m.start(groupToReplace))));
} while(m.find(m.end()));
return maskedMessage.toString();
}
}
EDIT: Here is an example message to process.
"2017.08.26 20:51 [Thread-Name] [Class-Name] [MethodName] Credit card holder 12345678901234567 02/2022 123 ........."
You can do it simply with this code:
str.replaceAll( "(?<=\\d{6})\\d(?=\\d{4})", "*" );
private String formatMessage(String message) throws NotMatchedException {
if (message.matches(".*\\b\\d{13,19}\\b.*")) {
return message.replaceAll("(?:[.\\b]*)(?<=\\d{6})\\d(?=\\d{4})(?:[.\\b]*)", "*");
} else {
throw new NotMatchedException() ;
}
}
Readable but uncool.
String in = "1234561231234";
String mask = in
.replaceFirst("^\\d{6}(\\d+)\\d{4}$", "$1")
.replaceAll("\\d", "\\*");
String out = in
.replaceFirst("^(\\d{6})\\d+(\\d{4})$", "$1" + mask + "$2");
You can use the following if your text contains multiple credit-card numbers with variable lengths:
str.replaceAll( "\\b(\\d{13,19})\\b", "\u0000$1\u0000" )
.replaceAll( "(?<=\\d{6})(?<=\u0000\\d{6,14})\\d(?=\\d{4,12}\u0000)(?=\\d{4})", "*" )
.replaceAll( "\u0000([\\d*]+)\u0000", "$1" );
Not really readable, though, but it's all in one go.
A simple solution for a 16 char "number":
String masked = num.substring(0,6) + "******" + num.substring(12,16);
For a string of arbitrary length ( >10 ):
String masked = num.substring(0,6)
+ stars(num.length() - 10)
+ num.substring(num.length() - 6);
... where stars(int n) returns a String of n stars. See Simple way to repeat a String in java -- or if you don't mind a limit of 9 stars, "*********".substring(0,n)
Use a StringBuffer and overwrite the desired characters:
StringBuffer buf = new StringBuffer(num);
for(int i=4; i< buf.length() - 6) {
buf.setCharAt(i, '*');
}
return buf.toString();
You could also use buf.replace(int start, int end, String str)
I'm using codingbat.com to get some java practice in. One of the String problems, 'withoutString' is as follows:
Given two strings, base and remove, return a version of the base string where all instances of the remove string have been removed (not case sensitive).
You may assume that the remove string is length 1 or more. Remove only non-overlapping instances, so with "xxx" removing "xx" leaves "x".
This problem can be found at: http://codingbat.com/prob/p192570
As you can see from the the dropbox-linked screenshot below, all of the runs pass except for three and a final one called "other tests." The thing is, even though they are marked as incorrect, my output matches exactly the expected output for the correct answer.
Here's a screenshot of my output:
And here's the code I'm using:
public String withoutString(String base, String remove) {
String result = "";
int i = 0;
for(; i < base.length()-remove.length();){
if(!(base.substring(i,i+remove.length()).equalsIgnoreCase(remove))){
result = result + base.substring(i,i+1);
i++;
}
else{
i = i + remove.length();
}
if(result.startsWith(" ")) result = result.substring(1);
if(result.endsWith(" ") && base.substring(i,i+1).equals(" ")) result = result.substring(0,result.length()-1);
}
if(base.length()-i <= remove.length() && !(base.substring(i).equalsIgnoreCase(remove))){
result = result + base.substring(i);
}
return result;
}
Your solution IS failing AND there is a display bug in coding bat.
The correct output should be:
withoutString("This is a FISH", "IS") -> "Th a FH"
Yours is:
withoutString("This is a FISH", "IS") -> "Th a FH"
Yours fails because it is removing spaces, but also, coding bat does not display the correct expected and run output string due to HTML removing extra spaces.
This recursive solution passes all tests:
public String withoutString(String base, String remove) {
int remIdx = base.toLowerCase().indexOf(remove.toLowerCase());
if (remIdx == -1)
return base;
return base.substring(0, remIdx ) +
withoutString(base.substring(remIdx + remove.length()) , remove);
}
Here is an example of an optimal iterative solution. It has more code than the recursive solution but is faster since far fewer function calls are made.
public String withoutString(String base, String remove) {
int remIdx = 0;
int remLen = remove.length();
remove = remove.toLowerCase();
while (true) {
remIdx = base.toLowerCase().indexOf(remove);
if (remIdx == -1)
break;
base = base.substring(0, remIdx) + base.substring(remIdx + remLen);
}
return base;
}
I just ran your code in an IDE. It compiles correctly and matches all tests shown on codingbat. There must be some bug with codingbat's test cases.
If you are curious, this problem can be solved with a single line of code:
public String withoutString(String base, String remove) {
return base.replaceAll("(?i)" + remove, ""); //String#replaceAll(String, String) with case insensitive regex.
}
Regex explaination:
The first argument taken by String#replaceAll(String, String) is what is known as a Regular Expression or "regex" for short.
Regex is a powerful tool to perform pattern matching within Strings. In this case, the regular expression being used is (assuming that remove is equal to IS):
(?i)IS
This particular expression has two parts: (?i) and IS.
IS matches the string "IS" exactly, nothing more, nothing less.
(?i) is simply a flag to tell the regex engine to ignore case.
With (?i)IS, all of: IS, Is, iS and is will be matched.
As an addition, this is (almost) equivalent to the regular expressions: (IS|Is|iS|is), (I|i)(S|s) and [Ii][Ss].
EDIT
Turns out that your output is not correct and is failing as expected. See: dansalmo's answer.
public String withoutString(String base, String remove) {
String temp = base.replaceAll(remove, "");
String temp2 = temp.replaceAll(remove.toLowerCase(), "");
return temp2.replaceAll(remove.toUpperCase(), "");
}
Please find below my solution
public String withoutString(String base, String remove) {
final int rLen=remove.length();
final int bLen=base.length();
String op="";
for(int i = 0; i < bLen;)
{
if(!(i + rLen > bLen) && base.substring(i, i + rLen).equalsIgnoreCase(remove))
{
i +=rLen;
continue;
}
op += base.substring(i, i + 1);
i++;
}
return op;
}
Something things go really weird on codingBat this is just one of them.
I am adding to a previous solution, but using a StringBuilder for better practice. Most credit goes to Anirudh.
public String withoutString(String base, String remove) {
//create a constant integer the size of remove.length();
final int rLen=remove.length();
//create a constant integer the size of base.length();
final int bLen=base.length();
//Create an empty string;
StringBuilder op = new StringBuilder();
//Create the for loop.
for(int i = 0; i < bLen;)
{
//if the remove string lenght we are looking for is not less than the base length
// and the base substring equals the remove string.
if(!(i + rLen > bLen) && base.substring(i, i + rLen).equalsIgnoreCase(remove))
{
//Increment by the remove length, and skip adding it to the string.
i +=rLen;
continue;
}
//else, we add the character at i to the string builder.
op.append(base.charAt(i));
//and increment by one.
i++;
}
//We return the string.
return op.toString();
}
Taylor's solution is the most efficient one, however I have another solution that is a naive one and it works.
public String withoutString(String base, String remove) {
String returnString = base;
while(returnString.toLowerCase().indexOf(remove.toLowerCase())!=-1){
int start = returnString.toLowerCase().indexOf(remove.toLowerCase());
int end = remove.length();
returnString = returnString.substring(0, start) + returnString.substring(start+end);
}
return returnString;
}
#Daemon
your code works. Thanks for the regex explanation. Though dansalmo pointed out that codingbat is displaying the intended output incorrectly, I through in some extra lines to your code to unnecessarily account for the double spaces with the following:
public String withoutString(String base, String remove){
String result = base.replaceAll("(?i)" + remove, "");
for(int i = 0; i < result.length()-1;){
if(result.substring(i,i+2).equals(" ")){
result = result.replace(result.substring(i,i+2), " ");
}
else i++;
}
if(result.startsWith(" ")) result = result.substring(1);
return result;
}
public String withoutString(String base, String remove){
return base.replace(remove,"");
}
I have a String "speed,7,red,fast". I want to replace the 7 by a String "Seven". How do I do that ?
More details -
7 can be replaced by ANY string and not just "Seven". It could also be "SevenIsHeaven".
I don't want to replace all occurrences of 7. Only 7 at the specified index, ie use the index of 7 to replace 7 by some string.
replaceAll("7", "Seven") //simple as that
EDIT
Then you should look for the specified index.
String input = "test 7 speed,7,red,fast yup 7 tr";
int indexInteresdIn = 13;
if(input.charAt(indexInteresdIn) == '7'){
StringBuilder builder = new StringBuilder(input);
builder.replace(indexInteresdIn, indexInteresdIn+1, "Seven");
System.out.println(builder.toString());
}
Because String is immutable you should use StringBuilder for better performance.
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
yourStringBuiler.replace(
yourStringBuiler.indexOf(oldString),
yourStringBuiler.indexOf(oldString) + oldString.length(),
newString);`
If you want to replace a whole String like the String.replaceAll() does you could create an own function like this:
public static void replaceAll(StringBuilder builder, String from, String to)
{
int index = builder.indexOf(from);
while (index != -1)
{
builder.replace(index, index + from.length(), to);
index += to.length(); // Move to the end of the replacement
index = builder.indexOf(from, index);
}
}
Source:
Replace all occurrences of a String using StringBuilder?
However if you doesn't need it frequently and performance is not that important a simple String.replaceAll() will do the trick, too.
How about simply like below ?
String str = "speed,7,red,fast";
str = str.replace("7", "Seven");
7 can be replaced by ANY string and not just "Seven". It could also be
"SevenIsHeaven". I don't want to replace all occurrences of 7. Only 7
at the specified index.
Or if you wanna use regex to replace the first numeric to a meaningful String.
String str = "speed,7,red,fast";
str = str.replaceFirst("\\d", "Seven");
better way is to store the string itself in an array, spiting it at a space.
String s[];
static int index;
s = in.readLine().spilt(" ");
Now scan the array for the specified word, at the specified index and replace that with the String you desire.
for(int i =0;i<s.length; i++)
{
if((s[i] == "7")&&(i==index))
{
s[i]= "Seven";
}
}
This question already has answers here:
Simple way to repeat a string
(32 answers)
Closed 4 years ago.
I did check the other questions; this question has its focus on solving this particular question the most efficient way.
Sometimes you want to create a new string with a specified length, and with a default character filling the entire string.
ie, it would be cool if you could do new String(10, '*') and create a new String from there, with a length of 10 characters all having a *.
Because such a constructor does not exist, and you cannot extend from String, you have either to create a wrapper class or a method to do this for you.
At this moment I am using this:
protected String getStringWithLengthAndFilledWithCharacter(int length, char charToFill) {
char[] array = new char[length];
int pos = 0;
while (pos < length) {
array[pos] = charToFill;
pos++;
}
return new String(array);
}
It still lacks any checking (ie, when length is 0 it will not work). I am constructing the array first because I believe it is faster than using string concatination or using a StringBuffer to do so.
Anyone else has a better sollution?
Apache Commons Lang (probably useful enough to be on the classpath of any non-trivial project) has StringUtils.repeat():
String filled = StringUtils.repeat("*", 10);
Easy!
Simply use the StringUtils class from apache commons lang project. You have a leftPad method:
StringUtils.leftPad("foobar", 10, '*'); // Returns "****foobar"
No need to do the loop, and using just standard Java library classes:
protected String getStringWithLengthAndFilledWithCharacter(int length, char charToFill) {
if (length > 0) {
char[] array = new char[length];
Arrays.fill(array, charToFill);
return new String(array);
}
return "";
}
As you can see, I also added suitable code for the length == 0 case.
Some possible solutions.
This creates a String with length-times '0' filled and replaces then the '0' with the charToFill (old school).
String s = String.format("%0" + length + "d", 0).replace('0', charToFill);
This creates a List containing length-times Strings with charToFill and then joining the List into a String.
String s = String.join("", Collections.nCopies(length, String.valueOf(charToFill)));
This creates a unlimited java8 Stream with Strings with charToFill, limits the output to length and collects the results with a String joiner (new school).
String s = Stream.generate(() -> String.valueOf(charToFill)).limit(length).collect(Collectors.joining());
In Java 11, you have repeat:
String s = " ";
s = s.repeat(1);
(Although at the time of writing still subject to change)
char[] chars = new char[10];
Arrays.fill(chars, '*');
String text = new String(chars);
To improve performance you could have a single predefined sting if you know the max length like:
String template = "####################################";
And then simply perform a substring once you know the length.
Solution using Google Guava
String filled = Strings.repeat("*", 10);
public static String fillString(int count,char c) {
StringBuilder sb = new StringBuilder( count );
for( int i=0; i<count; i++ ) {
sb.append( c );
}
return sb.toString();
}
What is wrong?
using Dollar is simple:
String filled = $("=").repeat(10).toString(); // produces "=========="
Solution using Google Guava, since I prefer it to Apache Commons-Lang:
/**
* Returns a String with exactly the given length composed entirely of
* the given character.
* #param length the length of the returned string
* #param c the character to fill the String with
*/
public static String stringOfLength(final int length, final char c)
{
return Strings.padEnd("", length, c);
}
The above is fine. Do you mind if I ask you a question - Is this causing you a problem? It seams to me you are optimizing before you know if you need to.
Now for my over engineered solution. In many (thou not all) cases you can use CharSequence instead of a String.
public class OneCharSequence implements CharSequence {
private final char value;
private final int length;
public OneCharSequence(final char value, final int length) {
this.value = value;
this.length = length;
}
public char charAt(int index) {
if(index < length) return value;
throw new IndexOutOfBoundsException();
}
public int length() {
return length;
}
public CharSequence subSequence(int start, int end) {
return new OneCharSequence(value, (end-start));
}
public String toString() {
char[] array = new char[length];
Arrays.fill(array, value);
return new String(array);
}
}
One extra note: it seems that all public ways of creating a new String instance involves necessarily the copy of whatever buffer you are working with, be it a char[], a StringBuffer or a StringBuilder. From the String javadoc (and is repeated in the respective toString methods from the other classes):
The contents of the character array are copied; subsequent modification of
the character array does not affect
the newly created string.
So you'll end up having a possibly big memory copy operation after the "fast filling" of the array. The only solution that may avoid this issue is the one from #mlk, if you can manage working directly with the proposed CharSequence implementation (what may be the case).
PS: I would post this as a comment but I don't have enough reputation to do that yet.
Try this Using the substring(int start, int end); method
String myLongString = "abcdefghij";
if (myLongString .length() >= 10)
String shortStr = myLongString.substring(0, 5)+ "...";
this will return abcde.
Mi solution :
pw = "1321";
if (pw.length() < 16){
for(int x = pw.length() ; x < 16 ; x++){
pw += "*";
}
}
The output :
1321************
Try this jobber
String stringy =null;
byte[] buffer = new byte[100000];
for (int i = 0; i < buffer.length; i++) {
buffer[i] =0;
}
stringy =StringUtils.toAsciiString(buffer);