Сортировка Multimap по ключам только в Java

Я хотел бы иметь c.g.c.c.Multimap, отсортированный только по ключам. Значения не должны быть отсортированы. Я пытался создать что-то с TreeMultimap гуавы, но я не могу его использовать, потому что тип значения не реализует Comparable.

public class MyObject /* doesn't implement Comparable */ {
  private String name;
  private int score;
  // Getters/setters are implemented
  public static Function<MyObject,Integer> myObjectToScore {
    @Override public Integer apply (MyObject o) { return o.score; }
  }
  public static Multimap<Integer,MyObject> indexOnScore(Iterable<MyObject> i) {
    Multimap<Integer,MyObject> m = Multimaps.index(i, myObjectToScore());
    // Do the sort of the keys.
    return m;
  }
}

Я думал о том, чтобы получить SortedSet ключей, а затем выполнить итерацию по каждому из этих ключей в отсортированном наборе для получения различных значений, но я надеялся использовать существующую (еще неизвестную) функцию в Guava, а не использовать этот тип < em>взломать.

Примечание. Я не буду реализовывать MyObject Comparable, потому что это не имеет смысла с моим реальным объектом.


Пример ввода/вывода:

Set<MyObject> s = Sets.newHashSet(
  new MyObject("a", 2),
  new MyObject("b", 3),
  new MyObject("c", 1),
  new MyObject("d", 3),
  new MyObject("e", 1)
); // Assuming constructor MyObject(String name, int score)

for (Map.Entry<Integer, MyObject> e: MyObject.indexedOnScore(s).entries()) {
  System.out.printf("%d -> %s%n", e.getKey(), e.getValue().getName());
}

Отпечатки:

1 -> c // or switched with line below
1 -> e
2 -> a
3 -> b // or switched with line below
3 -> d

person Olivier Grégoire    schedule 31.03.2011    source источник
comment
можете привести пример ввода/вывода   -  person ant    schedule 31.03.2011
comment
вы видели download.oracle.com/javase /1.4.2/docs/api/java/util/ ?   -  person Dan    schedule 31.03.2011
comment
@Dan: да, я знаю этот класс, но я говорю здесь о Multimaps Гуавы, а не Map.   -  person Olivier Grégoire    schedule 31.03.2011
comment
Что такое c.g.c.c? Пожалуйста, не используйте такие сокращения в вопросе.   -  person NomadMaker    schedule 09.08.2020


Ответы (8)


Multimaps.index возвращает ImmutableListMultimap, поэтому вы не сможете отсортировать его после его создания. Однако вы можете сначала создать отсортированную копию вашего Iterable<MyObject> и передать ее Multimap.index... ImmutableListMultimap сохраняет вещи в том же порядке, в котором они были заданы.

public static ImmutableMultimap<Integer, MyObject> indexOnScore(Iterable<MyObject> i) {
  List<MyObject> sorted = Ordering.natural().onResultOf(myObjectToScore())
      .sortedCopy(i);
  return Multimaps.index(sorted, myObjectToScore());
}

Другим вариантом может быть создание TreeMultimap и использование Ordering.arbitrary() в качестве Comparator для значений.

person ColinD    schedule 31.03.2011
comment
Да, это то, что я искал. И ты дал мне не одну, а две возможности. Большое спасибо! Редактировать: мне, однако, не нравятся два вида подряд, но я постараюсь справиться с этим. - person Olivier Grégoire; 31.03.2011
comment
@ogregoire: Ну, на самом деле он не сортирует дважды ... он просто сортирует один раз, а затем создает Multimap в том же порядке. Однако он копирует данные из Iterable в промежуточный List. - person ColinD; 31.03.2011
comment
Вы также можете использовать ImmutableMultimap.builder().orderKeysBy(); или вы можете использовать Multimaps.newMultimap(), который позволяет вам выбирать резервную карту и коллекции (используйте TreeMap и, скажем, ArrayList). - person Kevin Bourrillion; 01.04.2011
comment
Мило... раньше не замечал ImmutableMultimap.builder().orderKeysBy(). - person ColinD; 01.04.2011
comment
@ColinD Думаю, было бы очень полезно включить комментарий Кевина в свой ответ. - person assylias; 07.09.2013
comment
@ColinD Почему бы не Ordering.allEqual() вместо Ordering.arbitrary() для значений? В документации сказано: возвращает порядок, в котором все значения рассматриваются как равные, что указывает на отсутствие порядка. - person Dag; 11.04.2014
comment
@Dag: Основная причина не делать этого заключается в том, что TreeMultimap использует TreeSet для своих значений, и если все значения считаются равными, в конечном итоге он будет содержать только один этих значений на ключ. Кроме того, Ordering.allEqual() был фактически добавлен в Guava 13, спустя год после того, как я ответил на этот вопрос. =П - person ColinD; 11.04.2014

