How to prioritize/rank FilteredList results within a predicate?

别来无恙 提交于 2019-12-05 14:21:46

If not mistaken you only need to find a way to sort your filtered results correctly. To keep it simple I will use this comparator instead of yours :

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 itemName, String searchKey) {
                int sum = 0;
                if (itemName.startsWith(searchKey)) {
                    sum += 2;
                }

                if (itemName.contains(searchKey)) {
                    sum += 1;
                }
                return sum;
            }
        };

In the code above, I compare two DataItem. Each one will have a 'score' which depends on how similar their names are from our search keyword. For simplicity lets say we give 1 point if the searchKey appeared in the name of our item and 2 points if the item name starts with the searchKey, so now we can compare those two and sort them. If we return -1 the item1 will be placed first, if we return 1 then the item2 will be placed first and return 0 otherwise.

Here is addSearchFilter() method I used in your example :

private void addSearchFilter() {
        FilteredList<DataItem> filteredList = new FilteredList<>(dataItems);

        txtSearch.textProperty().addListener((observable, oldValue, newValue) -> filteredList.setPredicate(dataItem -> {

            dataItemListView.getSelectionModel().clearSelection();

            if (newValue == null || newValue.isEmpty()) {
                return true;
            }

            String query = newValue.toLowerCase();

            if (dataItem.getName().toLowerCase().contains(query)) {
                return true;
            } else {
                String[] searchTerms = query.split(" ");
                boolean match = false;
                for (String searchTerm : searchTerms) {
                    match = dataItem.getKeywordsString().toLowerCase().contains(searchTerm);
                }
                return match;
            }
        }));

        SortedList<DataItem> sortedList = new SortedList<>(filteredList);

        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 itemName, String searchKey) {
                int sum = 0;
                if (itemName.startsWith(searchKey)) {
                    sum += 2;
                }

                if (itemName.contains(searchKey)) {
                    sum += 1;
                }
                return sum;
            }
        };

        sortedList.setComparator(byName);

        dataItemListView.setItems(sortedList);
    }

Of course the findScore() could be more sophisticated if you want to create a more complex score system (for example checking upper and lower case letters, give more points depending the position of the keyword found in the item name etc).

I may have found a different way to accomplish this. Instead of using a Predicate, I've changed the ChangeListener to just use a couple of loops and build a new List manually:

    txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {

        if (newValue == null || newValue.isEmpty()) {
            // Reset the ListView to show all items
            dataItemListView.setItems(dataItems);
            return;
        }

        ObservableList<DataItem> filteredList = FXCollections.observableArrayList();
        String query = newValue.toLowerCase().trim();

        // First, look for exact matches within the DataItem's name
        for (DataItem item : dataItems) {
            if (item.getName().toLowerCase().contains(query)) {
                filteredList.add(0, item);
            } else {

                // If the item's name doesn't match, we'll look through search terms instead
                String[] searchTerms = query.split(" ");
                for (String searchTerm : searchTerms) {
                    // If the item has this searchTerm and has not already been added to the filteredList, add it
                    // now
                    if (item.getKeywordsString().toLowerCase().contains(searchTerm)
                            && !filteredList.contains(item)) {
                        filteredList.add(item);
                    }
                }
            }
        }

        dataItemListView.setItems(filteredList);

I'll leave the question unanswered for now to see if anyone has a solution for using the Predicate as well.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!