简体   繁体   中英

Java string problem: replacing specific part of a string

A method replacement replaces all names (from given String a) in [Name] or {Name} brackets, with telephone numbers if [] these brackets, or e-mails if {} these brackets. The address book is represented with array tel, whose elements can be "Tel Name telephoneNumber" or "Mail Name mail". For example if input is: "You can contact jake via phone number [Jake] or via email {Jake}", output should be "You can contact jake via phone number +12345 or via email jake@gmail.com", and tel elements are "Tel Jake +12345" and "Mail Jake jake@gmail.com". If the given name does not exist in address book do nothing with the string. The problem that I have is when it comes to replacing substrings I use method replaceFirst which will replace the first occurrence of the substring that I want to replace.

Maybe the shorter question would be how to replace specific part of string?

public static String replacement(String a, String[] tel) {

    for (int i = 0; i<a.length()-1; i++) {
        char c = a.charAt(i);
        if (c=='[') {
            int ind = a.indexOf(']', i);
            String name = a.substring(i+1, ind);
            for (int j=0; j<tel.length; j++) {
                int ind1 = tel[j].indexOf(' ', 4);
                String name1 = tel[j].substring(4, ind1);
                String p = tel[j].substring(0,3);
                String help = "Tel";
                int temp = p.compareTo(help);
                if (ime.equals(ime1)==true && temp==0) {
                    String telephone = tel[j].substring(ind1+1, tel[j].length());
                    a = a.replaceFirst(name, telephone);
                } 
            } 
        }
        if (c=='{') {
            int ind = a.indexOf('}', i);
            String name = a.substring(i+1, ind);
            for (int j=0; j<tel.length; j++) {
                int ind1 = tel[j].indexOf(' ', 5);
                String name1 = tel[j].substring(5, ind1);
                String p = tel[j].substring(0,4); 
                if (name.equals(name1) && p.compareTo("Mail")==0) {
                    String mail = tel[j].substring(ind1+1, tel[j].length());
                    
                    a = a.replaceFirst(name, mail);

                }
            }
        }
    }
    return a;
}

Main:

String a = "In NY you can contact peter via telephone number [Peter] or e-mail {Peter}. In London you can contact anna via telephone number [Anna] or e-mail {Anna}."
            + "In Chicago you can contact shawn via telephone number [Shawn] or e-mail {Shawn}";
String [] tel = {"Mail Peter peter@gmail.com", "Tel Anna +3456","Tel Shawn +1234", "Mail Shawn shawn@yahoo.com"};
String t = replacement(a,tel);
System.out.println(t);

Console:

In NY you can contact peter via telephone number [peter@gmail.com] or e-mail {peter@gmail.com}.
In London you can contact anna via telephone number [+3456] or e-mail {Anna}.In Chicago you can 
contact shawn via telephone number [+1234] or e-mail {shawn@yahoo.com}

Instead of encoding the type of the data (email vs phone number) and the replacement key into strings, I would put the data into separate variables and ues data structures like Map :

Map<String, String> tel = Map.of("Anna", "+3456", "Shawn", "+1234");
Map<String, String> mail = Map.of("Peter", "peter@gmail.com", "Shawn", "shawn@yahoo.com");
String t = replacement(a, tel, mail);

The replacement function could use a regular expression to find the substrings that match the key words you want to replace [something] and {something} . It would check which one it found, and add a replacement using the telephone or email it finds in the map data structure.

private static String replacement(String a, Map<String, String> tel, Map<String, String> mail) {
    Pattern compile = Pattern.compile("\\{(.*?)\\}|\\[(.*?)\\]");
    Matcher matcher = compile.matcher(a);
    StringBuilder sb = new StringBuilder();

    // Find substrings matching {something} and [something]
    while (matcher.find()) {
        String matched = matcher.group(0);

        // Which was it, { or [ ?
        if (matched.charAt(0) == '{') {
            // Email. Replace from "mail"
            String emailAddress = mail.getOrDefault(matcher.group(1), matched);
            matcher.appendReplacement(sb, emailAddress);
        } else if (matched.charAt(0) == '[') {
            // Telephone. Replace from "tel"
            String phoneNumber = tel.getOrDefault(matcher.group(2), matched);
            matcher.appendReplacement(sb, phoneNumber);
        }
    }
    matcher.appendTail(sb);
    return sb.toString();
}

Handling of strings in a specified format is done best using regular expressions . You define a specified pattern and after you find a part matching your pattern, you can replace it or analyze further.

It's best to write your code to make it easily extensible. For example - if a new contact form is added (home address, fax, business phone number), it should be easy to handle it in the code. Your solution makes it harder to resolve such problems as a whole new if branch is required and it's easy to make a mistake, it also makes the code less readable.

When dealing with a kind of dictionary (like your input String array), it's worth using a Map as it makes the processing faster and the code more readable. When a constant values are present, it's worth to define them too - as constants or enum values. Also - Java allows for writing more functional and more readable, functional-style code instead of nested for-eaches - it's worth using those features (JDK8+).

Please, find the code snippet below and a whole project with tests comparing your solution to mine on GitHub - you can view it there or clone the repository and verify the code yourself:

// we can simply add new contact types and their matchers using the constant below
private static final Map<Pattern, ContactType> CONTACT_PATTERNS = Map.of(
        Pattern.compile("\\[(\\S+)]"), ContactType.TEL,
        Pattern.compile("\\{(\\S+)}"), ContactType.MAIL
);

@Override
public String replace(String input, String[] dictionary) {
    // we're mapping the dictionary to make it easier to use and more readable (also in debugging)
    Map<ContactType, Map<String, String>> contactTypeToNameToValue =
            Arrays.stream(dictionary)
                  .map(entry -> entry.split(" ")) // dictionary entry is split by ' ' character
                  .collect(groupingBy(entry -> ContactType.fromString(entry[0]), // first split part is the contact type
                                      toMap(entry -> entry[1], // second part is the person's name
                                            entry -> entry[2]))); // third part is the contact value
    String output = input;
    for (Map.Entry<Pattern, ContactType> entry : CONTACT_PATTERNS.entrySet()) {
        Pattern pattern = entry.getKey();
        ContactType contactType = entry.getValue();
        output = pattern.matcher(output)
                        .replaceAll(matchResult -> {
                            String name = matchResult.group(1);
                            // we search our dictionary and get value from it or get the original value if nothing matches given name
                            return Optional.ofNullable(contactTypeToNameToValue.get(contactType))
                                           .map(nameToValue -> nameToValue.get(name))
                                           .orElseGet(matchResult::group);
                        });
    }
    return output;
}

public enum ContactType {
    TEL,
    MAIL;

    private static ContactType fromString(String value) {
        return Arrays.stream(values())
                     .filter(enumValue -> enumValue.name().equalsIgnoreCase(value))
                     .findFirst()
                     .orElseThrow(RuntimeException::new);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM