Android: SortedList с дубликатами

У меня есть некоторые проблемы с пониманием RecyclerViews SortedList.

Допустим, у меня есть очень простой класс, содержащий только очень простой класс, содержащий данные:

public class Pojo {
    public final int id;
    public final char aChar;

    public Pojo(int id, char aChar) {
        this.id = id;
        this.aChar = aChar;
    }

    @Override
    public String toString() {
        return "Pojo[" + "id=" + id
                + ",aChar=" + aChar
                + "]";
    }
}

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

Но когда у меня есть SortedList с такими обратными вызовами:

....

@Override
public boolean areContentsTheSame(Pojo oldItem, Pojo newItem) {
    return oldItem.aChar == newItem.aChar;
}

@Override
public int compare(Pojo o1, Pojo o2) {
    return Character.compare(o1.aChar, o2.aChar);
}

@Override
public boolean areItemsTheSame(Pojo item1, Pojo item2) {
    return item1.id == item2.id;
}

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

sortedList.add(new Pojo(1, 'a'));
sortedList.add(new Pojo(1, 'b'));

Я ожидаю, что список обновит элемент. Вместо этого теперь у меня есть несколько элементов, хотя areItemsTheSame вернул true.


person Paul Woitaschek    schedule 06.08.2015    source источник
comment
Список может иметь дубликаты. Попробуйте использовать hashMap.   -  person Anitha Manikandan    schedule 06.08.2015
comment
Насколько я понимаю, не Если элемент уже существует в списке и критерии его сортировки не меняются, он заменяется на существующий Item.. Мне не нужен HashMap, так как мне нужна эффективная связь с RecyclerView.   -  person Paul Woitaschek    schedule 06.08.2015
comment
Почему этот вопрос помечен тегом Java?   -  person Boris    schedule 06.08.2015
comment
@Paul Woitaschek, тебе удалось решить эту проблему?   -  person Ari    schedule 10.11.2015
comment
Да, @Ari, я проверяю это вручную. См. здесь   -  person Paul Woitaschek    schedule 10.11.2015


Ответы (6)


SortedList не сохраняет никаких сопоставлений по идентификаторам (поскольку в API нет идентификаторов). Поэтому, когда критерии сортировки меняются (в вашем случае от a до b), SortedList не может найти существующий элемент.

Вы можете сохранить сопоставление идентификаторов самостоятельно, а затем использовать метод добавления следующим образом:

void add(Item t) {
  Item existing = idMap.get(t.id);
  if (existing == null) {        
     sortedList.add(t);
  } else {
     sortedList.updateItemAt(sortedList.indexOf(existing), t);
  }
  idMap.put(t.id, t);
}

Вам также потребуется реализовать метод удаления для удаления элемента из idMap.

person yigit    schedule 07.08.2015
comment
Как вы указали, при изменении критериев сортировки SortedList не может найти существующий элемент, как indexOf узнать правильный индекс? В моем случае он всегда возвращает -1. - person Shaw; 30.03.2016
comment
вам нужно вызвать его перед изменением данных. - person yigit; 31.03.2016

Как Минхтд уже упоминал в своем ответе, проблема заключается в вашем compare().

Смотрите, add() ищет индекс существующего объекта, используя compare(), который вы реализуете. Поэтому, когда ваш compare() возвращает что-то отличное от 0, он добавляет объект в список.

Вам нужно будет проверить, совпадают ли элементы, прежде чем сравнивать их содержимое. Однако, если ваш контент может быть таким же, вам потребуется вторичное сравнение.

Вот как я бы реализовал compare() в вашем случае:

