Max Moroz
Max Moroz

Reputation: 73

How to create complex anagram using Streams

I have a task

Write an application that reverses all the words of input text:

E.g. "abcd efgh" => "dcba hgfe"

All non-letter symbols should stay on the same places:

E.g. "a1bcd efg!h" => "d1cba hgf!e"

and it is successfully solved using core java. The core method is as follows:

private String reverseSeparateWord(String word) {

        char sentencePart[] = new char[word.length()];
        sentencePart = word.toCharArray();

        int firstIndex = 0;
        int lastIndex = sentencePart.length - 1;

        for (int i = 0; i < sentencePart.length; i++) {

            char symbolToBeSwapped = sentencePart[lastIndex];

            if (firstIndex < lastIndex) {

                if (!Character.isLetter(sentencePart[firstIndex])) {
                    firstIndex++;

                } else {
                    if (!Character.isLetter(symbolToBeSwapped)) {
                        lastIndex--;
                        symbolToBeSwapped = sentencePart[lastIndex];

                    } else {
                        sentencePart[lastIndex] = sentencePart[firstIndex];
                        sentencePart[firstIndex] = symbolToBeSwapped;

                        firstIndex++;
                        lastIndex--;

                        symbolToBeSwapped = sentencePart[lastIndex];
                    }
                }
            }
        }
        return new String(sentencePart);
    }

Now I sould solve this task using Streams and it is where I'm struggling. I will appreciate any thoughts.

Upvotes: 0

Views: 649

Answers (3)

pero_hero
pero_hero

Reputation: 3184

You could do it by splitting the string into words and then reverting the position of letter characters:

 String input = "a1bcd efg!h a777b 123ab";
 Pattern word = Pattern.compile("\\s+");

 AtomicInteger position = new AtomicInteger(0);
 String collect = word.splitAsStream(input)
     .map(w -> Stream.iterate(w.length() - 1, (i) -> i >= 0, (i) -> --i)
                .collect(() -> new StringBuilder(w),
                   (b, i) -> {
                      if (i == w.length() - 1) position.set(0);
                      if (Character.isLetter(w.charAt(i))) {
                           while (!Character.isLetter(w.charAt(position.get()))
                              && position.get() < w.length())             
                               position.incrementAndGet();
                           } 
                           b.replace(position.get(), 
                                     position.incrementAndGet(),
                                     w.substring(i, i + 1));
                      }
                   },
                   (b1, b2) -> b1.append(b2)
                   ).toString())
    .collect(joining(" "));
System.out.println(collect);

results in:

d1cba hgf!e b777a 123ba

Upvotes: 0

Nowhere Man
Nowhere Man

Reputation: 19565

There is some solution but it does not look too clean:

String input = "abc1cdef |efghk| klm**mnops";

String result = Arrays.stream(input.split("(?<=[^A-Za-z])")) // split by non-letter delimiter and include delimiter
                .map(s -> {
                    int d = s.matches("[A-Za-z]*[^A-Za-z]$") ? 1 : 0; // check for the last non-letter symbol
                    List<Character> list = s.substring(0, s.length() - d).chars().mapToObj(c -> (char) c).collect(Collectors.toList()); // convert word to list of chars
                    Collections.shuffle(list);
                    return list.stream().map(String::valueOf).collect(Collectors.joining()) + s.substring(s.length() - d);
                })
                .collect(Collectors.joining());
System.out.println(result);

Example output:

cab1dfec |gfhek| klm**ompns
bac1fecd |ehgkf| mlk**smonp
etc.

Update A bit cleaner solution with shuffling the char array in a separate method:

private static String shuffle(char[] s) {
    Random random = new Random();
    IntStream.range(0, s.length - 1 - (Character.isLetter(s.length - 1) ? 0 : 1))
                .forEach(i -> {
                        int r = i+1+random.nextInt(s.length - i - 1 - (Character.isLetter(s[s.length - 1]) ? 0 : 1));
                        char c = s[i];
                        s[i] = s[r];
                        s[r] = c;
                });
    return new String(s);
}

// ------------
String input = "abcd1cdefg |efghk| klm**mnops";
String result = Arrays.stream(input.split("(?<=[^A-Za-z])"))
                .map(s -> Test.shuffle(s.toCharArray()))
                .collect(Collectors.joining());
// outputs
cdba1fgdec |ghfke| mkl**ompns
bdac1gfdce |hkefg| mkl**ompns
etc.

Upvotes: 0

WJS
WJS

Reputation: 40064

Using your method, do the following:

  • str is your string of words
  • newStr is the converted string of words
  • reverse is your method that takes a String.
String newStr = Arrays.stream(str.split("\\s+"))
                       .map(s->reverse(s))
                       .collect(Collectors.joining(" "));

System.out.println(str);
System.out.println(newStr);

No need to force fit a stream solution (streams are supposed to make your task easier and your code more readable).

Upvotes: 2

Related Questions