Flutter - Sort Comment Replies under their parent comment - firebase

I'm trying to sort all of my comments so that the replies will be threaded under their parent comment, going from oldest to newest in both the original comment along with their replies.
Comments will have an id, a date, and an optional parentId.
Parent Comments will not have a parentId, whereas the replies will have a parentId that is the id of their parent comment.
I'm getting my data from Firebase and adding it to a List.
From that List, I am separating out the comments that contain a parentId and those that do not.
final commentsWithoutParentID = comments
.where(
(c) => c.parentId.isEmpty,
).toList();
final commentsWithParentID = comments
.where(
(c) => c.parentId.isNotEmpty,
).toList();
I then sort these as:
// Sort comments with no parent id by date
commentsWithoutParentID.sort((a, b) => a.date!.compareTo(b.date!));
// Sort comments with a parentId by date
commentsWithParentID.sort((a, b) => a.date!.compareTo(b.date!));
// Sort comments with a parentId by comparing the various parentId
commentsWithParentID.sort((a, b) => a.parentId.compareTo(b.parentId));
After this I add these two Lists to an empty List:
sortedComments = commentsWithoutParentID + commentsWithParentID;
I then sort this to compare the parentId to the id:
sortedComments.sort((a, b) => a.parentId.compareTo(b.id));
The result I am getting is that all of the comments are sorted by date from oldest at the top to newest at the bottom and some of the replies get threaded under the incorrect parent comment.
So it seems that the date is taking a higher priority possibly.
I've altered these sorts but I either get the same result as I am getting now or something that is further from my desired result.

When you call sort after other sort, you break previous.
Consider group method (dart:collection dependency is required).
Or you can use temporary map to group children.
import 'package:collection/collection.dart';
class Comment {
final String date;
final int id;
Comment(this.date, this.id);
#override
String toString() => "($date, $id)";
}
class ThreadComment extends Comment {
final int parentId;
ThreadComment(String date, int id, this.parentId) : super(date, id);
#override
String toString() => "(parentId: $parentId, $date, $id)";
}
final threadComments = [
ThreadComment("day1", 1, 1),
ThreadComment("day2", 4, 1),
ThreadComment("day2", 3, 3),
ThreadComment("day1", 2, 3),
];
final parentComments = [
Comment("day3", 5),
Comment("day1", 1),
Comment("day2", 3),
Comment("day1", 2),
Comment("day3", 4),
];
int compare(v1, v2) => v1.date.compareTo(v2.date);
final comments = <Comment, List<ThreadComment>>{};
// group comments by parent id
for (final Comment c
in parentComments.sorted((a, b) => a.id.compareTo(b.id))) {
comments[c] = threadComments
.where((element) => element.parentId == c.id)
// sort children by date
.sorted(compare);
}
// sort parents by date
final sortedMap = comments.keys
.toList()
.sorted(compare)
.asMap()
.map((_, v) => MapEntry(v, comments[v]));
//expand map to one list
final sortedList = <List<Comment>>[
for (final parent in sortedMap.keys) [parent, ...?sortedMap[parent]]
].expand((element) => element).toList();
sortedList.forEach((element) => print("$element\n"));
// (day1, 1)
// (parentId: 1, day1, 1)
// (parentId: 1, day2, 4)
// (day1, 2)
// (day2, 3)
// (parentId: 3, day1, 2)
// (parentId: 3, day2, 3)
// (day3, 4)
// (day3, 5)

Related

Updating map value for a specific key in Kotlin

