Skip to content

Instantly share code, notes, and snippets.

Created October 20, 2015 18:51
Show Gist options
  • Save anonymous/e21e115895fb3b46bbc7 to your computer and use it in GitHub Desktop.
Save anonymous/e21e115895fb3b46bbc7 to your computer and use it in GitHub Desktop.
Grouped items
// This algorithm works so that the items in the beginning of the list
// have an older date than the items at the end of the list.
// I.e., the items in the list are ordered the same way as they would
// be visually shown on the UI (older items at the top, recent at the bottom).
main() => new Page();
class Item {
String user;
String message;
String type;
DateTime createdDate;
Item(this.user, this.message, this.createdDate, {this.type: 'regular'});
String toString() => '${user}: ${message} (${createdDate.toString()}) – $type';
}
enum TargetGroup {
Above, Below, Same, New
}
class Page {
List<ItemGroup> groups = [];
Page() {
// Initial items coming in!
var someItems = [
new Item('kai', 'Kai 5', new DateTime(2015, 6, 1), type: 'notification'),
new Item('kai', 'Kai 6', new DateTime(2015, 6, 2)),
new Item('dave', 'Dave 4', new DateTime(2015, 6, 3)),
new Item('kai', 'Kai 7', new DateTime(2015, 6, 4)),
new Item('kai', 'Kai EARLIER', new DateTime(2015, 6, 10, 12, 10)),
new Item('dave', 'Dave 5', new DateTime(2015, 6, 5)),
new Item('dave', 'Dave 6', new DateTime(2015, 6, 6)),
new Item('kai', 'Kai LATER', new DateTime(2015, 6, 10, 12, 12)),
new Item('dave', 'Dave 7', new DateTime(2015, 6, 7)),
new Item('dave', 'Dave 8', new DateTime(2015, 6, 8)),
new Item('kai', 'Kai 8', new DateTime(2015, 6, 7))
];
processAll(someItems);
// Later we get more items!
var someMoreItems = [
new Item('kai', 'Kai 1', new DateTime(2015, 5, 1)),
new Item('kai', 'Kai 2', new DateTime(2015, 5, 2)),
new Item('dave', 'Dave 1', new DateTime(2015, 5, 3)),
new Item('kai', 'Kai 3', new DateTime(2015, 5, 4)),
new Item('dave', 'Dave 2', new DateTime(2015, 5, 5)),
new Item('dave', 'Dave 3', new DateTime(2015, 5, 6)),
new Item('dave', 'Dave much later posted than the above', new DateTime(2015, 5, 8)),
new Item('kai', 'Kai 4', new DateTime(2015, 5, 9))
];
processAll(someMoreItems);
printItems();
}
void processAll(List<Item> items) {
items.forEach(process);
}
void process(Item item) {
// Retrieve the group within this item belongs to, if any.
var group = groups.firstWhere((group) => group.isDateWithin(item.createdDate), orElse: () => null);
if (group != null) {
TargetGroup target = group.determineTargetGroup(item);
if (target == TargetGroup.Same) {
group.put(item);
} else {
var groupIndex = groups.indexOf(group);
var intersection = group.indexOf(item);
var topHalf = group.items.sublist(0, intersection);
var bottomHalf = group.items.sublist(intersection);
groups.remove(group); // Get rid of the old group, we need to split this thing!
if (target == TargetGroup.New) {
groups.insert(groupIndex, new ItemGroup.fromItems(bottomHalf));
groups.insert(groupIndex, new ItemGroup(item));
groups.insert(groupIndex, new ItemGroup.fromItems(topHalf));
} else if (target == TargetGroup.Above) {
topHalf.add(item);
groups.insert(groupIndex, new ItemGroup.fromItems(bottomHalf));
groups.insert(groupIndex, new ItemGroup.fromItems(topHalf));
} else if (target == TargetGroup.Below) {
bottomHalf.insert(0, item);
groups.insert(groupIndex, new ItemGroup.fromItems(bottomHalf));
groups.insert(groupIndex, new ItemGroup.fromItems(topHalf));
} else {
throw 'Unknown target group!';
}
}
} else {
// Okay, so the item is not within ANY group.
// This leaves a few options:
// 1) The item belongs to the top or bottom position of some group (i.e. not within).
// 2) The item needs its own new group.
// Fetch the first groups that are after/before this item. i.e. surrounding the item.
var groupBefore = groups.reversed.firstWhere((group) => item.createdDate.isAfter(group.getLatestDate()), orElse: () => null);
var groupAfter = groups.firstWhere((group) => item.createdDate.isBefore(group.getOldestDate()), orElse: () => null);
if (groupBefore == null && groupAfter == null) {
// No groups at all!
groups.add(new ItemGroup(item));
} else if (groupBefore != null && groupAfter != null) {
if (groupBefore.determineTargetGroup(item) == TargetGroup.New && groupAfter.determineTargetGroup(item) == TargetGroup.New) {
// The item does not belong in either groups,
// so it has to go in between them in its own group.
var index = groups.indexOf(groupAfter);
groups.insert(index, new ItemGroup(item));
} else if (groupBefore.determineTargetGroup(item) == TargetGroup.Same) {
// We do not belong to groupAfter, but we belong to groupBefore!
groupBefore.put(item);
} else {
// We belong to groupAfter.
groupAfter.put(item);
}
} else if (groupBefore == null) {
// There was no group before this item, but one after.
// Thus if we belong to groupAfter, let's go there,
// otherwise we need a new group at the top.
if (groupAfter.determineTargetGroup(item) == TargetGroup.Same) {
groupAfter.put(item);
} else {
groups.insert(0, new ItemGroup(item));
}
} else {
// There was no group after this item, but one before.
// Same as earlier, let's put it there or in a new group at the bottom.
if (groupBefore.determineTargetGroup(item) == TargetGroup.Same) {
groupBefore.put(item);
} else {
groups.add(new ItemGroup(item));
}
}
}
}
void printItems() {
groups.forEach((group) {
print('GROUP ${group.user}');
group.items.forEach(print);
print('---');
});
}
}
class ItemGroup {
String user;
String type;
List<Item> items = [];
ItemGroup(Item item) {
user = item.user;
type = item.type;
items.add(item);
}
ItemGroup.fromItems(List<Item> list) {
user = list.first.user;
items.addAll(list);
}
DateTime getOldestDate() => items.first.createdDate;
DateTime getLatestDate() => items.last.createdDate;
/**
* Returns true when the given [DateTime] is in between
* the oldest and the latest item in this group.
*/
bool isDateWithin(DateTime date) => date.isAfter(getOldestDate()) && date.isBefore(getLatestDate());
/**
* Returns the index of where the given item should go to in this group.
*/
int indexOf(Item item) {
for (var i = 0; i < items.length; i++) {
if (item.createdDate.isBefore(items[i].createdDate)) {
return i;
}
}
return items.length;
}
/**
* Puts the given item into this group at the right position.
*/
void put(Item item) {
items.insert(indexOf(item), item);
}
/**
* Returns TargetGroup, which specifies where the item belongs to.
*/
TargetGroup determineTargetGroup(Item item) {
var index = indexOf(item);
var lastIndex = items.length - 1;
bool farFromAboveItem = index > 0 && items[index - 1].createdDate.difference(item.createdDate).inSeconds.abs() > 86400;
bool farFromBelowItem = index < lastIndex && items[index + 1].createdDate.difference(item.createdDate).inSeconds.abs() > 86400;
if (item.user != user || item.type == 'notification' || this.isNotification) {
return TargetGroup.New;
}
if (!farFromAboveItem && !farFromBelowItem) {
return TargetGroup.Same;
}
if (!farFromAboveItem && farFromBelowItem) {
return TargetGroup.Above;
}
if (!farFromAboveItem && !farFromBelowItem) {
return TargetGroup.Below;
}
return TargetGroup.New;
}
bool get isNotification => items.first.type == 'notification';
String get usernameForDisplay => items.first.user;
DateTime get lastCreatedDate => items.last.createdDate;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment