0

Problem
I have written a program to find out every possibility of Uppercase and Lowercase of a character for a given string.

An example would be, Input - "ab"/"Ab" etc. -- any one of those Output - ["ab","Ab","aB","AB"]

Code

Incorrect algorithm - please check below.

public static ArrayList<String> permuteUCLC(String a)
{

    String s=new String(a.toLowerCase());
    ArrayList<String> arr = new ArrayList<>();
    arr.add(a);
    int l = a.length();
    for(int i=0;i<=l;i++)
    {
        for(int j=i+1;j<=l;j++)
        {
            arr.add(s.substring(0,i)+s.substring(i,j).toUpperCase()+s.substring(j,l));
        }
    }
    Collections.sort(arr);
    Collections.reverse(arr);
    return arr;
}

Caution
I have realized after asking the question that my algorithm is wrong. I will try and upload correct algorithm in due course.


Subsequence Code (Correct Code) This is the code for finding all sub-sequences and upper-casing them. Assuming that all characters are unique. How to find the indices and implement it in functional way?

public static void permuteSubsequence(String a)
{
    int n=a.length();
    for(int i=0;i<(1<<n);i++)
    {
        String buff="";
        for(int j=0;j<n;j++)
        {
            if(((1<<j)&i)!=0) 
            {
                buff=buff+new Character(a.charAt(j)).toString().toUpperCase();
            }
            else
            {
                buff = buff + a.charAt(j);
            }
        }
        System.out.println(buff);
    }
}

Pick up the indices from the above case. i.e., the indices of 1's and uppercase them.


Request

How to convert the above code into functional style using Java streams?

The problem I am facing is simulating indices range in the map method. Also, is there a way to generate Strings's Stream for copying same string into all elements, something similar to IntStream.range(a,b)?

