4

So i'm working on a project in which I have to convert a String containing greek letters in to a string containing the english representation for this letter. So a greek α would become alpha and Α would become Alpha.

I have created a HashMap which has the appropriate conversions from Unicode Character to a normal String.

I have used a simple For loop to check if a Char in the input String is a Key in the HashMap and if it is then i'll append its replacement using a StringBuilder. Here is the code I use to do this:

char[] ca = snippet.toCharArray();
        StringBuilder out = new StringBuilder();
        for (char c : ca) {
            if (GREEK_LETTER_DICT.containsKey(Character.toString(c))) {
                out.append( GREEK_LETTER_DICT.get(Character.toString(c)));
            } else {
                out.append(c);
            }
        }
    return out.toString();

This is in a public static method with String as input. The thing I want to know is if it is possible to do the same with a lambda expression? I have already found a couple of solutions replacing a Character in String but they do not use a HashMap/Dictionary.

I understand that it is completely useless to just convert this into a lambda expression for the sake of using a lambda expression, but because I have another 7 of these functions shortening my code by almost 60% I would like to see if it is at all possible to do this in "one line" of code. This way i can decrease the number of separate methods I use and get cleaner code.

So far I have found a way to convert a String to a Stream using:

String.chars()

Then converting this IntStream to a Stream<Character> with

.mapToObj(ch -> (char) ch)

and filtering to see if the Character is in the HashMap with

.filter(ch -> (GREEK_LETTER_DICT.containsKey(ch)))

The problem lies in the fact that I am not able to

  1. append the original Character to the output String if it is not in the HashMap
  2. replace the Character with the String in the HashMap

So any help on these two points is appreciated. I found out that sometimes I think the wrong way around because instead of seeing if a Character in a String is equal to a Character in a list of options, I should have checked if that list of options returns a positive index. So this (PseudeoCode):

"StringWithOptions".indexOf('characterLiteral')

instead of this:

Character.equals("One|of|these")

3
  • 3
    Based on code in question str.chars().forEach(character -> out.append(GREEK_LETTER_DICT.getOrDefault(Character.toString(c), c))); Commented Jul 7, 2016 at 13:25
  • Well that was fast ;-) Thanks for the reply. with this code is was able to get it to work. I will put it up here. I had to make a few changes to the code. Commented Jul 7, 2016 at 13:35
  • @SMA beautiful. I'd advise you to put this into an answer. Commented Jul 7, 2016 at 13:36

4 Answers 4

4

How about that:

public static String replaceGreekLetters(String snippet) {
    return snippet
            .chars()
            .mapToObj(c -> (char) c)
            .map(c -> GREEK_LETTER_DICT.getOrDefault(c, c.toString()))
            .collect(Collectors.joining());
}
Sign up to request clarification or add additional context in comments.

6 Comments

this is better than manipulating a state variable(Stringbuilder), it provides better parallelism :)
@niceman The filter this is for can be executed in parallel. would you recommend not using the StringBuilder then?
@NathanvanDalen yes I would
I couldn't get this to work without adding c.toString() to the first parameter of the getOrDefault also. I think the mapToObj should map to String.
@4Castle My initial TS contains an error where GREEK_LETTER_DICT.containsKey(Character.toString(c)) should be GREEK_LETTER_DICT.containsKey(c), because the HashMap is <Character, String> but I forgot to change that. @xehpuk's code is correct.
|
2

I would do something like this:

str
    .chars()
    .forEach(
        character -> out.append(
            GREEK_LETTER_DICT.getOrDefault(Character.toString(c), c)
        )
    );

2 Comments

isn't map better ? the OP would then not need the StringBuilder at all ?
This is just an excuse to use streams. Use a normal for each loop if it's going to be stateful.
2

There are several good solutions using Steams. I like the simpler ones more:

  • avoid conversion from char to String
  • avoid usage of StringBuffer outside the Stream pipline
  • use predefined Collector
public class ReplaceGreek {

    private Map<String, String> DICT = new HashMap<>();
    {
        DICT.put("α", "alpha");
    }

    public String replace(final String original) {
        return Arrays.stream(original.split(""))
                .map(c -> DICT.getOrDefault(c, c))
                .collect(Collectors.joining());
    }
}

And it even works!

public class ReplaceGreekTest {

    @Test
    public void test() {
        ReplaceGreek rg = new ReplaceGreek();
        String greek = "This is greek: α";

        String nogreek = rg.replace(greek);

        assertEquals("This is greek: alpha", nogreek);
    }

}

4 Comments

The OP is using a Map<Character, String>. Good solution though!
But is this Thread Safe? I guess it is because String itself is. This is indeed a very elegant solution with just 3 lines of code. A big difference in readability compared to my 10 lines earlier. @4castle: I can easily switch back to <String, String> though.
@NathanvanDalen Yes, it's thread safe. It does very similar things to the other solution.
You may use Pattern.compile("").splitAsStream(original) instead of Arrays.stream(original.split("")) to avoid the creation of the temporary array. Still, original.chars().mapToObj(c -> Character.toString((char)c)) is more efficient.
1

So using the code from SMA and Martin Seeler I was able to get it to work. The resulting code now returns a String with the correct results and passes all my unit tests.

This is the resulting code:

    return snippet.chars()
            .mapToObj(c -> (char) c)
            .collect(Collector.of(StringBuilder::new,
                    (stringBuilder, c) -> stringBuilder.append(GREEK_LETTER_DICT.getOrDefault(c,c.toString())),
                    StringBuilder::append,
                    StringBuilder::toString));

The Code from SMA had one problem where the HashMap uses <Character, String> and not <String, String>. So I had to change the parameters of #getOrDefault. Combining this with .mapToObj(c -> (char) c) did the trick.

Now lets see if I can change this:

(stringBuilder, c) -> stringBuilder.append(GREEK_LETTER_DICT.getOrDefault(c,c.toString())),
                        StringBuilder::append

to something like StringBuilder::append(GREEK_LETTER_DICT.getOrDefault(c,Character.toString(c))

1 Comment

…you consider this shorter/simpler than your original loop?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.