MultimapBuilder был введено в Гуаве 16:

<K extends Comparable<? super K>, V> ListMultimap<K, V> multimap() {
    return MultimapBuilder.treeKeys().linkedListValues().build();
}

Это сохраняет ваши ключи отсортированными в их естественном порядке (MultimapBuilder::treeKeys также перегружен, чтобы принимать пользовательский компаратор), а значения, связанные с каждым ключом, сохраняются в LinkedList (ArrayList и HashSet среди других вариантов).

person gdejohn    schedule 10.07.2014

Хотя на конкретную ситуацию ОП, похоже, ответили с помощью неизменяемых функций построения мультикарт, мне нужна была изменяемая версия того, о чем он просил. Если это кому-то поможет, вот общий метод, который я создал:

static <K, V> Multimap<K, V> newTreeArrayListMultimap(
    final int expectedValuesPerKey)
{
    return Multimaps.newMultimap(new TreeMap<K, Collection<V>>(),
        new Supplier<Collection<V>>()
        {
            @Override
            public Collection<V> get()
            {
                return new ArrayList<V>(expectedValuesPerKey);
            }
        });
}
person Trevor Robinson    schedule 03.11.2011

Вызовите Multimaps.newMultimap, что дает вам возможность создавать, например, Multimap на основе TreeMap, значениями которого являются ArrayLists.

person Jared Levy    schedule 04.04.2011
comment
Интересно видеть, что вы написали код Multimaps, но, похоже, никто этого не заметил. - person nsawaya; 16.05.2016

Я хотел бы отметить, что альтернативное предлагаемое решение, а именно "создать TreeMultimap и использовать Ordering.arbitrary() в качестве компаратора для значений", работает только в том случае, если MyObject не переопределяет equals () или хэш-код(). Ordering.arbitrary() несовместим с equals и вместо этого использует идентификатор объекта, что делает его не очень хорошей идеей использовать его в сочетании с TreeSet.

person GFonte    schedule 16.04.2013

Как насчет этого:

    public static Multimap<Integer, MyObject> indexOnScore(Iterable<MyObject> i) {
        Multimap<Integer, MyObject> m = Multimaps.index(i, myObjectToScore());

        Multimap<Integer, MyObject> sortedKeys = Multimaps.newMultimap(
                Maps.<Integer, Collection<MyObject>>newTreeMap(),
                new Supplier<Collection<MyObject>>() {
                    @Override
                    public Collection<MyObject> get() {
                        return Lists.newArrayList(); // Or a Set if appropriate
                    }
                }
        );

        sortedKeys.putAll(m);

        return sortedKeys;
    }

Однако в этом случае возникнут накладные расходы на создание двух отдельных Multimap.

person Paul Blessing    schedule 31.03.2011
comment
Как вы сказали, слишком сложно создать два разных Multimap. - person Olivier Grégoire; 31.03.2011
comment
Да, мне тоже больше нравится ответ @ColinD. - person Paul Blessing; 31.03.2011

Вы можете сделать это с помощью TreeMultimap. если вы используете компараторы.

Создайте компаратор для типа ключа и типа значения (MyObject?). Затем используйте create(Comparator keyComparator, Comparator valueComparator) для создания карты.

Преимущество использования Comparator над реализацией Comparable заключается в том, что вы можете сделать Comparator специфичным для ситуации, которую вы хотите с картой, и это не влияет на ваш объект в целом. Пока ваш компаратор совместим с равными, он может делать все, что вы хотите.

person justkt    schedule 31.03.2011
comment
Мне это не очень нравится. Я сказал, что нет смысла заставлять MyObject реализовывать Comparable. Расширение этого утверждения заключается в том, что у меня логически нет значения по умолчанию Comparator для MyObject. Моя цель на самом деле состоит в том, чтобы иметь Multimap с отсортированными ключами, но значения для одного ключа не должны быть отсортированы. - person Olivier Grégoire; 31.03.2011

Лучшее решение, которое всегда работает для меня, — использовать Multimap и TreeMultiMap. это упорядочит результаты в порядке возрастания ключей, даже если у вас есть несколько дубликатов ключей. Решение ниже:

Multimap<Double, Integer> map= TreeMultimap.create(Ordering.natural().reverse(),         Ordering.natural());

if (!map.isEmpty()) {               
                printMap(map);
            }

public static <K, V> void printMap(Multimap<Double, Integer> map) throws Exception {
        for (Map.Entry<Double, Integer> entry : map.entries()) {
            System.out.println("Key : " + entry.getKey() 
                + " Value : " + entry.getValue());              
        }
    }
person Rahul k    schedule 19.09.2018