ZetCode

Dart 中的 Comparator

最后修改于 2025 年 5 月 25 日

本教程将探讨 Dart 的 Comparator<T> 类型,它支持对集合进行自定义排序。您将学习如何为内置类型和自定义对象创建可重用的比较函数,并通过实际示例演示各种排序场景。

Comparator 概述

Comparator<T> 是一种函数类型,它定义了如何对类型为 T 的两个对象进行排序。它遵循许多编程语言中使用的标准比较约定,返回一个整数,指示两个项目的相对顺序。

比较器函数的签名是

typedef Comparator<T> = int Function(T a, T b);

它必须返回

返回值 含义
负整数 a 排在 b 前面
ab 相等
正整数 a 排在 b 后面

Dart 的 Comparable 接口提供了一个 compareTo 方法,该方法遵循相同的约定。许多内置类型都实现了 Comparable,使其实例自然可排序。

基本 Comparator 用法

本示例演示了使用内联和外部比较函数的Sprites的基本 Comparator 用法。我们将使用不同的方法对数字和字符串进行排序。

basic_comparator.dart
// External comparator function
int compareNumbers(int a, int b) {
  return a.compareTo(b);
}

void main() {
  // Sorting numbers with external comparator
  List<int> numbers = [5, 2, 9, 1, 7];
  numbers.sort(compareNumbers);
  print('Sorted numbers: $numbers');

  // Sorting strings with inline comparator
  List<String> words = ['apple', 'banana', 'cherry'];
  words.sort((a, b) => a.compareTo(b));
  print('Sorted words: $words');

  // Reverse sorting with external comparator
  int reverseStringComparator(String a, String b) => b.compareTo(a);
  words.sort(reverseStringComparator);
  print('Reverse sorted words: $words');
}

该示例显示了三种排序场景:使用外部比较器对数字进行排序,使用内联比较器对字符串进行排序,以及使用另一个外部比较器对字符串进行反向排序。每种都演示了标准的比较模式。

compareNumbers 这样的外部比较器可以在您的代码库中重用,而内联比较器则适用于一次性排序需求。compareTo 方法为许多类型提供了自然排序。

$ dart run basic_comparator.dart
Sorted numbers: [1, 2, 5, 7, 9]
Sorted words: [apple, banana, cherry]
Reverse sorted words: [cherry, banana, apple]

排序自定义对象

本示例展示了如何使用 Comparator 对自定义对象进行排序。我们将定义一个 Person 类并创建用于不同属性的 Comparator。

object_comparator.dart
class Person {
  final String name;
  final int age;
  final double height;

  Person(this.name, this.age, this.height);

  @override
  String toString() => '$name ($age years, ${height}m)';
}

// External comparators
int compareByAge(Person a, Person b) => a.age.compareTo(b.age);
int compareByName(Person a, Person b) => a.name.compareTo(b.name);
int compareByHeight(Person a, Person b) => a.height.compareTo(b.height);

void main() {
  List<Person> people = [
    Person('Alice', 30, 1.65),
    Person('Bob', 25, 1.80),
    Person('Charlie', 35, 1.75)
  ];

  // Sort by age
  people.sort(compareByAge);
  print('By age:');
  people.forEach(print);

  // Sort by name
  people.sort(compareByName);
  print('\nBy name:');
  people.forEach(print);

  // Sort by height (inline comparator)
  people.sort((a, b) => compareByHeight(a, b));
  print('\nBy height:');
  people.forEach(print);
}

我们为 Person 类定义了三个外部比较器,每个比较器按不同的属性排序。该示例演示了如何通过传递不同的比较函数轻松更改排序标准。

toString 的重写提供了可读的输出,我们使用 forEach(print) 来显示排序列表。请注意,我们也可以内联使用外部 compareByHeight 函数。

$ dart run object_comparator.dart
By age:
Bob (25 years, 1.8m)
Alice (30 years, 1.65m)
Charlie (35 years, 1.75m)

By name:
Alice (30 years, 1.65m)
Bob (25 years, 1.8m)
Charlie (35 years, 1.75m)

By height:
Alice (30 years, 1.65m)
Charlie (35 years, 1.75m)
Bob (25 years, 1.8m)

