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

时间:2018-10-01 23:07:19

标签: java comparator

  

这是我发布的here上一个问题的跟进。

在下面的MCVE中,我有一个TableView,显示Person对象的列表。在列表上方,我只有一个TextField,可用来过滤TableView中列出的项目。

Person类包含4个字段,但是我的搜索字段仅检查其中3个匹配项:userIdlastNameemailAddress

过滤功能可以正常工作。

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

  

MCVE代码

Person.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;
    }
}

Main.java

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. userIdkeyword开头
  2. lastNamekeyword开头
  3. emailAddresskeyword开头
  4. lastName包含一个keyword
  5. emailAddress包含一个keyword
  6. 在匹配范围内,任何userType = "VET"应该首先列出

我不是在寻找Google级别的算法,而是通过某种方式确定匹配的优先级。我对Comparator类不是很熟悉,并且很难理解JavaDocs,因为它适用于我的需求。


StackOverflow上有几篇文章涉及按多个字段进行排序,但是我发现的所有文章都是将PersonPerson进行比较。在这里,我需要将Person字段与txtSearch.getText()值进行比较。

我该如何重构这个Comparator来设置这种性质的自定义排序?

2 个答案:

答案 0 :(得分:2)

您的评分概念很接近,您只需要拿出因素并遵守规则即可。

所以,这是一个简单的示例:

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)。最后,我为“ VET”类型加了5。

假设得分规则不是排他性的(即每个规则都会完善得分而不是停止得分),并且VET类型是每个条件内的决胜局,而不是排在榜首。如果VET需要排在列表的顶部(即所有VET都将显示在所有非VET之前),则可以将5更改为10000,使其具有自己的数量级。

现在,使用十进制数很容易,但是9以后会用尽数量级(您将使int溢出)-您还可以使用其他基数(在此示例中为3)整数中的更多“位”。您可以使用long,也可以使用BigDecimal值,并根据需要选择任意多个条件。

但是基本知识是相同的。

获得分数后,只需在比较器中比较两个值的分数即可。

答案 1 :(得分: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);
};

按c1然后c2然后c3的顺序查询比较器。

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

相关问题