在Java中按字母顺序对一个列表进行排序

评论 0 浏览 0 2022-06-02

1.绪论

在本教程中,我们将探讨在Java中按字母顺序对列表进行排序的各种方法。

首先,我们将从Collections类开始,然后使用Comparator接口。我们还将使用List的API来按字母顺序排序,然后是,最后使用TreeSet.

此外,我们将扩展我们的例子,以探索几种不同的情况,包括基于特定区域的列表排序,排序重音列表,以及使用RuleBasedCollator 来定义我们的自定义排序规则。

2.使用Collections类进行排序

首先,让我们看看如何使用Collections类来对一个列表进行排序。

Collections类提供了一个静态的、重载的方法sort,它可以接受列表并按自然顺序排序,或者我们可以使用Comparator提供自定义的排序逻辑。

2.1.按自然/词汇顺序排序

首先,我们要定义输入列表。

private static List<String> INPUT_NAMES = Arrays.asList("john", "mike", "usmon", "ken", "harry");

现在我们先用Collections类来对列表进行自然排序,也就是所谓的lexicographic order

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSorted() {
    Collections.sort(INPUT_NAMES);
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_NATURAL_ORDER);
}

其中,EXPECTED_NATURAL_ORDER是。

private static List<String> EXPECTED_NATURAL_ORDER = Arrays.asList("harry", "john", "ken", "mike", "usmon");

这里需要注意的一些要点是:

  • 列表按照其元素的自然排序,以升序进行排序
  • 我们可以将任何Collection传递给sort方法,其元素是Comparable(实现Comparable接口)。
  • 在这里,我们传递了一个ListString类,它是一个可比较的类,因此,排序工作了。
  • Collection类改变了被传递给sortList对象的状态。因此,我们不需要返回列表

2.2.倒序排序

让我们来看看如何对同一列表按字母顺序进行反向排序。

让我们再次使用sort方法,但现在提供了一个Comparator

Comparator<String> reverseComparator = (first, second) -> second.compareTo(first);

另外,我们也可以简单地使用Comparator接口中的这个静态方法。

Comparator<String> reverseComparator = Comparator.reverseOrder();

一旦我们有了反向比较器,我们就可以简单地把它传递给sort

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSortedInReverse() {
    Comparator<String> reverseComparator = Comparator.reverseOrder();
    Collections.sort(INPUT_NAMES, reverseComparator); 
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER); 
}

其中EXPECTED_REVERSE_ORDER为:。

private static List<String> EXPECTED_REVERSE_ORDER = Arrays.asList("usmon", "mike", "ken", "john", "harry");

从中得到的一些相关启示是:

  • 因为String类是最终的,所以我们不能扩展和覆盖Comparable接口的compareTo方法来进行反向排序。
  • 我们可以使用Comparator接口来实现一个自定义的排序策略,即按照降序字母顺序进行排序。
  • 由于Comparator是一个函数式接口,我们可以使用一个lambda表达式

3.使用Comparator接口进行自定义排序

通常情况下,我们必须对一个需要一些自定义排序逻辑的字符串列表进行排序。这时我们要实现Comparator接口,并提供我们想要的排序标准。

3.1.Comparator 用大写和小写的字符串对列表进行排序

一个可以要求自定义排序的典型场景是一个混合的字符串列表,以大写字母和小写字母开始。

让我们来设置和测试这个场景。

@Test
void givenListOfStringsWithUpperAndLowerCaseMixed_whenCustomComparator_thenListIsSortedCorrectly() {
    List<String> movieNames = Arrays.asList("amazing SpiderMan", "Godzilla", "Sing", "Minions");
    List<String> naturalSortOrder = Arrays.asList("Godzilla", "Minions", "Sing", "amazing SpiderMan");
    List<String> comparatorSortOrder = Arrays.asList("amazing SpiderMan", "Godzilla", "Minions", "Sing");

    Collections.sort(movieNames);
    assertThat(movieNames).isEqualTo(naturalSortOrder);

    Collections.sort(movieNames, Comparator.comparing(s -> s.toLowerCase()));
    assertThat(movieNames).isEqualTo(comparatorSortOrder);
}

这里要注意的是。

  • Comparator之前的排序结果将是不正确的。[“Godzilla, “Minions” “Sing” , “amazing SpiderMan”]
  • String::toLowerCase是一个键提取器,它从一个String类型中提取一个Comparable排序键,并返回一个Comparator<String>
  • 用自定义的比较器进行排序后,正确的结果将是。[“amazing SpiderMan”, “Godzilla”, “Minions”, “Sing”]