多字段排序

对于更复杂的排序需求,您可以比较多个字段。本示例展示了如何创建按主键和次键排序的比较器。

multi_field_comparator.dart
class Product {
  final String category;
  final String name;
  final double price;

  Product(this.category, this.name, this.price);

  @override
  String toString() => '$category / $name: \$$price';
}

// Compare by category, then by price
int compareProducts(Product a, Product b) {
  var categoryCompare = a.category.compareTo(b.category);
  if (categoryCompare != 0) return categoryCompare;
  return a.price.compareTo(b.price);
}

// Compare by price descending, then by name
int compareByPriceDescThenName(Product a, Product b) {
  var priceCompare = b.price.compareTo(a.price); // Descending
  if (priceCompare != 0) return priceCompare;
  return a.name.compareTo(b.name);
}

void main() {
  List<Product> products = [
    Product('Electronics', 'Laptop', 999.99),
    Product('Food', 'Apple', 0.99),
    Product('Electronics', 'Phone', 699.99),
    Product('Food', 'Banana', 0.59),
    Product('Electronics', 'Tablet', 499.99),
  ];

  // Sort by category then price
  products.sort(compareProducts);
  print('By category then price:');
  products.forEach(print);

  // Sort by price (descending) then name
  products.sort(compareByPriceDescThenName);
  print('\nBy price (desc) then name:');
  products.forEach(print);
}

该示例演示了两种多字段排序策略。第一个按类别(主键)然后按价格(次键)对产品进行排序。第二个首先按降序价格排序,然后按相同价格的商品名称排序。

每个比较器首先检查主字段 - 如果这些值不同,它将立即返回该比较结果。只有当主字段相同时,它才会比较次字段。此模式可以扩展到任意数量的字段。

$ dart run multi_field_comparator.dart
By category then price:
Electronics / Tablet: $499.99
Electronics / Phone: $699.99
Electronics / Laptop: $999.99
Food / Banana: $0.59
Food / Apple: $0.99

By price (desc) then name:
Electronics / Laptop: $999.99
Electronics / Phone: $699.99
Electronics / Tablet: $499.99
Food / Apple: $0.99
Food / Banana: $0.59

Comparator 工具

Dart 在 package:collection 库中提供了有用的 Comparator 工具。本示例演示了 compareNaturalcompareAsciiUpperCase 以及组合 Comparator。

comparator_utilities.dart
import 'package:collection/collection.dart';

class Student {
  final String name;
  final int grade;
  final DateTime birthDate;

  Student(this.name, this.grade, this.birthDate);

  @override
  String toString() => '$name (Grade $grade, DOB: ${birthDate.year})';
}

void main() {
  // Natural string comparison (case sensitive)
  List<String> names = ['alice', 'Bob', 'Charlie', 'dave'];
  names.sort(compareNatural);
  print('Natural sort: $names');

  // Case-insensitive comparison
  names.sort(compareAsciiUpperCase);
  print('Case-insensitive sort: $names');

  // Composing comparators
  List<Student> students = [
    Student('Alice', 10, DateTime(2007, 5, 15)),
    Student('Bob', 10, DateTime(2007, 3, 20)),
    Student('Charlie', 11, DateTime(2006, 8, 10)),
    Student('Alice', 9, DateTime(2008, 2, 5)),
  ];

  // Sort by grade, then name, then birthdate
  var studentComparator = Comparator<Student>((a, b) {
    var gradeCompare = a.grade.compareTo(b.grade);
    if (gradeCompare != 0) return gradeCompare;
    
    var nameCompare = compareNatural(a.name, b.name);
    if (nameCompare != 0) return nameCompare;
    
    return a.birthDate.compareTo(b.birthDate);
  });

  students.sort(studentComparator);
  print('\nSorted students:');
  students.forEach(print);
}

package:collection 库提供了几个内置的 Comparator。compareNatural 执行区分大小写的字符串比较,而 compareAsciiUpperCase 通过在比较之前将字符串转换为大写来提供不区分大小写的排序。

