如何使用流将此程序转换为java 8功能样式?

时间:2016-11-15 23:24:27

标签: java algorithm functional-programming java-8 java-stream

问题
我编写了一个程序来查找给定字符串的字符的大写和小写的所有可能性。

一个例子是, 输入 - " ab" /" Ab"等等 - 其中任何一个 输出 - [" ab"," Ab"," aB"," AB"]

代码

算法不正确 - 请在下方查看。

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;
}

注意
在提出问题后我意识到我的算法是错误的。我会在适当的时候尝试上传正确的算法。

后续代码(正确代码) 这是用于查找所有子序列并对其进行上限的代码。假设所有字符都是唯一的。如何找到指数 并以功能的方式实现它?

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);
    }
}

从上述案例中选取指数。即,1&s的指数和大写它们。

请求

如何使用Java流将上述代码转换为函数样式?

我面临的问题是在map方法中模拟索引范围。 另外,有没有办法生成字符串流用于将相同的字符串复制到所有元素中,类似于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;
}

3 个答案:

答案 0 :(得分:2)

  

我面临的问题是在map方法中模拟索引范围。

使用IntStream.range(或rangeClosed)从索引更改问题 - 开始。有关详细信息,请参阅this answer。这是原始代码的(主要)面向流的版本:

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;
}

答案 1 :(得分:2)

有一些事情需要考虑。首先,构建流api时假设流中的位置与您对每个元素执行的操作无关。为了自然地适应这种情况,您需要重新设计算法而不关心索引号。

其次,您没有将任何单个字母映射到任何特定结果(或结果集),因此使用输入字符串字符的流不是特别有用。您应该寻找其他东西来使用流。

您为固定的非流方法提出的算法实际上非常适合流式传输 - 将buff的内容粘贴到流中并通过流API执行对它的所有更改。我认为这应该可以解决问题:

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());
}

或者,如果您不介意大约两倍的字符串复制操作:

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());
}

实际上可以通过这种方式节省一些空间,因为StringBuilder分配了额外的容量以防你不想做的事情。

答案 2 :(得分:2)

不要通过拆分和连接来排列字符串,这是非常昂贵的操作,完全没必要。考虑到“大写”和“小写”恰好是两个状态,并且两个状态项的置换组合应该响铃,我们在讨论binary numbers。计算机中的整数是位的组合,具有两种状态,并且迭代这些位的所有可能的排列就像迭代整数范围一样容易。

即。范围0, 1, 2, 3, 4, 5, 6, 7的二进制数表示形式为000, 001, 010, 011, 100, 101, 110, 111。现在假设0代表“小写”而1代表“大写”代表三个字符的字符串并且你差不多完成了。

所以剩下的任务是根据相关位是否设置将String的字符转换为大写或小写。有几种方法可以实现这一目标。以下代码创建一个最初的小写字符串作为所有迭代的起始点,如果设置了一个位,则将字符切换为大写:

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);
}

请注意,此实现仅通过置换较长字符串的第一个62字符来作弊,因为用于迭代的签名long不允许更多,但置换62个字符已经允许4611686018427387904组合,所以即使我们假设打印一个变体只需要一个纳秒,我们需要超过一百年的时间来打印它们。所以你永远不会注意到作弊。

字符串的大写/小写转换不必生成相同长度的字符串。此实现将拒绝具有非平凡案例映射的字符串,对此无法进行此类排列。

要改进的一件事是省略不具有不同大写和小写形式的字符。这可以通过首先识别可置换字符(它们的位置)并仅置换这些字符来完成:

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);
}

这样,permuteSubsequence("Mr.X");将打印

mr.x
Mr.x
mR.x
MR.x
mr.X
Mr.X
mR.X
MR.X