-1

I have a SplayTreeSet of the objects ChatRoomListModel. I'd like to order my set based on the objects DateTime createTime value.

I'm not sure where I'm going wrong because there's duplicate items being added item. I later down the line perform a _latestMessages.add(newMessage) and it's not actually calling the overloads and there's duplicates being added.

I tested by using _latestMessage.contains(newMessageButSameChatRoomId), it returns false.

When I perform _latestMessage.toSet() every duplicate goes away.

How can I get SplayTreeSet to use my overloading equals? Thanks!

  ObservableSet<ChatRoomListModel> _latestMessages = ObservableSet.splayTreeSetFrom(
    ObservableSet(),
    compare: (a, b) => b.compareTo(a),
  );

The ChatRoomListModel model has the following methods and overloads:

  int compareTo(ChatRoomListModel other){
    return messagesModel.createTime.compareTo(other.messagesModel.createTime);
  }

  ChatRoomListModel copyWith({
    String? firstName,
    String? otherUserId,
    MessagesModel? messagesModel,
  }) {
    return ChatRoomListModel(
      firstName: firstName ?? this.firstName,
      otherUserId: otherUserId ?? this.otherUserId,
      messagesModel: messagesModel ?? this.messagesModel,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ChatRoomListModel &&
          runtimeType == other.runtimeType &&
          messagesModel.chatRoomId == other.messagesModel.chatRoomId;

  @override
  int get hashCode => messagesModel.chatRoomId.hashCode;
DanMossa
  • 994
  • 2
  • 17
  • 48
  • It seems very strange that your `compareTo` and `operator ==` implementations have completely different notions of what constitutes element equality. `SplayTreeSet` considers elements to be equal only if `compareTo` returns 0. – jamesdlin Feb 19 '23 at 03:36
  • @jamesdlin Yeah. Does the goal of having a list where each element has a unique `chatRoomId` and only the most recent `createTime` is kept possible? – DanMossa Feb 19 '23 at 03:38
  • To be clear: you want a data structure that keeps only the most recent message for a given `chatRoomId`? If so, then you could maintain a `Map` of `chatRoomId`s to whatever the latest message is and replace items in the `Map` only if the replacement is newer than the existing value. – jamesdlin Feb 19 '23 at 03:45
  • @jamesdlin Correct! But I then want the values ordered by createtime – DanMossa Feb 19 '23 at 05:49
  • I would use two data structures then. You could encapsulate them together to ensure that they are kept in sync when entries are added or removed. – jamesdlin Feb 19 '23 at 06:21
  • @jamesdlin If you can come with an efficient way to do so I'd be happy with that answer. – DanMossa Feb 19 '23 at 19:27

1 Answers1

0

Your issue is that you have two completely different notions of what it means for two ChatRoomListModel objects to be "equal". You provide both compareTo and operator == implementations, but they consider different sets of properties, so compareTo can return 0 when operator == returns false, which is confusing at best. SplayTreeMap considers only compareTo, not operator ==. From the SplayTreeSet documentation:

Elements of the set are compared using the compare function passed in the constructor, both for ordering and for equality. If the set contains only an object a, then set.contains(b) will return true if and only if compare(a, b) == 0, and the value of a == b is not even checked.

I'm presuming what you call "duplicates" are elements that have equal chatRoomIds but that have different creation times, and creation times are the only things that your SplayTreeSet cares about.

If your goal is to maintain only the latest message per chatRoomId, you need to maintain a data structure that uses the chatRoomId (a String) as a key. The natural collection for that would be a Map<String, ChatRoomListModel>. (Since the ChatRoomListModel knows its own chatRoomId, it also could just be a HashSet with explicit equals and hashCode callbacks.)

If you additionally want to keep messages in chronological order, you either will need to explicitly sort them afterward or maintain a separate data structure that keeps them in chronological order. You could continue using a SplayTreeSet for that. Basically before you add any entry to the SplayTreeSet, check the Map first to see if an existing entry for that chatRoomId.

I don't fully understand your data structures, but here's an example that you presumably can adapt:

import 'dart:collection';

class Message {
  Message(this.creationTime, {required this.chatRoomId, required this.text});

  final DateTime creationTime;
  final String chatRoomId;
  final String text;

  @override
  String toString() => '$creationTime: [$chatRoomId] $text';
}

class Messages {
  final _latestMessages = <String, Message>{};
  final _orderedMessages = SplayTreeSet<Message>((message1, message2) {
    var result = message1.creationTime.compareTo(message2.creationTime);
    if (result != 0) {
      return result;
    }
    result = message1.chatRoomId.compareTo(message2.chatRoomId);
    if (result != 0) {
      return result;
    }
    return message1.text.compareTo(message2.text);
  });

  void add(Message message) {
    var existingMessage = _latestMessages[message.chatRoomId];
    if (existingMessage != null &&
        message.creationTime.compareTo(existingMessage.creationTime) < 0) {
      // An existing message exists with a later creation date.  Ignore the
      // incoming message.
      return;
    }

    _latestMessages[message.chatRoomId] = message;
    _orderedMessages.remove(existingMessage);
    _orderedMessages.add(message);
  }

  void printAll() => _orderedMessages.forEach(print);
}

void main() {
  var messages = Messages();

  messages.add(Message(
    DateTime(2023, 1, 1),
    chatRoomId: 'foo',
    text: 'Hello foo!',
  ));

  messages.add(Message(
    DateTime(2023, 1, 2),
    chatRoomId: 'bar',
    text: 'Goodbye bar!',
  ));

  messages.add(Message(
    DateTime(2023, 1, 2),
    chatRoomId: 'foo',
    text: 'Goodbye foo!',
  ));

  messages.add(Message(
    DateTime(2023, 1, 1),
    chatRoomId: 'bar',
    text: 'Hello bar!',
  ));

  messages.printAll();
}
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • Can you clarify what `return message1.text.compareTo(message2.text);` does? – DanMossa Feb 19 '23 at 20:37
  • @DanMossa It does a string comparison. It's there to provide a strict, canonical ordering. Without it, then two `Message`s with identical `creationTime` and `chatRoomId` values would be considered equal even if they had different values for `text`. – jamesdlin Feb 19 '23 at 20:52