Java 编程问题:一、字符串、数字和数学2

简介: Java 编程问题:一、字符串、数字和数学2

5 元音和辅音计数


以下代码适用于英语,但取决于您所涵盖的语言种类,元音和辅音的数量可能会有所不同,因此应相应调整代码。


此问题的第一个解决方案需要遍历字符串并执行以下操作:


   我们需要检查当前字符是否是元音(这很方便,因为英语中只有五个纯元音;其他语言有更多元音,但数量仍然很小)。


   如果当前字符不是元音,则检查它是否位于'a'和'z'之间(这意味着当前字符是辅音)。


注意,最初,给定的String对象被转换为小写。这有助于避免与大写字符进行比较。例如,仅对'a'进行比较,而不是对'A'和'a'进行比较。


此解决方案的代码如下:

private static final Set<Character> allVowels
            = new HashSet(Arrays.asList('a', 'e', 'i', 'o', 'u'));
public static Pair<Integer, Integer> 
    countVowelsAndConsonants(String str) {
  str = str.toLowerCase();
  int vowels = 0;
  int consonants = 0;
  for (int i = 0; i < str.length(); i++) {
    char ch = str.charAt(i);
    if (allVowels.contains(ch)) {
      vowels++;
    } else if ((ch >= 'a' && ch <= 'z')) {
      consonants++;
    }
  }
  return Pair.of(vowels, consonants);
}



在 Java8 函数式中,可以使用chars()filter()重写此代码:

private static final Set<Character> allVowels
            = new HashSet(Arrays.asList('a', 'e', 'i', 'o', 'u'));
public static Pair<Long, Long> countVowelsAndConsonants(String str) {
  str = str.toLowerCase();
  long vowels = str.chars()
    .filter(c -> allVowels.contains((char) c))
    .count();
  long consonants = str.chars()
    .filter(c -> !allVowels.contains((char) c))
    .filter(ch -> (ch >= 'a' && ch<= 'z'))
    .count();
  return Pair.of(vowels, consonants);
}


相应地过滤给定的字符串,count()终端操作返回结果。依赖partitioningBy()将减少代码,如下所示:

Map<Boolean, Long> result = str.chars()
  .mapToObj(c -> (char) c)
  .filter(ch -> (ch >= 'a' && ch <= 'z'))
  .collect(partitioningBy(c -> allVowels.contains(c), counting()));
return Pair.of(result.get(true), result.get(false));

完成!现在,让我们看看如何计算字符串中某个字符的出现次数。




6 计算某个字符的出现次数


此问题的简单解决方案包括以下两个步骤:

  1. 将给定字符串中出现的每个字符替换为""(基本上,这类似于删除给定字符串中出现的所有字符)。
  2. 从初始字符串的长度中减去在第一步中获得的字符串的长度。



此方法的代码如下:

public static int countOccurrencesOfACertainCharacter(
    String str, char ch) {
  return str.length() - str.replace(String.valueOf(ch), "").length();
}


以下解决方案还包括 Unicode 代理项对:

public static int countOccurrencesOfACertainCharacter(
    String str, String ch) { 
  if (ch.codePointCount(0, ch.length()) > 1) {
    // there is more than 1 Unicode character in the given String
    return -1; 
  }
  int result = str.length() - str.replace(ch, "").length();
  // if ch.length() return 2 then this is a Unicode surrogate pair
  return ch.length() == 2 ? result / 2 : result;
}



另一个易于实现且快速的解决方案包括循环字符串(一次遍历)并将每个字符与给定的字符进行比较。每一场比赛增加一个计数器:

public static int countOccurrencesOfACertainCharacter(
    String str, char ch) {
  int count = 0;
  for (int i = 0; i < str.length(); i++) {
    if (str.charAt(i) == ch) {
      count++;
    }
  }
  return count;
}



涵盖 Unicode 代理项对的解决方案包含在本书附带的代码中。在 Java8 函数风格中,一种解决方案是使用filter()reduce()。例如,使用filter()将产生以下代码:

public static long countOccurrencesOfACertainCharacter(
    String str, char ch) {
  return str.chars()
    .filter(c -> c == ch)
    .count();
}