对于 Student 类,我们组合了一个自定义比较器,该比较器首先按年级排序,然后按姓名,然后按出生日期排序。这展示了如何在保持代码可读性的同时构建复杂的排序逻辑。

$ dart run comparator_utilities.dart
Natural sort: [Bob, Charlie, alice, dave]
Case-insensitive sort: [alice, Bob, Charlie, dave]

Sorted students:
Alice (Grade 9, DOB: 2008)
Bob (Grade 10, DOB: 2007)
Alice (Grade 10, DOB: 2007)
Charlie (Grade 11, DOB: 2006)

Comparator 工厂

为了获得最大的灵活性,您可以创建 Comparator 工厂,根据参数生成比较函数。本示例演示了一个为任何可比较属性创建比较器的工厂。

comparator_factories.dart
class Book {
  final String title;
  final String author;
  final int year;
  final double rating;

  Book(this.title, this.author, this.year, this.rating);

  @override
  String toString() => '$title by $author ($year) ★$rating';
}

// Comparator factory for any Comparable property
Comparator<T> compareBy<T, V extends Comparable<V>>(V Function(T) selector) {
  return (a, b) => selector(a).compareTo(selector(b));
}

// Comparator factory for descending order
Comparator<T> compareByDescending<T, V extends Comparable<V>>(V Function(T) selector) {
  return (a, b) => selector(b).compareTo(selector(a));
}

void main() {
  List<Book> books = [
    Book('Dart in Action', 'Manning', 2023, 4.5),
    Book('Flutter Basics', 'OReilly', 2022, 4.2),
    Book('Dart in Action', 'Manning', 2021, 4.7),
    Book('Advanced Flutter', 'Apress', 2023, 4.8),
  ];

  // Sort by title
  books.sort(compareBy<Book, String>((b) => b.title));
  print('By title:');
  books.forEach(print);

  // Sort by year descending then rating descending
  books.sort((a, b) {
    var yearCompare = compareByDescending<Book, num>((b) => b.year)(a, b);
    if (yearCompare != 0) return yearCompare;
    return compareByDescending<Book, num>((b) => b.rating)(a, b);
  });
  print('\nBy year (desc) then rating (desc):');
  books.forEach(print);

  // Sort by author then title
  books.sort(compareBy<Book, String>((b) => b.author)
      .thenCompare(compareBy<Book, String>((b) => b.title)));
  print('\nBy author then title:');
  books.forEach(print);
}

extension ComparatorExtensions<T> on Comparator<T> {
  Comparator<T> thenCompare(Comparator<T> other) {
    return (a, b) {
      var result = this(a, b);
      if (result != 0) return result;
      return other(a, b);
    };
  }
}

compareBy 工厂创建的比较器可以从对象中提取可比较的属性。thenCompare 扩展方法将比较器链接在一起,类似于 Java 中的 thenComparing。

这种方法提供了最大的灵活性 - 您可以轻松地为任何属性创建比较器,而无需编写重复的比较逻辑。该示例展示了如何用最少的代码按不同属性组合对书籍进行排序。

$ dart run comparator_factories.dart
By title:
Advanced Flutter by Apress (2023) ★4.8
Dart in Action by Manning (2023) ★4.5
Dart in Action by Manning (2021) ★4.7
Flutter Basics by OReilly (2022) ★4.2

By year (desc) then rating (desc):
Advanced Flutter by Apress (2023) ★4.8
Dart in Action by Manning (2023) ★4.5
Flutter Basics by OReilly (2022) ★4.2
Dart in Action by Manning (2021) ★4.7

By author then title:
Advanced Flutter by Apress (2023) ★4.8
Dart in Action by Manning (2023) ★4.5
Dart in Action by Manning (2021) ★4.7
Flutter Basics by OReilly (2022) ★4.2

来源

Dart Comparator API
Dart Comparable API
Dart Collection 包

Dart 的 Comparator<T> 提供了强大而灵活的排序功能。通过创建可重用的比较函数并利用 Dart 的函数式特性,您可以实现复杂的排序逻辑,同时保持代码的整洁和可维护性。所示技术从简单的属性比较到复杂的 Faça排序场景都适用。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出 所有 Dart 教程