golang-set icon indicating copy to clipboard operation
golang-set copied to clipboard

Suggestion: Add comparator for structs

Open ScreamingHawk opened this issue 1 year ago • 4 comments

What is considered comparable in Go?

  • Structs if all of their fields are also comparable independently

Would be great to be able to pass a key (or function) to for struct comparison instead of relying on complete field equality.

Particularly useful when comparing structs with incomplete data (e.g. from an API) with complete data (e.g. from DB).

ScreamingHawk avatar Jul 10 '24 21:07 ScreamingHawk

Hello,

Thanks for the recommendation! I'm certainly not opposed to changes that would be considered general enough that others would find useful and if you have an API in mind I'd love to hear more details.

This package is so widely used it would be help to get more context and allow others to chime in.

deckarep avatar Jul 10 '24 22:07 deckarep

Here's some pseudo code for the pattern I've got. Please forgive errors.

type Entity struct {
  ID        uint64
  Data1 string
  Data2 bool
  // ...
  UpdatedAt *time.Time
}

func Update(apiEntities []*Entity) error {
  dbEntities = db.ListEntities()
  origSet := mapset.NewSet(dbEntities...)
  updateSet := mapset.NewSet(apiEntities...)

  added := updateSet.Difference(origSet)
  // Process newly added entries

  removed := origSet.Difference(updateSet)
  // Process deleted entries
}

Entity's that come in via the API don't have an ID populated, this causes all data to not match. Also in this case UpdatedAt should be ignored when looking for equality.

If we could pass a function to do the comparison, that would be a nice general solution. Something like:

func entityEqual(a Entity, b Entity) bool {
  // Whatever logic here
  return a.Data1 == b.Data1 && a.Data2 == b.Data2
}

func Update(apiEntities []*Entity) error {
  // ...
  added := updateSet.Difference(origSet, entityEqual)
  // ...
}

ScreamingHawk avatar Jul 10 '24 23:07 ScreamingHawk

Sounds good I’ll be looking at this next week due to a new project deadline so please forgive the upcoming delay!

On Wed, Jul 10, 2024 at 4:01 PM Michael Standen @.***> wrote:

Here's some pseudo code for the pattern I've got. Please forgive errors.

type Entity struct { ID uint64 Data1 string Data2 bool // ... UpdatedAt *time.Time } func Update(apiEntities []*Entity) error { dbEntities = db.ListEntities() origSet := mapset.NewSet(dbEntities...) updateSet := mapset.NewSet(apiEntities...)

added := updateSet.Difference(origSet) // Process newly added entries

removed := origSet.Difference(updateSet) // Process deleted entries }

Entity's that come in via the API don't have an ID populated, this causes all data to not match. Also in this case UpdatedAt should be ignored when looking for equality.

If we could pass a function to do the comparison, that would be a nice general solution. Something like:

func entityEqual(a Entity, b Entity) bool { // Whatever logic here return a.Data1 == b.Data1 && a.Data2 == b.Data2 } func Update(apiEntities []*Entity) error { // ... added := updateSet.Difference(origSet, entityEqual) // ... }

— Reply to this email directly, view it on GitHub https://github.com/deckarep/golang-set/issues/139#issuecomment-2221666023, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABQ73R6UMVPORVJYVXZG23ZLW4NTAVCNFSM6AAAAABKVWYCHKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMRRGY3DMMBSGM . You are receiving this because you commented.Message ID: @.***>

deckarep avatar Jul 10 '24 23:07 deckarep

@ScreamingHawk - circling back on this seems like this would be a fairly large breaking change. I understand what your concern is but I do feel like this would possibly complicate the API. I'm still thinking about it.

deckarep avatar Aug 04 '24 20:08 deckarep

The safest IMO would be to implement a new interface entirely with this functionality. I for one would prefer the existing behavior unchanged by default, but this functionality could also be useful if implemented in a generic way. Something like NewSetWithComparator that accepts a user-defined function for determining equality along with the usual list of initial items?

This would definitely be a larger effort, and might be best suited to a sub-package instead of the main one to keep the changes contained in a non-breaking way

ryclarke avatar Aug 15 '25 16:08 ryclarke

The main challenge operationally would be that deviating from Golang's native equality check renders direct map key references no longer useful. The set would either need to iterate through elements and check them (which doesn't scale) or implement a form of hash keying (which greatly adds to complexity), neither of which is ideal when compared to the existing implementation

ryclarke avatar Aug 15 '25 16:08 ryclarke