涵盖 Unicode 代理项对的解决方案包含在本书附带的代码中。


对于第三方库支持,请考虑 Apache Commons Lang、StringUtils.countMatches()、Spring Framework、StringUtils.countOccurrencesOf()和 Guava、CharMatcher.is().countIn()。



7 将字符串转换为intlongfloatdouble

让我们考虑以下字符串(也可以使用负数):

private static final String TO_INT = "453"; 
private static final String TO_LONG = "45234223233"; 
private static final String TO_FLOAT = "45.823F";
private static final String TO_DOUBLE = "13.83423D";

String转换成IntegerLongFloatDouble对象,可以通过以下 Java 方法来完成:Integer.valueOf()Long.valueOf()Float.valueOf()Double.valueOf()

Integer toInt = Integer.valueOf(TO_INT);
Long toLong = Long.valueOf(TO_LONG);
Float toFloat = Float.valueOf(TO_FLOAT);
Double toDouble = Double.valueOf(TO_DOUBLE);


String无法成功转换时,Java 抛出NumberFormatException异常。以下代码不言自明:

private static final String WRONG_NUMBER = "452w";
try {
  Integer toIntWrong1 = Integer.valueOf(WRONG_NUMBER);
} catch (NumberFormatException e) {
  System.err.println(e);
  // handle exception
}
try {
  int toIntWrong2 = Integer.parseInt(WRONG_NUMBER);
} catch (NumberFormatException e) {
  System.err.println(e);
  // handle exception
}

对于第三方库支持,请考虑 ApacheCommons BeanUtils:IntegerConverterLongConverterFloatConverterDoubleConverter




8 从字符串中删除空格


这个问题的解决方案是使用带有正则表达式的String.replaceAll()方法。主要是\s删除所有的空白,包括不可见的空白,如\t\n\r

public static String removeWhitespaces(String str) {
  return str.replaceAll("\\s", "");
}

从 JDK11 开始,String.isBlank()检查字符串是空的还是只包含空格代码点。对于第三方库支持,请考虑 Apache Commons Lang,StringUtils.deleteWhitespace()和 Spring 框架,StringUtils.trimAllWhitespace()。



9 用分隔符连接多个字符串


有几种解决方案很适合解决这个问题。在 Java8 之前,一种方便的方法依赖于StringBuilder,如下所示:

public static String joinByDelimiter(char delimiter, String...args) {
  StringBuilder result = new StringBuilder();
  int i = 0;
  for (i = 0; i < args.length - 1; i++) {
    result.append(args[i]).append(delimiter);
  }
  result.append(args[i]);
  return result.toString();
}

从 Java8 开始,这个问题至少还有三种解决方案。其中一个解决方案依赖于StringJoiner工具类。此类可用于构造由分隔符(例如逗号)分隔的字符序列。


它还支持可选的前缀和后缀(此处忽略):

public static String joinByDelimiter(char delimiter, String...args) {
  StringJoiner joiner = new StringJoiner(String.valueOf(delimiter));
  for (String arg: args) {
    joiner.add(arg);
  }
  return joiner.toString();
}


另一种解决方案依赖于String.join()方法。此方法是在 Java8 中引入的,有两种风格:

String join​(CharSequence delimiter, CharSequence... elems)
String join​(CharSequence delimiter,
  Iterable<? extends CharSequence> elems)


连接由空格分隔的多个字符串的示例如下:

String result = String.join(" ", "how", "are", "you"); // how are you


更进一步说,Java8 流和Collectors.joining()也很有用:

public static String joinByDelimiter(char delimiter, String...args) {
  return Arrays.stream(args, 0, args.length)
    .collect(Collectors.joining(String.valueOf(delimiter)));
}


注意通过+=运算符以及concat()和String.format()方法连接字符串。这些字符串可以用于连接多个字符串,但它们容易导致性能下降。例如,下面的代码依赖于+=并且比依赖于StringBuilder慢得多:


