代码之家  ›  专栏  ›  技术社区  ›  Zephyr

如何创建自定义比较器来对搜索结果进行排名?

  •  3
  • Zephyr  · 技术社区  · 6 年前

    这是我之前发布的问题的后续内容 here .

    TableView 显示列表 Person 物体。在名单上方,我有一个 TextField 我用它来过滤 表视图 .

    这个 userId , lastName emailAddress .

    但是,我现在需要根据匹配的字段对结果进行排序 用户 Type .

    个人.java :

    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    public final class Person {
    
        private StringProperty userType = new SimpleStringProperty();
        private IntegerProperty userId = new SimpleIntegerProperty();
        private StringProperty firstName = new SimpleStringProperty();
        private StringProperty lastName = new SimpleStringProperty();
        private StringProperty emailAddress = new SimpleStringProperty();
    
        public Person(String type, int id, String firstName, String lastName, String emailAddress) {
            this.userType.set(type);
            this.userId.set(id);
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.emailAddress.set(emailAddress);
        }
    
        public String getUserType() {
            return userType.get();
        }
    
        public void setUserType(String userType) {
            this.userType.set(userType);
        }
    
        public StringProperty userTypeProperty() {
            return userType;
        }
    
        public int getUserId() {
            return userId.get();
        }
    
        public void setUserId(int userId) {
            this.userId.set(userId);
        }
    
        public IntegerProperty userIdProperty() {
            return userId;
        }
    
        public String getFirstName() {
            return firstName.get();
        }
    
        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }
    
        public StringProperty firstNameProperty() {
            return firstName;
        }
    
        public String getLastName() {
            return lastName.get();
        }
    
        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }
    
        public StringProperty lastNameProperty() {
            return lastName;
        }
    
        public String getEmailAddress() {
            return emailAddress.get();
        }
    
        public void setEmailAddress(String emailAddress) {
            this.emailAddress.set(emailAddress);
        }
    
        public StringProperty emailAddressProperty() {
            return emailAddress;
        }
    }
    

    主类 :

    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.collections.transformation.FilteredList;
    import javafx.collections.transformation.SortedList;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.control.TextField;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    import java.util.Comparator;
    
    public class Main extends Application {
    
        TableView<Person> tableView;
        private TextField txtSearch;
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            // Simple Interface
            VBox root = new VBox(10);
            root.setAlignment(Pos.CENTER);
            root.setPadding(new Insets(10));
    
            // Create the TableView of data
            tableView = new TableView<>();
            TableColumn<Person, Integer> colId = new TableColumn<>("ID");
            TableColumn<Person, String> colFirstName = new TableColumn<>("First Name");
            TableColumn<Person, String> colLastName = new TableColumn<>("Last Name");
            TableColumn<Person, String> colEmailAddress = new TableColumn<>("Email Address");
    
            // Set the ValueFactories
            colId.setCellValueFactory(new PropertyValueFactory<>("userId"));
            colFirstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
            colLastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));
            colEmailAddress.setCellValueFactory(new PropertyValueFactory<>("emailAddress"));
    
            // Add columns to the TableView
            tableView.getColumns().addAll(colId, colFirstName, colLastName, colEmailAddress);
    
            // Create the filter/search TextField
            txtSearch = new TextField();
            txtSearch.setPromptText("Search ...");
    
            addSearchFilter(getPersons());
    
            // Add the controls to the layout
            root.getChildren().addAll(txtSearch, tableView);
    
            // Show the stage
            primaryStage.setScene(new Scene(root));
            primaryStage.setTitle("Sample");
            primaryStage.show();
        }
    
        private void addSearchFilter(ObservableList<Person> list) {
    
            FilteredList<Person> filteredList = new FilteredList<Person>(list);
    
            txtSearch.textProperty().addListener(((observable, oldValue, newValue) ->
                    filteredList.setPredicate(person -> {
    
                        // Clear any currently-selected item from the TableView
                        tableView.getSelectionModel().clearSelection();
    
                        // If search field is empty, show everything
                        if (newValue == null || newValue.trim().isEmpty()) {
                            return true;
                        }
    
                        // Grab the trimmed search string
                        String query = newValue.trim().toLowerCase();
    
                        // Convert the query to an array of individual search terms
                        String[] keywords = query.split("[\\s]+");
    
                        // Create a single string containing all the data we will match against
                        // BONUS QUESTION: Is there a better way to do this?
                        String matchString =
                                String.valueOf(person.getUserId())
                                        + person.getLastName().toLowerCase()
                                        + person.getEmailAddress().toLowerCase();
    
                        // Check if ALL the keywords exist in the matchString; if any are absent, return false;
                        for (String keyword : keywords) {
                            if (!matchString.contains(keyword)) return false;
                        }
    
                        // All entered keywords exist in this Person's searchable fields
                        return true;
    
                    })));
    
            SortedList<Person> sortedList = new SortedList<>(filteredList);
    
            // Create the Comparator to allow ranking of search results
            Comparator<Person> comparator = new Comparator<Person>() {
                @Override
                public int compare(Person person, Person t1) {
                    return 0;
    
                }
            };
    
            // Set the comparator and bind list to the TableView
            sortedList.setComparator(comparator);
            tableView.setItems(sortedList);
    
        }
    
        private ObservableList<Person> getPersons() {
    
            ObservableList<Person> personList = FXCollections.observableArrayList();
    
            personList.add(new Person("DECEASED", 123, "Chrissie", "Watkins", "fishfood@email.com"));
            personList.add(new Person("VET", 342, "Matt", "Hooper", "m.hooper@noaa.gov"));
            personList.add(new Person("VET", 526, "Martin", "Brody", "chiefofpolice@amity.gov"));
            personList.add(new Person("NEW", 817, "Larry", "Vaughn", "lvaughn@amity.gov"));
    
            return personList;
        }
    }
    

    你会看到我有一个空的 Comparator 在我的 Main previous question ):

        Comparator<DataItem> byName = new Comparator<DataItem>() {
            @Override
            public int compare(DataItem o1, DataItem o2) {
                String searchKey = txtSearch.getText().toLowerCase();
                int item1Score = findScore(o1.getName().toLowerCase(), searchKey);
                int item2Score = findScore(o2.getName().toLowerCase(), searchKey);
    
                if (item1Score > item2Score) {
                    return -1;
                }
    
                if (item2Score > item1Score) {
                    return 1;
                }
    
                return 0;
            }
    
            private int findScore(String item1Name, String searchKey) {
                int sum = 0;
                if (item1Name.startsWith(searchKey)) {
                    sum += 2;
                }
    
                if (item1Name.contains(searchKey)) {
                    sum += 1;
                }
                return sum;
            }
        };
    

    不过,我不知道如何将其应用于多个领域。具体来说,我希望能够选择哪些领域应该排名“更高”

    对于本例,我要完成的是按以下顺序对列表进行排序:

    1. 用户ID 从一个 keyword
    2. 姓氏 从一个
    3. 电子邮件地址 从一个 关键字
    4. 姓氏 关键字
    5. 电子邮件地址 包含 关键字
    6. 在匹配的任何 userType = "VET" 应该先列出来


    StackOverflow上有几个帖子处理按多个字段排序的问题,但我发现的所有帖子都在比较 txtSearch.getText() 价值观。

    比较器 设置这种性质的自定义排序?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Will Hartung    6 年前

    public int score(Item item, String query) {
        int score = 0;
    
        if (item.userId().startsWith(query) {
            score += 2000;
        }
        if (item.lastName().startsWith(query) {
            score += 200;
        } else if (item.lastName().contains(query) {
            score += 100;
        }
        if (item.email().startsWith(query) {
            score += 20;
        } else if (item.email().contains(query) {
            score += 10;
        }
        if (item.userType().equals("VET")) {
            score += 5;
        }
    
        return score;
    }
    

    如你所见,我取了你的每一个标准,并将它们转换成分数中的不同数字,对于每个标准中的区别,我有不同的值(例如10比20)。最后我为“兽医”类型加了5分。

    假设评分规则不是排他性的(即,每个规则都细化评分,而不是停止评分),并且VET类型在每个标准中都是平局破坏者,而不是排在列表顶部。如果兽医需要排在列表的最前面(也就是说,所有的兽医都会排在所有的非兽医之前),你可以把5改成10000,给它自己的数量级。

    现在,使用十进制数很简单,但是9之后的数量级就用完了(int会溢出)--您还可以使用其他基(本例中的基3),这样就可以访问整数中的更多“位”。您可以使用long,也可以使用BigDecimal值并具有任意多个条件。

    但基本原理是一样的。

    一旦你得到了分数,就在比较器中比较两个值的分数。

        2
  •  0
  •   DudeDoesThings    6 年前

    通过将比较器链接在一起,可以对多个字段进行排序。如果第一个比较器声明两个对象相等,那么您将委托给下一个比较器,并继续这样操作,直到查询完所有比较器或其中任何一个比较器返回的值不是0为止。

    举个例子:

    static class Person {
        String name;
        int age;
        int id;
    }
    
    Comparator<Person> c3 = (p1, p2) -> {
        return Integer.compare(p1.id, p2.id);
    };
    
    Comparator<Person> c2 = (p1, p2) -> {
        if (p1.name.compareTo(p2.name) == 0) {
            return c3.compare(p1, p2);
        }
        return p1.name.compareTo(p2.name);
    };
    
    Comparator<Person> c1 = (p1, p2) -> {
        if (Integer.compare(p1.age, p2.age) == 0) {
            return c2.compare(p1, p2);
        }
        return Integer.compare(p1.age, p2.age);
    };
    

    当然,这是一个过于简化的例子。在生产代码中,最好使用更干净、更面向OOP的解决方案。