问题
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;
}
回答1:
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
回答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;
}
回答3:
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.
来源:https://stackoverflow.com/questions/40621512/how-do-i-convert-this-program-into-java-8-functional-style-using-streams