Dart 中的 Comparator
最后修改于 2025 年 5 月 25 日
本教程将探讨 Dart 的 Comparator<T> 类型,它支持对集合进行自定义排序。您将学习如何为内置类型和自定义对象创建可重用的比较函数,并通过实际示例演示各种排序场景。
Comparator 概述
Comparator<T> 是一种函数类型,它定义了如何对类型为 T 的两个对象进行排序。它遵循许多编程语言中使用的标准比较约定,返回一个整数,指示两个项目的相对顺序。
比较器函数的签名是
typedef Comparator<T> = int Function(T a, T b);
它必须返回
| 返回值 | 含义 |
|---|---|
| 负整数 | a 排在 b 前面 |
| 零 | a 和 b 相等 |
| 正整数 | a 排在 b 后面 |
Dart 的 Comparable 接口提供了一个 compareTo 方法,该方法遵循相同的约定。许多内置类型都实现了 Comparable,使其实例自然可排序。
基本 Comparator 用法
本示例演示了使用内联和外部比较函数的Sprites的基本 Comparator 用法。我们将使用不同的方法对数字和字符串进行排序。
// 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。
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)
多字段排序
对于更复杂的排序需求,您可以比较多个字段。本示例展示了如何创建按主键和次键排序的比较器。
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 工具。本示例演示了 compareNatural、compareAsciiUpperCase 以及组合 Comparator。
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 工厂,根据参数生成比较函数。本示例演示了一个为任何可比较属性创建比较器的工厂。
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排序场景都适用。
作者
列出 所有 Dart 教程。