Как отфильтровать значения карты с помощью набора строк? - PullRequest
0 голосов
/ 06 марта 2020

Мне пришлось отфильтровать Map значения, используя Set строк, я мог бы заставить это работать (партнер предложил использовать anyMatch() вместо того, что я здесь делаю, но я не понимаю, как ) и я хотел бы узнать, что вы думаете об этом алгоритме и, возможно, его можно улучшить, возможно, с помощью другой функции Stream или даже метода contains(), также я не уверен, смогу ли я избежать итерации непосредственно через Set (для каждого l oop).

ProductsResponse serviceResponse = (obtained from backend) ...;
Set<String> productIds = (some code to collect the strings from another API) ...;
//Here starts the filtering process 
serviceResponse.setProducts(serviceResponse.getProducts().entrySet().stream()
          .filter(product -> {
             for (String productId: productIds) {
                if (product.getKey().startsWith(productId)) {
                    return true;
                }
             }
             return false;
           }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

Важное примечание: Я использую Set для фильтрации продуктов в Map, строки в Set имеют почти тот же формат, что и ключи Map, поэтому их можно сравнивать, используя startsWith()

Update 1: добавляя определение участвующих классов.

class ProductsResponse() {

  private Map<String, ProductResource> products;
}

class ProductResource () {

   private String productId;
   private String name;
   private Double price;
}

Ответы [ 2 ]

4 голосов
/ 06 марта 2020

Если в наборе productIds, где a.startsWith(b) нет двух значений a и b, то вы можете значительно улучшить производительность, сделав набор TreeSet.

TreeSet<String> productIds = (some code to collect the strings from another API) ...;
* 1008. *

Или:

.filter(product -> Optional.ofNullable(productIds.floor(product.getKey()))
                   .map(product.getKey()::startsWith).orElse(false))

Это меняет производительность с O (n * m) до O (n * log (m)) , где m - это размер productIds.


ОБНОВЛЕНИЕ

Если есть , имеются значения a и b in productIds установить где a.startsWith(b), тогда вам понадобится немного дополнительных логик c.

Например, если набор содержит G и GED, и вы проверяете, содержит ли он префикс для GET, затем floor() вернет GED, поэтому вам нужно удалить последний символ и повторить поиск с GE, теперь возвращая G, чтобы найти его в качестве действительного префикса.

Поэтому нам нужно добавить al oop для перепроверки:

.filter(product -> {
   String candidate = product.getKey();
   while ((candidate = productIds.floor(candidate)) != null) {
      if (product.getKey().startsWith(candidate))
         return true;
      candidate = candidate.substring(0, candidate.length() - 1);
   }
   return false;
})

Это немного замедлит поиск, но все же будет намного лучше, чем полный последовательный поиск.

2 голосов
/ 06 марта 2020

Вы можете избежать внутреннего for-l oop путем потоковой передачи через набор и использования anyMatch:

serviceResponse.setProducts(serviceResponse.getProducts()
    .entrySet().stream()
    .filter(product -> productIds.stream().anyMatch(productId -> product.getKey().startsWith(productId)))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

Вы также можете использовать для краткости ссылку на метод:

serviceResponse.setProducts(serviceResponse.getProducts()
    .entrySet().stream()
    .filter(product -> productIds.stream().anyMatch(product.getKey()::startsWith))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
...