@Override
public int compare(Pojo o1, Pojo o2) {
    int result;
    if (areItemsTheSame(o1, o2) {
        result = 0;
    } else {
        result = Character.compare(o1.aChar, o2.aChar);
        if (result == 0) {
            // TODO implement a secondary comparison 
        }
    }

    return result;
}
person robocab    schedule 19.08.2015

Я думаю, вы должны использовать Integer.compare(o1.id, o2.id); в методе compare, именно здесь SortList решает, являются ли эти 2 элемента одинаковыми или нет.

person justHooman    schedule 06.08.2015
comment
Из документа ясно сказано, что это для заказа. И я думаю, что если › 2 элемента одинаковы или нет, решается в areItemsTheSame() - person Paul Woitaschek; 06.08.2015
comment
Ну, я думаю, что эти 2 метода compare и areItemsTheSame похожи на hashCode и equal. Если вы хотите, чтобы 2 объекта имели одинаковую идентичность, они должны сначала иметь одинаковые hashCode, а затем equal возвращать true. Таким образом, в этом случае сначала должно идти compare == 0, а затем истинно areItemsTheSame. - person justHooman; 06.08.2015

Вы можете проверить, существует ли объект в отсортированном списке, выполнив это.

if (sortedList.indexOf(item) == -1) 
{
    sortedList.add(item);  //Item still does not exist because index is -1
} 
else 
{
    sortedList.updateItemAt(sortedList.indexOf(item), item);
}
person gienapps    schedule 21.10.2017

У меня была аналогичная проблема при создании приложения чата, которое мне нужно для обновления сообщений по их идентификаторам и сортировки их по датам. SortedList из библиотеки поддержки этого не делает, или, по крайней мере, у меня было мало времени, чтобы погрузиться в его исходный код и протестировать. Итак, я создал небольшой компонент MultiSortedList:

import android.support.v7.widget.RecyclerView

/**
 * Created by abduaziz on 6/14/18.
 *
 *   MultiSortedList is a wrapper component to ArrayList that keeps its elements in a sorted order
 *   using UpdateCallbackInterface. It is intended to be used inside recycler view adapters.
 *
 * */

class MultiSortedList<T>(var updateCallback: UpdateCallback<T>, var adapter: RecyclerView.Adapter<*>? = null) {

    companion object {
        val TAG = "SORTEDLIST"
    }

    // internal list to hold elements by sortBy() -> visible to user
    private val list: ArrayList<T> = arrayListOf()

    // internal list to hold elements by updateBy() -> not visible
    private val uList: ArrayList<T> = arrayListOf()

    // add adapter from ui
    fun addAdapter(adapter: RecyclerView.Adapter<*>?) {
        this.adapter = adapter
    }

    /*
    * 1. Search for existing element that satisfies updateBy()
    * 2. Remove the existing element if found
    * 3. Add the new item with sortBy()
    * 4. Notify if adapter is not null
    * */
    fun add(newItem: T) {
        remove(newItem)

        // save to internal list by updateBy()
        var toBeStoredPosition = uList.binarySearch { updateCallback.updateBy(it, newItem) }
        if (toBeStoredPosition < 0) toBeStoredPosition = -(toBeStoredPosition + 1)
        uList.add(toBeStoredPosition, newItem)

        // save to UI list and notify changes
        var sortPosition = list.binarySearch { updateCallback.sortBy(it, newItem) }
        if (sortPosition < 0) sortPosition = -(sortPosition + 1)
        list.add(sortPosition, newItem)
        adapter?.notifyItemInserted(sortPosition)
    }

    /*
    * Remove and notify the adapter
    * */
    fun remove(removeItem: T) {
        val storedElementPosition = uList.binarySearch { updateCallback.updateBy(it, removeItem) }
        if (storedElementPosition >= 0 && storedElementPosition < uList.size) {

            // remove from internal list
            val itemTobeRemoved = uList[storedElementPosition]
            uList.removeAt(storedElementPosition)

            // remove from ui
            val removePosition = list.binarySearch { updateCallback.sortBy(it, itemTobeRemoved) }
            if (removePosition >= 0 && removePosition < list.size) {
                list.removeAt(removePosition)
                adapter?.notifyItemRemoved(removePosition)
            }
        }
    }

    // can be accessed -> list.get(position) or list[position]
    operator fun get(pos: Int): T {
        return list[pos]
    }

    // for adapter use
    fun size(): Int {
        return list.size
    }

    inline fun forEachIndexed(action: (Int, T) -> Unit) {
        for (index in 0 until size()) {
            action(index, get(index))
        }
    }

    /*
    * UpdateCallback is the main interface that is used to compare the elements.
    *   - sortBy() is used to locate new elements passed to SortedList
    *   - updateBy() is used to update/remove elements
    *
    * Typical example would be Message model class which we want to:
    *   - Sort messages according to their dates
    *   - Update/Remove messages according to their randomIDs or IDs.
    * */
    interface UpdateCallback<T> {
        fun sortBy(i1: T, i2: T): Int
        fun updateBy(oldItem: T, newItem: T): Int
    }
}

Использование объясняется здесь: https://medium.com/@abduazizkayumov/sortedlist-with-recyclerview-part-2-64c3e9b1b124

person Abduaziz Kayumov    schedule 13.06.2018

В Java коллекция, не содержащая повторяющихся элементов, называется Set. Общие классы реализации: HashSet и TreeSet. Вы ошибаетесь, полагая, что SortedList делает это.

person Boris    schedule 06.08.2015
comment
Но не это подразумевать это? - person Paul Woitaschek; 06.08.2015
comment
Да, мне это кажется запутанным. Обычно List может содержать дубликаты, а Set — нет. Можете ли вы попробовать использовать SortedSet вместо SortedList? - person Boris; 06.08.2015
comment
Это Android SortedList, а не utils.java. Нет SortedSet для привязки с recyclerview. - person Paul Woitaschek; 06.08.2015