Abion47 / darq

A port of .NET's LINQ IEnumerable functions to Dart.
MIT License
85 stars 9 forks source link

.groupBy makes multiple groups for the same value, under certain conditions #13

Closed Brads3290 closed 2 years ago

Brads3290 commented 2 years ago

Strange one, had me ripping my hair out for a while.

Take this example:

void main() {
  var ls = [
    'ZZ', 'ZZ',   // Group 0

    'AA', 'AA',   // Group 1
    'AB',         // Group 2
    'AC',         // Group 3
    'AD',         // Group 4
    'AE',         // Group 5
    'AF',         // Group 6
    'AG',         // Group 7

    'ZZ', 'ZZ',   // Should be part of group 0
  ];

  var groups = ls.groupBy((e) => e).select((e, i) => '[$i] ${e.key}: [${e.join(', ')}]').toList();
  print(groups.join('\n'));
}

Expected output:

[0] ZZ: [ZZ, ZZ, ZZ, ZZ]
[1] AA: [AA, AA]
[2] AB: [AB]
[3] AC: [AC]
[4] AD: [AD]
[5] AE: [AE]
[6] AF: [AF]
[7] AG: [AG]

Actual output:

[0] ZZ: [ZZ, ZZ]
[1] AA: [AA, AA]
[2] AB: [AB]
[3] AC: [AC]
[4] AD: [AD]
[5] AE: [AE]
[6] AF: [AF]
[7] AG: [AG]
[8] ZZ: [ZZ, ZZ]

For some reason, groupBy is splitting ZZ into two groups. I've been playing around with this example and this seems to only happen when there are 7 or more distinct values placed between the ZZ values. Not sure what the significance of 7 is, but if I take any of those groups in the middle away, it correctly groups ZZ again.

This seems to happen with any values too; I originally noticed the issue with a long list of ISO date strings in my application and simplified it down to this example.

Any thoughts?

Brads3290 commented 2 years ago

Note: A workaround for now is to sort an iterable before grouping, because then there are no values between groups. Might be a performance issue in some circumstances though.

Brads3290 commented 2 years ago

Workaround as an extension method.

extension DarqExtension<T> on Iterable<T> {
  GroupByIterable<T, TKey> groupByHotfix<TKey>(
    TKey Function(T element) keySelector, {
    EqualityComparer<TKey>? keyComparer,
  }) {
    return this
        .orderBy((e) => keySelector(e), keyComparer: keyComparer)
        .groupBy((e) => keySelector(e), keyComparer: keyComparer);
  }
}

Note: This executes keySelector for both orderBy and groupBy.