3.2.比较器对特殊字符进行排序

让我们考虑另一个例子,即有一些名字以特殊字符‘@'开头的列表。

我们希望它们被排序在列表的末尾,其余的应该按自然顺序排序。

@Test
void givenListOfStringsIncludingSomeWithSpecialCharacter_whenCustomComparator_thenListIsSortedWithSpecialCharacterLast() {
    List<String> listWithSpecialCharacters = Arrays.asList("@alaska", "blah", "jo", "@ask", "foo");

    List<String> sortedNaturalOrder = Arrays.asList("@alaska", "@ask", "blah", "foo", "jo");
    List<String> sortedSpecialCharacterLast = Arrays.asList("blah", "foo", "jo", "@alaska", "@ask");

    Collections.sort(listWithSpecialCharacters);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedNaturalOrder);

    Comparator<String> specialSignComparator = Comparator.<String, Boolean>comparing(s -> s.startsWith("@"));
    Comparator<String> specialCharacterComparator = specialSignComparator.thenComparing(Comparator.naturalOrder());

    listWithSpecialCharacters.sort(specialCharacterComparator);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedSpecialCharacterLast);
}

最后,一些关键点是:

  • 没有Comparator的排序输出是。[“@alaska”, “@ask”, “blah”, “foo”, “jo”]这对我们的情况是不正确的
  • 像以前一样,我们有一个键提取器,从一个Comparable类型的String中提取出一个Comparable排序键,并返回一个specialCharacterLastComparator
  • 我们可以通过链式 specialCharacterLastComparator做二次排序,使用thenComparing期待另一个Comparator
  • Comparator.naturalOrder按自然顺序比较可比较的对象。
  • 在排序中的Comparator后的最终输出是:[“blah”, “foo”, “jo”, “@alaska”, “@ask”]

4.使用进行排序

现在,让我们用Java 8 Streams来对String的列表进行排序。

4.1.按自然顺序排序

让我们先按自然顺序排序。

@Test
void givenListOfStrings_whenSortWithStreams_thenListIsSortedInNaturalOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted()
      .collect(Collectors.toList());

    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

重要的是,这里的sorted方法会返回一个按照自然顺序排序的String流。

另外,这个流的元素是可比较的

4.2.倒序排序

接下来,让我们传递一个Comparatorsorted,它定义了反向排序的策略。

@Test
void givenListOfStrings_whenSortWithStreamsUsingComparator_thenListIsSortedInReverseOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted((element1, element2) -> element2.compareTo(element1))
      .collect(Collectors.toList());
    assertThat(sortedList).isEqualTo(EXPECTED_REVERSE_ORDER);
}

注意,这里我们使用Lamda函数来定义比较器。

另外,我们也可以得到同样的比较器

Comparator<String> reverseOrderComparator = Comparator.reverseOrder();

然后我们将简单地把这个reverseOrderComparator 传递给sorted

List<String> sortedList = INPUT_NAMES.stream()
  .sorted(reverseOrder)
  .collect(Collectors.toList());

5.使用TreeSet进行排序

TreeSet使用Comparable接口,以排序和升序的方式存储对象。

我们可以简单地将我们的未排序列表转换为TreeSet,然后将其作为List:收集回来。

