Как создать собственный компаратор для ранжирования результатов поиска? - PullRequest
0 голосов
/ 02 октября 2018

Это продолжение предыдущего вопроса, который я разместил здесь .

В MCVE ниже у меня есть TableView, отображающий список Person объектов.Над списком у меня есть один TextField, который я использую для фильтрации перечисленных элементов в TableView.

. Класс Person содержит 4 поля, но мое поле поиска только проверяет совпаденияв 3 из них: userId, lastName и emailAddress.

Функция фильтрации работает, как и ожидалось.

Однако теперь мне нужно оценитьрезультаты, основанные на том, какие поля были сопоставлены и пользователя Type.

MCVE CODE

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 класс.Это то, что мне нужно помочь.В прошлом я создал компараторы, которые могут сортировать по одному полю (из моего предыдущего вопроса ):

    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. userId начинается с keyword
  2. lastName начинается с keyword
  3. emailAddress начинается с keyword
  4. lastName содержитkeyword
  5. emailAddress содержит keyword
  6. В пределах совпадений любой userType = "VET" должен быть указан первым

Я не ищу Google-алгоритмы уровня, но просто какой-то способ расставить приоритеты совпадений.Я не очень хорошо знаком с классом Comparator, и мне трудно понять, какие JavaDocs у него есть, поскольку он применим к моим потребностям.


В StackOverflow есть несколько постов, посвященных сортировке по несколькимполя, но все, что я нашел, сравнивают Person с Person.Здесь мне нужно сравнить Person поля со значением txtSearch.getText().

Как мне провести рефакторинг этого Comparator для настройки пользовательской сортировки такого типа?

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Ваша концепция оценки близка, вам просто нужно придумать факторы и следовать правилам.

Итак, вот простой пример:

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».

Предположение состоит в том, что правила оценки не являются исключительными (то есть, что каждое правило уточняет оценку, а не останавливает ее), и типы VET были связанынарушители в каждом критерии, против верхней части списка.Если VET необходимо перейти в начало списка (то есть все VET будут отображаться раньше всех не-VET), вы можете изменить 5 на 10000, присвоив ему свой собственный порядок величины.

Теперь, используядесятичные числа - это просто, но после 9 у вас кончатся величины (вы переполните целое число) - вы также можете использовать другие базы (в этом примере базу 3), предоставляя вам доступ к большему количеству «битов» вцелое число.Вы можете использовать long, или вы можете использовать значение BigDecimal и иметь столько критериев, сколько хотите.

Но основы те же.

Как только вы наберете счет, просто сравнитеоценки двух значений в вашем компараторе.

0 голосов
/ 02 октября 2018

Вы можете сортировать по нескольким полям, объединяя компараторы.Если первый компаратор объявляет два объекта равными, вы делегируете следующему компаратору и продолжаете так до тех пор, пока не будут запрошены все компараторы или один из них не вернет значение, отличное от 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.

Конечно, это слишком упрощенный пример.В производственном коде лучше использовать более чистое и более ориентированное на ООП решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...