String str = "";``for(int i = 0; i < 1_000_000; i++) {`` str += "x";


+=被附加到一个字符串并重建一个新的字符串,这需要时间。


对于第三方库支持,请考虑 Apache Commons Lang,StringUtils.join()和 Guava,Joiner。




10 生成所有置换


涉及置换的问题通常也涉及递归性。基本上,递归性被定义为一个过程,其中给定了一些初始状态,并且根据前一个状态定义了每个连续状态


在我们的例子中,状态可以通过给定字符串的字母来具体化。初始状态包含初始字符串,每个连续状态可通过以下公式计算字符串的每个字母将成为字符串的第一个字母(交换位置),然后使用递归调用排列所有剩余字母。虽然存在非递归或其他递归解决方案,但这是该问题的经典解决方案。


将这个解决方案表示为字符串ABC,可以这样做(注意排列是如何完成的):


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10RzDdgx-1657077108922)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/64849bb0-4ed5-4534-abfc-dff0fbe86514.png)]


对该算法进行编码将产生如下结果:


public static void permuteAndPrint(String str) {
  permuteAndPrint("", str);
}
private static void permuteAndPrint(String prefix, String str) {
  int n = str.length();
  if (n == 0) {
    System.out.print(prefix + " ");
  } else {
    for (int i = 0; i < n; i++) {
      permuteAndPrint(prefix + str.charAt(i),
        str.substring(i + 1, n) + str.substring(0, i));
    }
  }
}


最初,前缀应该是一个空字符串""。在每次迭代中,前缀将连接(固定)字符串中的下一个字母。剩下的字母将再次通过该方法传递。


假设这个方法存在于一个名为Strings的实用类中。你可以这样称呼它:

Strings.permuteAndStore("ABC");


这将产生以下输出:

ABC ACB BCA BAC CAB CBA


注意,这个解决方案在屏幕上打印结果。存储结果意味着将Set添加到实现中。最好使用Set,因为它消除了重复:

public static Set<String> permuteAndStore(String str) {
  return permuteAndStore("", str);
}
private static Set<String> 
    permuteAndStore(String prefix, String str) {
  Set<String> permutations = new HashSet<>();
  int n = str.length();
  if (n == 0) {
    permutations.add(prefix);
  } else {
    for (int i = 0; i < n; i++) {
      permutations.addAll(permuteAndStore(prefix + str.charAt(i),
        str.substring(i + 1, n) + str.substring(0, i)));
    }
  }
  return permutations;
}


例如,如果传递的字符串是TEST,那么Set将导致以下输出(这些都是唯一的排列):

ETST SETT TEST TTSE STTE STET TETS TSTE TSET TTES ESTT ETTS


使用List代替Set将产生以下输出(注意重复项):

TEST TETS TSTE TSET TTES TTSE ESTT ESTT ETTS ETST ETST ETTS STTE STET STET STTE SETT SETT TTES TTSE TEST TETS TSTE TSET


有 24 个排列。通过计算n阶乘(n!)。对于n = 4(字符串长度),4! = 1 x 2 x 3 x 4 = 24。当以递归方式表示时,这是n! = n x (n-1)!。


自从n!以极快的速度生成大量数据(例如,10! = 3628800),建议避免存储结果。对于 10 个字符的字符串(如直升机),有 3628800 个排列!


尝试用 Java8 函数式实现此解决方案将导致如下结果:


private static void permuteAndPrintStream(String prefix, String str) {
  int n = str.length();
  if (n == 0) {
    System.out.print(prefix + " ");
  } else {
    IntStream.range(0, n)
      .parallel()
      .forEach(i -> permuteAndPrintStream(prefix + str.charAt(i),
        str.substring(i + 1, n) + str.substring(0, i)));
  }
}


作为奖励,本书附带的代码中提供了返回Stream<String>的解决方案。

相关文章
|
10天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
16天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
9天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
8天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
11天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
17天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
14天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
16天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
39 2
|
17天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
2天前
|
Java API 数据库
Java 反射机制:动态编程的 “魔法钥匙”
Java反射机制是允许程序在运行时访问类、方法和字段信息的强大工具,被誉为动态编程的“魔法钥匙”。通过反射,开发者可以创建更加灵活、可扩展的应用程序。
下一篇
无影云桌面