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 计算某个字符的出现次数
此问题的简单解决方案包括以下两个步骤:
- 将给定字符串中出现的每个字符替换为
""
(基本上,这类似于删除给定字符串中出现的所有字符)。 - 从初始字符串的长度中减去在第一步中获得的字符串的长度。
此方法的代码如下:
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 将字符串转换为int
、long
、float
或double
让我们考虑以下字符串(也可以使用负数):
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
转换成Integer
、Long
、Float
或Double
对象,可以通过以下 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:IntegerConverter
、LongConverter
、FloatConverter
和DoubleConverter
。
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>
的解决方案。