public static List<String> permuteStreams(String a)
{
    int l=(int)(Math.pow(2,a.length())-1)
    ArrayList<String> al = new ArrayList<>();
    for(int i=0;i<=l;i++)
        al.add(a);//Generate a stream and copy back into arraylist maybe if possible?

    List<String> sl = al.stream()
      .map()//incomplete code
      .collect(Collectors.toList());
    return sl;
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Tarun Maganti
  • 3,076
  • 2
  • 35
  • 64
  • 1
    "I have written a program to find out all permutations of Uppercase and Lowercase for a given string." That's horrifying, I hope this is an academic exercise. – Randy the Dev Nov 15 '16 at 23:32
  • Would you please elaborate? Is the statement unnecessary or the problem is not clear? – Tarun Maganti Nov 15 '16 at 23:33
  • Why do you need every possible permutation of a string with varying letter case? – Randy the Dev Nov 15 '16 at 23:33
  • I think, the statement is misleading. Not every possible permutation. It's every possible case. That is ab becomes ab,Ab,aB,AB. – Tarun Maganti Nov 15 '16 at 23:37
  • The non-stream version, given "abc", will produce "abc", "abC", "aBc", "Abc", "aBC", "ABc", and "ABC". It will not produce "AbC". Is that intentional? – Douglas Nov 15 '16 at 23:40
  • 2
    To answer your side question - if I needed a list with X copies of an immutable object, I'd use `Arrays.fill` to fill an array of the size I wanted, then `Arrays.asList` to get a List out. – Sbodd Nov 15 '16 at 23:42
  • @Douglas No, seems like my code is flawed. I have to correct it. – Tarun Maganti Nov 15 '16 at 23:42
  • Why do you require every possible case though? This problem will take O^n space. Why do you need every possible combination of letter case? – Randy the Dev Nov 15 '16 at 23:59
  • @AndrewDunn It's an excercise. The input size is very small. I think if we don't want to use O(^n) space, passing a string into a stream and applying operations on each character and printing them might do. Assuming input size is very small. – Tarun Maganti Nov 16 '16 at 00:02
  • I'm able to think a solution but not able to implement it. It goes like this. Find end points of a subsequence. Apply the above procedure i.e., the incorrect code only on the characters in elements of subsequences. – Tarun Maganti Nov 16 '16 at 00:06
  • I mean it's impossible not to use O^n space, as a string will have 2^n possible permutations. Since this is a homework exercise, I believe it's your responsibility to solve the problem. Go over your course material, think about where else 2^n is used. – Randy the Dev Nov 16 '16 at 00:08
  • It's not homework. I'm practicing for interview to a college. I'm not able to figure out how to make it completely in a functional way and now the algorithm is also wrong. I'll write the correct code ASAP, I get it. – Tarun Maganti Nov 16 '16 at 00:09
  • I now know there has to be an exhaustive search over it. Build an n [size of the string] ary tree to traverse all possibilities. The problem, I wanted to do it traverse all possible cases. – Tarun Maganti Nov 16 '16 at 00:14
  • 2
    Expressions like `new Character(a.charAt(j)).toString().toUpperCase()` are really horrible. Why don’t you look for a straight-forward solution, i.e. `Character.toUpperCase(a.charAt(j))` first? – Holger Nov 16 '16 at 12:06
  • 2
    @Sbodd: if you need a list with X copies of an object, just use [`Collections.nCopies(X, object)`](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#nCopies-int-T-), no need to fill an array first… if you need a stream of `X` repetitions of `object`, you can call `stream()` right on that `nCopies` list, it’ll do the same as `IntStream.range(0, X).mapToObj(ignored -> object)`… – Holger Nov 17 '16 at 17:26
  • @Holger - neat, I didn't know about that. Thanks! – Sbodd Nov 17 '16 at 18:15

3 Answers3

2

The problem I am facing is simulating indices range in the map method.

Change the question around - start from indices using IntStream.range (or rangeClosed). See this answer for more information. Here is a (mostly) stream-oriented version of your original code:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;

public static List<String> permuteUCLCStream(String a)
{
    String s = a.toLowerCase();
    int l = a.length();
    Stream<String> result = IntStream
            .rangeClosed(0, l)
            .mapToObj(i -> IntStream
                    .rangeClosed(i + 1, l)
                    .mapToObj(j -> s.substring(0, i) + s.substring(i, j).toUpperCase() + s.substring(j, l)))
            .flatMap(identity());
    List<String> arr = Stream.concat(Stream.of(a), result).sorted().collect(toList());
    Collections.reverse(arr);
    return arr;
}
Community
  • 1
  • 1
Alex Taylor
  • 8,343
  • 4
  • 25
  • 40
  • Thank you. A wonderful approach. But, I changed the question for considering not just contiguous substrings but also subsequences. Also, if you could explain rangeClosed()? Until, I find such exact answer, this'll do. – Tarun Maganti Nov 16 '16 at 00:22
2

There are a few things to consider for this. First, the stream api was built with the assumption that position in the stream is not relevant to whatever operation you're doing to each element. To fit that naturally, you need to redesign your algorithm to not care about index numbers.

Second, you're not mapping any single letter to any particular result (or set of results), so using a stream of the input String's characters isn't particularly helpful. You should be looking for something else to use a stream for.

The algorithm you came up with for the fixed non-stream approach is actually a pretty good fit for streaming - stick the contents of buff in a stream and do all the alterations to it by the stream api. I think this should do the trick:

public static List<String> permuteStreams(String a) {
    Stream<StringBuilder> partialSolutions = Stream.of(new StringBuilder(a.length()));

    for (char c : a.toCharArray()) {
        partialSolutions = partialSolutions.flatMap(solution -> Stream.of(
                new StringBuilder(solution).append(Character.toLowerCase(c)),
                solution.append(Character.toUpperCase(c))));
    }

    return partialSolutions.map(StringBuilder::toString).collect(Collectors.toList());
}

Or, if you don't mind approximately double the string copy operations:

public static List<String> permuteStreams(String a) {
    Stream<String> partialSolutions = Stream.of("");

    for (char c : a.toCharArray()) {
        partialSolutions = partialSolutions.flatMap(solution -> Stream.of(
                solution + Character.toLowerCase(c),
                solution + Character.toUpperCase(c));
    }

    return partialSolutions.collect(Collectors.toList());
}

Might actually save some space that way, due to StringBuilder allocating extra capacity in case of additions you're not going to do.

Douglas
  • 5,017
  • 1
  • 14
  • 28
2

Don’t permutate strings by splitting and joining them, that is quite an expensive operation and completely unnecessary. Consider that “uppercase” and “lowercase” are exactly two states and permuting combinations of two-state items should ring a bell, we’re talking about binary numbers. Integer numbers in a computer are combinations of bits, having two states, and iterating through all possible permutations of these bits is as easy as iterating through an integer number range.

I.e. the binary number representation of the range 0, 1, 2, 3, 4, 5, 6, 7 is 000, 001, 010, 011, 100, 101, 110, 111. Now imagine 0 to stand for “lowercase” and 1 to stand for “uppercase” for a three character string and you’re almost done.

So the remaining task is to turn the characters of a String to uppercase or lowercase according to whether the associated bit is set or not. There are several ways to achieve this. The following code creates an initially lowercase string as starting point of all iterations and toggles the characters to uppercase, if a bit is set:

public static void permuteSubsequence(String s) {
    if(s.isEmpty()) {
        System.out.println();
        return;
    }
    String lower = s.toLowerCase(), upper = s.toUpperCase();
    if(s.length()!=lower.length() || s.length()!=upper.length())
        throw new UnsupportedOperationException("non trivial case mapping");
    LongStream.range(0, 1L<<Math.min(lower.length(), 62))
        .mapToObj(l -> {
            StringBuilder sb=new StringBuilder(lower);
            BitSet.valueOf(new long[] { l }).stream()
                  .forEach(ix -> sb.setCharAt(ix, upper.charAt(ix)));
            return sb.toString();
        })
        .forEach(System.out::println);
}

Note that this implementation cheats by only permuting the first 62 characters of longer strings, as the signed long used for iterating doesn’t allow more, but permuting 62 characters already allows 4611686018427387904 combinations, so even if we assume that printing one variant takes only one nanosecond, we would need way more than hundred years to print them all. So you’ll never notice the cheating.

A uppercase/lowercase conversion of a string doesn’t have to produce a string of the same length. This implementation will reject strings with nontrivial case mapping, for which this kind of permutation is not possible.

One thing to improve, is to leave out characters that don’t have a different uppercase and lowercase form. This can be done by identifying the permutable characters (their positions) first and permute only these characters:

public static void permuteSubsequence(String s) {
    int[] permutable = IntStream.range(0, s.length())
        .filter(i->Character.toLowerCase(s.charAt(i))!=Character.toUpperCase(s.charAt(i)))
        .toArray();
    if(permutable.length == 0) {
        System.out.println(s);
        return;
    }
    String lower = s.toLowerCase(), upper = s.toUpperCase();
    if(s.length()!=lower.length() || s.length()!=upper.length())
        throw new UnsupportedOperationException("non trivial case mapping");
    LongStream.range(0, 1L<<Math.min(permutable.length, 62))
        .mapToObj(l -> {
            StringBuilder sb=new StringBuilder(lower);
            BitSet.valueOf(new long[] { l }).stream()
                  .map(bit -> permutable[bit])
                  .forEach(ix -> sb.setCharAt(ix, upper.charAt(ix)));
            return sb.toString();
        })
        .forEach(System.out::println);
}

With this, permuteSubsequence("Mr.X"); will print

mr.x
Mr.x
mR.x
MR.x
mr.X
Mr.X
mR.X
MR.X
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    Note that if you detect a non-trivial case mapping, you can try to use the [`Normalizer`](https://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) to convert it to a form where this permutation is possible. For some scenarios, this would work. Any other solution that only considers single `char`s won’t work in these scenarios. – Holger Nov 16 '16 at 13:16