如何使用java8在字符串中查找第一个重复和非重复字符

时间:2016-09-16 10:59:10

标签: java java-8

我有一个工作示例,找到第一个重复和 字符串中的非重复字符使用java 7

以下是工作示例

public class FindFirstRepeatedAndNonRepeatedChar {
    static void firstRepeatedNonRepeatedChar(String inputString) {

        HashMap<Character, Integer> charCountMap = new HashMap<Character, Integer>();

        char[] strArray = inputString.toCharArray();

        for (char c : strArray) {
            if (charCountMap.containsKey(c)) {
                charCountMap.put(c, charCountMap.get(c) + 1);
            } else {
                charCountMap.put(c, 1);
            }
        }

        for (char c : strArray) {
            if (charCountMap.get(c) == 1) {
                System.out.println("First Non-Repeated Character In '" + inputString + "' is '" + c + "'");

                break;
            }
        }

        for (char c : strArray) {
            if (charCountMap.get(c) > 1) {
                System.out.println("First Repeated Character In '" + inputString + "' is '" + c + "'");

                break;
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter the string :");
        String input = sc.next();
        firstRepeatedNonRepeatedChar(input);
    }
}

有人可以帮我解决如何使用Java8重构上述代码吗?

3 个答案:

答案 0 :(得分:5)

通过一些有用的输入,我用更少的代码调整了我的答案:

public class FirstRepeat {

    public static void main(String[] args) {
        Map<Character, Long> collect =  "abcsdnvs".chars().mapToObj(i -> (char)i).collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
        collect.forEach( (x,y) -> System.out.println( "Key: " + x + " Val: " + y));

        Optional<Character> firstNonRepeat = collect.entrySet().stream().filter( (e) -> e.getValue() == 1).map(e -> e.getKey()).findFirst();
        if(firstNonRepeat.isPresent()) {
            System.out.println("First non repeating:" + firstNonRepeat.get());
        }
        Optional<Character> firstRepeat = collect.entrySet().stream().filter( (e) -> e.getValue() > 1).map(e -> e.getKey()).findFirst();
        System.out.println("First repeating:" + firstRepeat.orElse(null));
    }
}

以上是做什么的:

  1. 创建一个字符流:"abcsdnvs".chars().mapToObj(i -> (char)i)
  2. 使用收集器对这些字符进行分组:.collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
  3. 分组分为3个不同的部分:

    1. 分类
    2. 正如所指出的,我可以使用Function.identity()。相当于x -> x。这意味着我们按实际角色分组。

      1. mapFactory
      2. 我们正在使用LinkedHashMap::new。原因是我们需要保留插入顺序以便找到第一个元素。默认实现使用HashMap,它不会保留插入顺序。

        1. 下游收集器
        2. 由于我们正在使用分组,因此我们需要决定如何收集分组元素。在这种情况下,我们需要发生次数。为此,您可以使用:Collectors.counting(),它将简单地总结给定角色可用的元素数量。

          程序然后打印:

          Key: a Val: 1
          Key: b Val: 1
          Key: c Val: 1
          Key: s Val: 2
          Key: d Val: 1
          Key: n Val: 1
          Key: v Val: 1
          First non repeating:a
          First repeating:s
          

          我们正在使用流操作来查找第一个元素(基于过滤器):

          Optional<Character> firstNonRepeat = collect.entrySet().stream().filter( (e) -> e.getValue() == 1).map(e -> e.getKey()).findFirst();
          

          我们可以对分组元素进行流式处理,使用值(>1表示第一次重复,==1表示第一个非重复字符)。然后findFirst方法返回Element,如果存在这样的元素。

          返回的值是Optional,应该安全处理。如前所述,您可以使用isPresent()检查是否找到了值(请参阅第一个print语句)或使用orElse(...)返回默认值而不是抛出异常(请参阅print statement number 2 where我返回null作为默认值,以防止Optional在没有找到重复的字母的情况下抛出异常)

答案 1 :(得分:2)

请注意,即使对于当前仍然需要的传统,必需的解决方案,Java 8也提供了改进。以下成语:

if (charCountMap.containsKey(c)) {
    charCountMap.put(c, charCountMap.get(c) + 1);
} else {
    charCountMap.put(c, 1);
}

可以用简单的

替换
charCountMap.merge(c, 1, Integer::sum);

这将放置指定的值(1),如果没有先前的值或者使用先前的值和新的值评估指定的函数(这里是方便的方法引用Integer::sum),获取存储的新值。

但是对于这项特定任务,HashMap<Character, Integer>是过度的。首先,我们对实际计数不感兴趣,我们需要知道的是是否遇到了一个字符,以确定是否再次遇到过这个字符,这只是两位状态。由于char s的值范围有限,我们可以轻松使用线性映射而不是散列。换句话说,两个BitSet就足够了:

static void firstRepeatedNonRepeatedChar(String s) {
    if(s.isEmpty()) {
        System.out.println("empty string");
        return;
    }
    BitSet seen=new BitSet(), repeated=new BitSet();
    s.chars().forEachOrdered(c -> (seen.get(c)? repeated: seen).set(c));
    if(repeated.isEmpty()) System.out.println("first unique: "+s.charAt(0));
    else {
        s.chars().filter(repeated::get).findFirst()
                 .ifPresent(c -> System.out.println("first repeated: "+(char)c));
        s.chars().filter(c -> !repeated.get(c)).findFirst()
                 .ifPresent(c -> System.out.println("first unique: "+(char)c));
    }
}

主要任务是遍历所有字符并设置位组seenrepeated中的位,具体取决于之前是否遇到过。

然后,如果没有重复的字符,任务很简单。从那时起,所有角色都是独一无二的,第一个角色也是第一个独特的角色。否则,我们只是再次迭代字符串,停在第一个字符,其repeated位置位/未设置以获得第一个重复/唯一字符。

答案 2 :(得分:1)

这只是一个想法,也许是矫枉过正,但我​​喜欢Holger的答案,所以我想补充一点,只是为了完整性。

基于他的回答,我起草了一个快速的收集器impl,可以在整个过程中使用它来缩短它。

首先收集第一个非重复字符和第一个重复字符的静态收集器作为Optionals。它基于Holger已经指出的相同逻辑:

public class PairCollector {

    public static 
    Collector<Character, ?, Pair<Optional<Character>,Optional<Character>>> get() {
        return Collector.of(PairCollectorImpl::new, PairCollectorImpl::accumulate,
                            PairCollectorImpl::merge, PairCollectorImpl::finish);
    }

    private static final class PairCollectorImpl {

        private BitSet seen=new BitSet(); 
        private BitSet repeated=new BitSet();
        private StringBuilder builder=new StringBuilder();

        public void accumulate(Character val) {
            builder.append(val);
            (seen.get(val)? repeated: seen).set(val);
        }
        PairCollectorImpl merge(PairCollectorImpl other) {
            builder.append(other.builder);
            repeated.or(other.repeated);
            other.seen.stream().forEach(c -> (seen.get(c)? repeated: seen).set(c));
            return this;
        }
        public Pair<Optional<Character>, Optional<Character>> finish() {
            return Pair.of(
                builder.chars().filter(repeated::get).mapToObj(c -> (char)c).findFirst(),
                builder.chars().filter(c -> !repeated.get(c))
                               .mapToObj(c -> (char)c).findFirst());
        }
    }
}

通过这种方式,您现在可以在一个流中处理这两个流并为您吐出结果,例如:

public class FirstRepeat {

    public static void main(String[] args) {

        Pair<Optional<Character>, Optional<Character>> collect = "asdbsjd".chars().mapToObj(c -> (char) c)
                .collect(PairCollector.get());
        collect.getLeft().ifPresent(c -> System.out.println(c));
        collect.getRight().ifPresent(c -> System.out.println(c));

        System.out.println();
        List<Character> toTest = "asdbsjd".chars().mapToObj(c -> (char) c).collect(Collectors.toList());

        Pair<Optional<Character>,Optional<Character>> collect2 = toTest.parallelStream().collect(PairCollector.get());
        collect2.getLeft().ifPresent(c -> System.out.println(c));
        collect2.getRight().ifPresent(c -> System.out.println(c));
    }

}

打印哪些:

s
a

s
a

虽然编写自己的收集器可能有点过分,除非你在代码中的所有地方重复使用它:)