@Test
void givenNames_whenUsingTreeSet_thenListIsSorted() {
    SortedSet<String> sortedSet = new TreeSet<>(INPUT_NAMES);
    List<String> sortedList = new ArrayList<>(sortedSet);
    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

这种方法的一个约束条件是,我们的原始列表不应该有任何重复的值

6.使用sortList进行排序

我们也可以使用Listsort方法对一个列表进行排序。

@Test
void givenListOfStrings_whenSortOnList_thenListIsSorted() {
    INPUT_NAMES.sort(Comparator.reverseOrder());
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}

请注意,sort方法是根据指定的比较器所决定的顺序对这个列表进行排序。

7.对本地语言敏感的列表排序

由于语言规则的不同,按字母顺序排序的结果可能会有差异

让我们举一个StringList的例子吧。

 List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

让我们先把它们按正常情况分类。

 Collections.sort(accentedStrings);

输出结果将是。[“cosas”, “fútbol”, “árbol”, “único”].

然而,我们希望它们能够使用特定的语言规则进行排序。

让我们创建一个具有特定localeCollator的实例。

Collator esCollator = Collator.getInstance(new Locale("es"));

注意,在这里我们使用es区域设置创建了一个Collator的实例。

然后,我们可以将这个Collator作为一个Comparator被用于排序的listCollection,或者使用Streams:

accentedStrings.sort((s1, s2) -> {
    return esCollator.compare(s1, s2);
});

现在的排序结果将是。[árbol, cosas, fútbol, único].

最后,让我们把这一切都展示出来。

@Test
void givenListOfStringsWithAccent_whenSortWithTheCollator_thenListIsSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");
    List<String> sortedNaturalOrder = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> sortedLocaleSensitive = Arrays.asList("árbol", "cosas", "fútbol", "único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(sortedNaturalOrder);

    Collator esCollator = Collator.getInstance(new Locale("es"));

    accentedStrings.sort((s1, s2) -> {
        return esCollator.compare(s1, s2);
    });

    assertThat(accentedStrings).isEqualTo(sortedLocaleSensitive);
}

8.用重音字对列表进行排序

带有重音或其他装饰的字符在Unicode中可以用几种不同的方式进行编码,因此排序也不同。

我们可以在排序前将重音字符正常化,也可以将重音字符移除。

让我们来看看这两种重音列表的排序方式。

8.1.归一化重音列表和排序

为了准确地对这样的字符串列表进行排序,让我们使用java.text.Normalizer对重音字符进行归一化处理。

让我们从重音字符串的列表开始吧。

List<String> accentedStrings = Arrays.asList("único","árbol", "cosas", "fútbol");

接下来,让我们定义一个ComparatorNormalize函数,并将其传递给我们的sort方法。

Collections.sort(accentedStrings, (o1, o2) -> {
    o1 = Normalizer.normalize(o1, Normalizer.Form.NFD);
    o2 = Normalizer.normalize(o2, Normalizer.Form.NFD);
    return o1.compareTo(o2);
});

归一化后的排序列表将是。[árbol, cosas, fútbol, único]

重要的是,这里我们使用Normalizer.Form.NFD的形式对数据进行规范化处理,对重音字符使用Canonical分解。还有一些其他形式也可以用于规范化。

8.2.剥离重音和排序

让我们在Comparator中使用StringUtils stripAccents 方法,并将其传递给sort方法。

@Test
void givenListOfStrinsWithAccent_whenComparatorWithNormalizer_thenListIsNormalizedAndSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

    List<String> naturalOrderSorted = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> stripAccentSorted = Arrays.asList("árbol","cosas", "fútbol","único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(naturalOrderSorted);
    Collections.sort(accentedStrings, Comparator.comparing(input -> StringUtils.stripAccents(input)));
    assertThat(accentedStrings).isEqualTostripAccentSorted); 
}

9.使用基于规则的整理器进行排序

我们还可以使用java text.RuleBasedCollator.来定义我们的自定义规则,按字母顺序进行排序。

在这里,让我们定义我们的自定义排序规则,并将其传递给RuleBasedCollator

@Test
void givenListofStrings_whenUsingRuleBasedCollator_then ListIsSortedUsingRuleBasedCollator() throws ParseException {
    List<String> movieNames = Arrays.asList(
      "Godzilla","AmazingSpiderMan","Smurfs", "Minions");

    List<String> naturalOrderExpected = Arrays.asList(
      "AmazingSpiderMan", "Godzilla", "Minions", "Smurfs");
    Collections.sort(movieNames);

    List<String> rulesBasedExpected = Arrays.asList(
      "Smurfs", "Minions", "AmazingSpiderMan", "Godzilla");

    assertThat(movieNames).isEqualTo(naturalOrderExpected);

    String rule = "< s, S < m, M < a, A < g, G";

    RuleBasedCollator rulesCollator = new RuleBasedCollator(rule);
    movieNames.sort(rulesCollator);

    assertThat(movieNames).isEqualTo(rulesBasedExpected);
}

需要考虑的一些重要问题是:。

  • RuleBasedCollator将字符映射到排序键上。
  • 上面定义的规则是<关系>的形式,但在规则中也有其他的形式
  • 字符s小于m,M小于a,其中A小于g,根据规则的定义
  • 如果规则的构建过程失败,将抛出一个格式异常。
  • RuleBasedCollator实现了Comparator接口,因此可以被传递给sort

10.结语

在这篇文章中,我们研究了java中按字母顺序排列列表的各种技术。

首先,我们使用了Collections,然后我们用一些常见的例子解释了Comparator接口。

Comparator之后,我们研究了sortList方法,然后是TreeSet

我们还探讨了如何处理不同地区的String列表的排序,规范化和重音列表的排序,以及使用RuleBasedCollator与自定义规则的使用。

像往常一样,本文的完整源代码可以在GitHub上找到over on GitHub

最后更新2023-04-03
0 个评论
标签