I am new at Kotlin and trying to catch up the language.
I have a function that returns DayofWeek as a key and an Int as a value.
The issue that I am facing is that I need to take a list of object that has another list of object inside that has an Int value which I need to save and increment for every time I see the same value.
Here is my function -
class OrdersAnalyzer {
data class Order(val orderId: Int, val creationDate: LocalDateTime, val orderLines: List<OrderLine>)
data class OrderLine(val productId: Int, val name: String, val quantity: Int, val unitPrice: BigDecimal)
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> {
val map: MutableMap<DayOfWeek, Int>? = mutableMapOf(
Pair(DayOfWeek.SUNDAY, 0),
Pair(DayOfWeek.MONDAY, 0),
Pair(DayOfWeek.TUESDAY, 0),
Pair(DayOfWeek.WEDNESDAY, 0),
Pair(DayOfWeek.THURSDAY, 0),
Pair(DayOfWeek.FRIDAY, 0),
Pair(DayOfWeek.SATURDAY, 0)
)
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
var quantity = 0
map?.put(dayOfWeek, quantity)
}
return map!!
}
}
So the issues I am facing right now are 2 -
1) How can I increment the value of each pair when it is the corrent DayOfWeek? I don't want to replace, I want to add it to the last value.
2) When returning the Map, I do not want to return the DayOfWeeks that have the value of 0. How can I do that?
Here is a modified version of the elegant answer provided by Arjan, with some test code.
Updated the answer after the comment from Alon Shlider -- now counting all order item quantities grouped by day of the week:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { sumItemQuantities(it.value) }
fun sumItemQuantities(orders: List<Order>) =
orders.flatMap { it.orderLines.map { line -> line.quantity } }.sum()
fun main() {
val orders = listOf(
Order(
1,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(6, "laptop", 28, 1200.toBigDecimal())
)
),
Order(
2,
LocalDateTime.now().minusDays(1),
listOf(
OrderLine(496, "VR headset", 6, 400.toBigDecimal())
)
)
)
println(totalDailySales(orders))
}
Output:
{FRIDAY=28, SATURDAY=6}
With this approach, Kotlin functions do the grouping and counting for you. The groupBy function creates a map from DayOfWeek to a list of orders (grouping all orders with the same day of week in a list). The mapValues function transforms that map by replacing the lists with the result of the sumItemQuantities function (for each list).
In the for loop in your code, you can retrieve the current quantity for a specific day (or use zero if it isn't set yet), increase it by the right amount and then store it. To return only the map entries with non zero values, you could filter (return totalsPerDay.filter { it.value > 0 }) or start with an empty map. This is your function with some changes:
fun totalDailySales(orders: List<Order>): Map<DayOfWeek, Int> {
val totalsPerDay = mutableMapOf<DayOfWeek, Int>()
for (order in orders) {
val dayOfWeek = order.creationDate.dayOfWeek
val currentQuantity = totalsPerDay[dayOfWeek] ?: 0
// This is not the best way to increment by the sum of the order
// item quantities...
val orderItemQuantities = sumItemQuantities(listOf(order))
totalsPerDay[dayOfWeek] = currentQuantity + orderItemQuantities
}
return totalsPerDay
}
Output after calling it:
println(OrdersAnalyzer().totalDailySales(orders))
{FRIDAY=28, SATURDAY=6}
Updated answer (also thanks to Freek de Bruijn), with some test code.
I think it would be something like this:
fun totalDailySales(orders: List<Order>) : Map<DayOfWeek, Int> =
orders.groupBy { it.creationDate.dayOfWeek }
.mapValues { it.value.flatMap { it.orderLines } }
.mapValues { it.value.map { it.quantity } }
.mapValues { it.value.sum() }
groupBy creates a Map where the values are of type List<Order>, so you need to call a few steps to convert these values to Int. First we use flatMap to convert List<Order> to List<OrderLine> (map would convert to List<List<OrderLine>>). Then we use map to get the quantities out of List<OrderLine>, and finally sum() to add up all those quantities.
val orders = listOf(
Order(
2,
LocalDateTime.now().minusDays(2),
listOf(
OrderLine(5, "monitor", 10, 200.toBigDecimal()),
OrderLine(4, "keyboard", 5, 50.toBigDecimal())
)
)
)
println(totalDailySales(orders))
This results in the output:
{FRIDAY=15}

Get minimum value from list

I have a class ABC like this
public class ABC{
public int Id {get;set;}
public int UserCount {get;set;}
}
Now I add following records to a list of type ABC
List<ABC> lstABC = new List<ABC>();
lstABC.Add(new ABC(){Id=1,UserCount=5});
lstABC.Add(new ABC(){Id=2,UserCount=15});
lstABC.Add(new ABC(){Id=3,UserCount=3});
lstABC.Add(new ABC(){Id=4,UserCount=20});
I've another list of type int
List<int> lstIds = new List<int>();
lstIds.Add(1);
lstIds.Add(3);
lstIds.Add(4);
Now i want to find out the minimum value of UserCount by comparing both the list where Id in lstABC should match the items presesnt in lstIds. I can get the minimum value of UserCount by using loops but is there any another way to get the value in an optimized way by avoiding loops?
You can use Enumerable.Join to link both lists:
var joined = from id in lstIds
join x in lstABC
on id equals x.Id
select x;
int minValue = joined.Min(x => x.UserCount);
This is more efficient than loops since Join uses a set to find matching items.
There's more than one way to skin a cat, this is a little bit less efficient:
int minValue = lstABC.Where(x => lstIds.Contains(x.ID)).Min(x => x.UserCount);
Try below code:
int min = lstABC.Min(entry => entry.UserCount);
var a = lstABC.Where(e => e.UserCount == min).FirstOrDefault();
var resul = lstIds.Where(e => e.Equals(a.Id)).FirstOrDefault();
var result = lstABC.Where(n =>n.Id == resul).FirstOrDefault();

Linq to Entity, selecting group with or without value

Hi Need some help with LINQ query.
I have entity called Shift. This entity has several value field but the ones I am intressted in are ShiftID (int), ShiftDate (DateTime) and GrossMount (decimal(10,2). And this needs to be grouped by month (binding this to a graph in ASP.NET).
I need data for the last 12 months grouped by month.
I have come a bit on the way with this post: Linq to Entity, selecting group without value but not quite all the way.
This is my code for now:
public IQueryable<Shift> GetPastMonths(int months, string accountNumber)
{
_context = new EtaxiEnteties();
var _date = DateTime.Today;
var _firstOfMonth = new DateTime(_date.Year, _date.Month, 31);
var _twelveMonthAgoFirstOfMonth = _firstOfMonth.AddMonths(-12);
// Generate a collection of the months and years for the last 12 months
var _monthYears = Enumerable.Range(-12, 12).Select(monthOffset => { var monthDate = _firstOfMonth.AddMonths(monthOffset); return new { y = monthDate.Year, m = monthDate.Month }; });
var _data = (from _monthYear in _monthYears
join _i in
(from _i in _context.Shifts.Where(acc => acc.Account.AccountNumber == accountNumber)
where _i.ShiftDate >= _twelveMonthAgoFirstOfMonth && _i.ShiftDate < _firstOfMonth
group _i by new { y = _i.ShiftDate.Year, m = _i.ShiftDate.Month } into g
select new { ShiftID = g.Key, GrossAmount = g.Count() }) on _monthYear equals _i.ShiftID into j
from _k in j.DefaultIfEmpty()
select new Shift() { ShiftDate = new DateTime(_monthYear.y, _monthYear.m, 1), GrossAmount = _k != null ? _k.GrossAmount : 0 });
return _data as IQueryable<Shift>;
}
Now I have in return a collection of Shift objects, grouped by month but still missing the GrossAmount. Althoug i would need this from today date (only getting from 1 of current month).
Believe this is my main problem: GrossAmount = g.Count(), but I am not sure
Any LINQ specialist out there that could give me a push?
Use GrossAmount = g.Sum(x => x.GrossAmount) to calculate total GrossAmount value of grouped Shift entities. I believe you have typo in GrossAmount (GrossMount) property name.

Entity Framework query syntax

I having a trouble with a query
I need to take out the SHIPMENT from GetAllOrderData - the same place where you can find POD_DATE and RECEIVE_NAME...but I get an error
Error 1 The name 'x' does not exist in the current context
My code is:
public IEnumerable<ReportItemDTO> GetTaskProgress(DateTime targetDate)
{
try
{
var startDate = targetDate.Date;
var endDate = startDate.AddDays(1);
OrderDataRepository rep = new OrderDataRepository();
var query = rep.GetAllOrderData()
.Where(x => x.POD_DATE >= startDate && x.POD_DATE <= endDate)
.GroupBy(o => o.User)
.Select(g => new ReportItemDTO
{
DriverId = g.Key.Id,
PdriverName = g.Key.Name,
OrderCount = g.Count(),
ReportedOrdersCount = g.Count(o => o.RECEIVE_NAME != null),
SHIPMENT = (x.SHIPMENT)
} );
return query;
SHIPMENT = (x.SHIPMENT)
Well you are within a grouping when you try to make that assignment - there are many shipments in each grouping not just one - in fact all shipments for that particular user. Assuming you want a collection of them you could do:
Shipments = g.Select( x=> x.SHIPMENT)
Edit:
If you just want the first shipment for each user (somewhat illogical but fits your data model):
SHIPMENT = g.Select( x=> x.SHIPMENT).First()

How to do Previous and Next item on a List<G>

I have a class called Person, which contains various properties such as first name, last name, etc.
I have a List that contains n instances of Person.
List<Person> lstPerson = new List<Person>();
What I need to be able to do is search lstPerson for a given person, and having found that person then obtain the previous and next person relative to the person's item position in lstPerson.
Person p = lstPerson.Find(delegate(Person o) { return o.FirstName == "Joe"; });
// Then use an accessor to get the previous and next persons
Person prevPerson = p.Previous();
Person nextPerson = p.Next();
Is there a better way to do this then the one that I stated above? What I don't like about the above solution is that I have to construct the previous and next pointers at the time that I build the Person List.
You could do something like the following:
int personIndex = lstPerson.FindIndex(delegate(Person o) { return o.FirstName == "Joe" });
Person p = lstPerson[personIndex];
// verify that personIndex+1 and personIndex-1 still fall within the list
Person prevPerson = lstPerson[personIndex-1];
Person nextPerson = lstPerson[personIndex+1];
Hmm.. why not simply using the LinkedList class?
It comes with Next and Previous built in.
You could do this, which skips elements until the next element matches or it has gone through the entire list:
var list = (new List<Person>() { null }).Concat(lstPerson);
var p = list.SkipWhile((x, index) => { var n = list.ElementAtOrDefault(index + 1); return n == null || n.FirstName != name; }).Take(3);
Person previous = p.FirstOrDefault();
Person person = p.ElementAtOrDefault(1);
Person next = p.ElementAtOrDefault(2);
Although this (similar to #marcind's answer) might be clearer:
Person person = lstPerson.Find(p => p.FirstName == name);
Person previous = null;
Person next = null;
if (person != null)
{
int index = lstPerson.IndexOf(person);
previous = lstPerson.ElementAtOrDefault(index - 1);
next = lstPerson.ElementAtOrDefault(index + 1);
}

Resources