Карта, которая позволяет предоставлять компаратор равенства и функцию хэширования отдельно

Пытаясь моделировать многочлены, в частности их умножение, я столкнулся со следующей проблемой. Во время умножения отдельные мономы двух многочленов умножаются, и, конечно, может случиться так, что у меня будет (3x^2 y + 5x y^2) * (x + y). Результат содержит 3x^2 y^2 и 5x^2 y^2, которые я хочу сразу объединить путем сложения.

Естественно, я хотел бы использовать часть монома x^2 y^2 в качестве ключа в (хэш-карте) для сложения различных коэффициентов (3 и 5 в примере). Но мономиальный объект, как я предполагаю, должен, естественно, также содержать коэффициент, который не должен быть частью ключа карты.

Конечно, я мог бы написать equals/hashcode мономиального объекта, чтобы они игнорировали коэффициент. Но это кажется таким неправильным, потому что математически ясно, что моном равен другому только в том случае, если равны и коэффициенты.

Введение бескоэффициентного мономиального объекта для промежуточных операций также не выглядит правильным.

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

Если не считать реализации карты, которая использует не ключи equals/hashcode, а выделенную, есть ли какие-нибудь лучшие идеи о том, как объединить мономы?


person Harald    schedule 10.09.2014    source источник


Ответы (4)


Поскольку реализация JDK [Linked]HashMap не позволяет вам переопределить реализацию equals/hashCode, единственными другими способами являются:

  • такой обертывающий объект:

      class A {
        private final String fieldA; // equals/hashCode based on that field.
        private final String fieldB; // equals/hashCode based on that field.
      }
    
      class B {
        private A a;
        public int hashCode() {return a.fieldA.hashCode();} 
        public boolean equals(Object o) {... the same ... }
      }
    
      Map<B, Value> map = new HashMap<B, Value>();
      map.put(new B(new A("fieldA", "fieldB")), new Value(0));
    

    Ну, с большим количеством геттеров/конструкторов.

    Это может раздражать, и, возможно, существует какая-то библиотека (например, Guava), которая позволяет использовать метод equals/hashCode, как вы можете указать Comparator для TreeMap.

    Ниже вы найдете пример реализации, в котором показано, что нужно сделать, чтобы украсить существующую карту.

  • используйте TreeMap с определенным Comparator. Другой ответ указывает на это, но я бы сказал, что вам нужно правильно определить Comparator, потому что это может привести к проблемам: если ваш метод compareTo возвращает 0 при достижении равенства и 1 в другом случае, это означает, что нет естественного заказ. Вы должны попытаться найти его или использовать объект-оболочку.


Если вы хотите принять вызов, вы можете создать базовую реализацию, используя делегирование/декорирование поверх другого HashMap (это может быть карта другого типа, например LinkedHashMap):

public class DelegatingHashMap<K,V> implements Map<K,V> {
  private final BiPredicate<K,Object> equalsHandler;
  private final IntFunction<K> hashCodeHandler;
  private final Map<Wrapper<K>,V> impl = new HashMap<>();

  public DelegatingHashMap(
    BiPredicate<K,Object> equalsHandler,
    IntFunction<K> hashCodeHandler
  ) {
    this.equalsHandler = requireNonNull(equalsHandler, "equalsHandler");
    this.hashCodeHandler= requireNonNull(hashCodeHandler, "hashCodeHandler");
  }

  public Object get(K key) {
    Wrapper<K> wrap = new Wrapper<>(key);
    return impl.get(wrap);
  }  

  ...

  static class Wrapper<K2> {
    private final K2 key;
    private final BiPredicate<K> equalsHandler;
    private final IntFunction<K> hashCodeHandler;
    public int hashCode() {return hashCodeHandler.apply(key);}
    public boolean equals(Object o) {
      return equalsHandler.test(key, o);
    }
  }
}

И код с использованием карты:

DelegatingHashMap<String, Integer> map = new DelegatingHashMap<>(
  (key, old) -> key.equalsIgnoreCase(Objects.toString(o, "")),
  key -> key.toLowerCase().hashCode()
);
map.put("Foobar", 1);
map.put("foobar", 2);

System.out.println(map); // print {foobar: 2}

Но, возможно, лучше всего (для памяти) было бы переписать HashMap, чтобы напрямую использовать обработчик вместо оболочки.

person NoDataFound    schedule 10.09.2014
comment
Использование Wrapper, конечно, возможно, но я считаю это слишком большими накладными расходами, если бы не производительность (не оптимизируйте заранее), но концептуально, на мой взгляд, это слишком много. - person Harald; 12.09.2014
comment
Тогда попробуйте с TreeMap, но это должен быть TreeMap‹K, List‹V›› или не забудьте суммировать коэффициент :) - person NoDataFound; 13.09.2014

Вы можете использовать TreeMap с пользовательским компаратором:

TreeMap(Comparator<? super K> comparator)

Создает новую пустую карту дерева, упорядоченную в соответствии с заданным компаратором.

(Источник)

person Alexander Langer    schedule 10.09.2014

Рассмотрите возможность использования TreeMap, который является SortedMap и, следовательно, также Map. Вы можете предоставить Comparator его конструктору. Отсортированная карта будет использовать этот Comparator для сортировки ключей карты. Но важно то, что в вашем случае ключи будут считаться равными, если Comparator возвращает 0. В вашем случае потребуется Comparator, несовместимое с равными, что может вызвать у вас проблемы, если вы не осторожно.

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

person Raedwald    schedule 10.09.2014
comment
TreeMap полагается на двоичную сортировку монома. Хотя хорошо, что я могу предоставить необходимый компаратор, я бы предпочел использовать только отсортированный двоичный массив. Я думаю, что накладные расходы меньше, а производительность должна быть сопоставима. - person Harald; 12.09.2014

Я думаю, может быть лучше разделить одночлен на 2 части: коэффициент и переменную. Таким образом, вы можете использовать переменную часть своей карты в качестве ключа и коэффициент в качестве значения (которое затем может быть обновлено).

Весь этот код должен быть деталями реализации внутри объекта Polynomial

Я не уверен, почему вы думаете, что моном без коэффициентов выглядит неправильно. Вам не нужно выставлять объект наружу, если вы этого не хотите. Но это может быть хороший способ иметь геттеры на вашем Polynomial для получения коэффициентов для каждого монома.

person dkatzel    schedule 10.09.2014
comment
Что ж, моном без коэффициента выглядит неправильно, потому что без него он уже не является мономом в математическом смысле. - person Harald; 12.09.2014