Grouping By bool produces wrong result - c#

I am trying to visualize data from database in charts using LiveCharts library. I have managed to get some of them working fine. However I am having hard times with PieCharts. I want to simply display data in two slices. For that matter I have column in DB with name AppIsRunning.
I have made a simple example that is working fine and displaying data in slices as expected:
List<DataModel> records = new List<DataModel>();
records.Add(new DataModel { Id = 1, Revenue = 43, Name = "Item 1", AppIsRunning = true });
records.Add(new DataModel { Id = 2, Revenue = 23, Name = "Item 2", AppIsRunning = true });
records.Add(new DataModel { Id = 3, Revenue = 13, Name = "Item 3", AppIsRunning = true });
records.Add(new DataModel { Id = 4, Revenue = 87, Name = "Item 4", AppIsRunning = true });
records.Add(new DataModel { Id = 5, Revenue = 23, Name = "Item 5", AppIsRunning = true });
IEnumerable<ISeries> result1 = records.Select(x =>
new PieSeries<double>
{
Values = new List<double> { x.Revenue },
Name = x.Name,
});
Now I would like to group data to display only 2 slices AppIsRunning true/false and for Values have Revenue. So I have ended up with this code:
List<DataModel> records = new List<DataModel>();
records.Add(new DataModel { Id = 1, Revenue = 43, Name = "Item 1", AppIsRunning = true });
records.Add(new DataModel { Id = 2, Revenue = 23, Name = "Item 2", AppIsRunning = true });
records.Add(new DataModel { Id = 3, Revenue = 13, Name = "Item 3", AppIsRunning = false });
records.Add(new DataModel { Id = 4, Revenue = 87, Name = "Item 4", AppIsRunning = true });
records.Add(new DataModel { Id = 5, Revenue = 23, Name = "Item 5", AppIsRunning = true });
IEnumerable<ISeries> result1 = records
.GroupBy(g => g.AppIsRunning)
.Select(item => new PieSeries<double>
{
Values = item.Select(x => Convert.ToDouble(x.Revenue)),
Name = item.Key ? "Running" : "Not running",
});
However this makes AppIsRunning true section divided into 4 sub-slices = 4 AppIsRunning TRUE values. Please see screenshot with 4 sub-sections in blue slice:
My question is how to get rid of these 4 sub-sections and group that data into one? I need only 2 slices, no need to divide slices into sub-sections.
Here is original example from LiveCharts:
this.ActivityChartSeries = new ISeries[]
{
new PieSeries<double> { Values = new double[] { 2 }, Name = "Section 1"},
new PieSeries<double> { Values = new double[] { 21 }, Name = "Section 2"},
new PieSeries<double> { Values = new double[] { 28 }, Name = "Section 3"},
new PieSeries<double> { Values = new double[] { 2 }, Name = "Section 4"},
new PieSeries<double> { Values = new double[] { 52 }, Name = "Section 5"},
};
https://github.com/beto-rodriguez/LiveCharts2/blob/master/samples/ViewModelsSamples/Pies/Basic/ViewModel.cs

Judging from the example, you want to see a breakdown of total (sum of) revenue as a single value for each status. To do that, you could do:
IEnumerable<ISeries> result1 = records
.GroupBy(g => g.AppIsRunning)
.Select(item => new PieSeries<double>
{
Values = new List<double> {item.Sum(x => Convert.ToDouble(x.Revenue))},
Name = item.Key ? "Running" : "Not running",
});

Related

For-loop within "new"-operator

I am looking for a way to interactively create a List-Object with a flexible number of elements. At the moment, i have a fixed number of elements; i can create the Object like this:
private List<DropItem> _items = new()
{
new DropItem(){ Name = "Cam 0", Identifier = "Drop Zone 1", cam_idx = 0 },
new DropItem(){ Name = "Cam 1", Identifier = "Drop Zone 1", cam_idx = 1 },
new DropItem(){ Name = "Cam 2", Identifier = "Drop Zone 1", cam_idx = 2 },
new DropItem(){ Name = "Cam 3", Identifier = "Drop Zone 1", cam_idx = 3 },
new DropItem(){ Name = "Cam 4", Identifier = "Drop Zone 2", cam_idx = 4 },
new DropItem(){ Name = "Cam 5", Identifier = "Drop Zone 2", cam_idx = 5 },
new DropItem(){ Name = "Cam 6", Identifier = "Drop Zone 2", cam_idx = 6 },
new DropItem(){ Name = "Cam 7", Identifier = "Drop Zone 2", cam_idx = 7 },
};
Is there a way to directly construct such an object with an abitrary number of elements? (a trivial attempt to write a loop within the new() - construction did unsurprisingly not work)
Enumerable.Range() should help you here
int count = 10;
List<DropItem> _items = Enumerable.Range(0, count).Select(x => new DropItem()
{
Name = "Cam " + x,
Identifier = "Drop Zone" + ((x < count / 2) ? 1 : 2),
cam_idx = x
}).ToList();
https://dotnetfiddle.net/ePpi5T
Use linq and Enumerable for this purpose:
Enumerable.Range(1, 12) // 12 cand be other number
.Select(x =>
new DropItem
{
Name = "Cam " + x,
cam_idx = x
... // whatever you want
}).ToList();
int itemsCount = 8;
for (int i = 0; i < itemsCount; i++)
{
_items.Add(new DropItem() { Name = $"Cam {i}", Identifier = $"Drop Zone {i / 4 + 1}", cam_idx = i});
}

How to make one collection using three collections?

I want to make one collection of three according to certain conditions (with LINQ). let's say I have a Class:
class Collection
{
public int id;
public string name;
public double weight;
}
Then I'm creating collections:
List<Collection> collection1 = new()
{
new Collection() { id = 11, name = "Abraham 1", weight = 1.1 },
new Collection() { id = 12, name = "Abraham 2", weight = 1.2 },
new Collection() { id = 13, name = "Abraham 3", weight = 1.3 },
};
List<Collection> collection2 = new()
{
new Collection() { id = 21, name = "Bill 1", weight = 2.1 },
new Collection() { id = 22, name = "Bill 2", weight = 2.2 },
new Collection() { id = 23, name = "Bill 3", weight = 2.3 },
};
List<Collection> collection3 = new()
{
new Collection() { id = 31, name = "David 1", weight = 3.1 },
new Collection() { id = 32, name = "David 2", weight = 3.2 },
new Collection() { id = 33, name = "David 3", weight = 3.3 },
};
TODO 1. Condition: get 1st column from 1st collection, 2nd column from 2nd collection, 3rd column from 3rd column. result should be:
{
new Collection() { id = 11, name = "Bill 1", weight = 3.1 },
new Collection() { id = 12, name = "Bill 2", weight = 3.2 },
new Collection() { id = 13, name = "Bill 3", weight = 3.1 }
}
TODO 2. Second case condition: get first elements from columns of all collections. result should be:
{
new Collection() { id = 11, name = "Abraham 1", weight = 1.1 },
new Collection() { id = 21, name = "Bill 1", weight = 2.1 },
new Collection() { id = 31, name = "David 1", weight = 3.1 }
}
Please help.
Using C# 10 and .Net 6, just use Enumerable.Zip:
var todo1 = collection1.Zip(collection2, collection3)
.Select(t => new Collection { id = t.First.id, name = t.Second.name, weight = t.Third.weight })
.ToList();
var todo2 = collection1.Zip(collection2, collection3)
.Select(t => new List<Collection> { t.First, t.Second, t.Third })
.First();
If you don't have the three argument Zip that returns a tuple, just roll your own, e.g. for Todo1:
var td1 = collection1.Zip(collection2, (c1, c2) => (c1, c2))
.Zip(collection3, (t12, c3) => (First: t12.c1, Second: t12.c2, Third: c3))
.Select(t => new Collection { id = t.First.id, name = t.Second.name, weight = t.Third.weight })
.ToList();
If you want to merge multiple collections into one collection using LINQ then do this:
List<Collection> result = collection1.Concat(collection2).OrderBy(x => x.id).ToList();

Separate a list of objects into multiple lists that only contain unique items

I have a requirement in a C# application, that needs to send a list of applications to an api. the api can take multiple application in one call but there can only be one application per person in the api call. if a person has more than 1 application in the list it must be removed and sent in it's own call to the api.
The ID is what will be unique for each "person" and what is in the Data does not matter, bob could have 3 applications with data set to "A" and it would still be valid for this example.
here is a basic example of what I mean
I need to go from this, a single list with all applications:
{
[
{name: 'Bob', id: 1, data: 'A'},
{name: 'Jim', id: 2, data: 'A'},
{name: 'Sam', id: 3, data: 'A'},
{name: 'Bob', id: 1, data: 'B'},
{name: 'Bob', id: 1, data: 'C'},
{name: 'Sam', id: 3, data: 'B'},
{name: 'Bob', id: 4, data: 'Notice this is a different Bob'}
]
}
to this, multiple lists that only have one application per person, i.e. bob is in all the lists because he has 3 applications, sam is in 2 of them and jim is just in one.:
{
[
{name: 'Bob', id: 1, data: 'A'},
{name: 'Jim', id: 2, data: 'A'},
{name: 'Sam', id: 3, data: 'A'},
{name: 'Bob', id: 4, data: 'Notice this is a different Bob'}
],
[
{name: 'Bob', id: 1, data: 'B'},
{name: 'Sam', id: 3, data: 'B'}
],
[
{name: 'Bob', id: 1, data: 'C'}
]
}
I'm trying to find an efficient way of doing this without needing to create nested loops.
Update Edit 2
Playing around in LinqPad using the default DemoDB it comes with an using the orders table for test data using the EmployeeID, the following seems to work:
// make a copy of the orders table so as not to delete any actual data
List<Orders> lst = Orders.OrderBy(x => x.OrderID).ToList();
int x = 0; // only used as a counter for the console output
while (lst.Any()) {
Console.WriteLine($"List has {lst.Count()} orders");
List<Orders> lst1 = lst.OrderBy(x => x.OrderID).GroupBy(x => x.EmployeeID).Select(x => x.FirstOrDefault()).ToList();
Console.WriteLine($"List {x++} has {lst1.Count()} orders");
lst = lst.Where(x => !lst1.Contains(x)).ToList();
Console.Write(lst1);
}
Can anyone improve on this performance wise?
You basically want to group all your items by id and then transpose the groupings.
So (when only considering the ids) the list [1, 2, 3, 1, 1, 3, 4] is turned into a list of groupings:
[
[1, 1, 1],
[2],
[3, 3],
[4]
]
Which then is transposed (rows become columns and vice versa):
[
[1, 2, 3, 4],
[1, 3 ],
[1 ]
]
With the MoreLINQ package you can do that easily:
using System.Linq; // for GroupBy()
using MoreLinq; // for Transpose()
// ...
var result = new[] {
new { name = "Bob", id = 1, data = "A" },
new { name = "Jim", id = 2, data = "A" },
new { name = "Sam", id = 3, data = "A" },
new { name = "Bob", id = 1, data = "B" },
new { name = "Bob", id = 1, data = "C" },
new { name = "Sam", id = 3, data = "B" },
new { name = "Bob", id = 4, data = "Notice this is a different Bob" }
}
.GroupBy(x => x.id)
.Transpose();
Working example:
{id: 1, name: Bob, data: A}, {id: 2, name: Jim, data: A}, {id: 3, name: Sam, data: A}, {id: 4, name: Bob, data: Notice this is a different Bob}
{id: 1, name: Bob, data: B}, {id: 3, name: Sam, data: B}
{id: 1, name: Bob, data: C}
The performance should be good enough. An optimized implementation without LINQ is always faster, but big O complexity will be the same anyway. IMHO clarity and brevity clearly beat a micro-optimized implementation in this case.
Similar to GoodNightNerdPride's solution and RandRandom's solution, but without using an external dependency, and without using LINQ with side-effects:
UserApplication[] source = new[]
{
new UserApplication() { Id = 1, Name = "Bob", Data = "A"},
new UserApplication() { Id = 2, Name = "Jim", Data = "A"},
new UserApplication() { Id = 3, Name = "Sam", Data = "A"},
new UserApplication() { Id = 1, Name = "Bob", Data = "B"},
new UserApplication() { Id = 1, Name = "Bob", Data = "C"},
new UserApplication() { Id = 3, Name = "Sam", Data = "B"},
new UserApplication() { Id = 4, Name = "Bob", Data = "A"} // Different Bob
};
List<UserApplication>[] lists = source
.GroupBy(x => x.Id)
.SelectMany(g => g.Select((x, i) => (Item: x, Ordinal: i)))
.GroupBy(entry => entry.Ordinal)
.Select(g => g.Select(entry => entry.Item).ToList())
.ToArray();
foreach (var list in lists)
{
Console.WriteLine(String.Join(", ",
list.Select(a => $"(#{a.Id} {a.Name}: {a.Data})")));
}
The first GroupBy groups the applications by Id, and then an Ordinal is assigned to the applications that are grouped together. Then the groups are flattened, and a second GroupBy groups again the applications by Ordinal. Finally the Ordinal is discarded.
Output:
(#1 Bob: A), (#2 Jim: A), (#3 Sam: A), (#4 Bob: A)
(#1 Bob: B), (#3 Sam: B)
(#1 Bob: C)
The UserApplication class used in the example:
class UserApplication
{
public int Id { get; set; }
public string Name { get; set; }
public string Data { get; set; }
}
Try this
var array = new List<Example>
{
new Example{ Name = "Bob", Id = 1, Data = "A" },
new Example{ Name = "Jim", Id = 2, Data = "A" },
new Example{ Name = "Sam", Id = 3, Data = "A" },
new Example{ Name = "Bob", Id = 1, Data = "B" },
new Example{ Name = "Bob", Id = 1, Data = "C" },
new Example{ Name = "Sam", Id = 3, Data = "B" },
new Example{ Name = "Bob", Id = 4, Data = "Notice this is a different Bob" },
};
var dict = new Dictionary<int, int>();
var x = array.GroupBy(x =>
{
dict.TryGetValue(x.Id, out var value);
dict[x.Id] = ++value;
return value;
}).ToList();
If I am not mistaken, it is basically #Amadan 's idea.
Make a Dictionary of names to integers. For each application, see how many applications that person you saw so far (0 if not yet there); insert that application in that row of the result (creating the row if it doesn't yet exist) (e.g. if count of Bob is 0, insert into 0th row), then increment the count (e.g. we've now seen 1 Bob; next Bob would go into the 1st row). Complexity is O(N).
You can use a GroupBy() method from LINQ to group collection by property you need. Try the following code:
public class Example
{
public string Name { get; set; }
public long Id { get; set; }
public string Data { get; set; }
}
var array = new[]
{
new Example{ Name = "Bob", Id = 1, Data = "A" },
new Example{ Name = "Jim", Id = 2, Data = "A" },
new Example{ Name = "Sam", Id = 3, Data = "A" },
new Example{ Name = "Bob", Id = 1, Data = "B" },
new Example{ Name = "Bob", Id = 1, Data = "C" },
new Example{ Name = "Sam", Id = 3, Data = "B" },
};
var groups = array.GroupBy(x => x.Data);
UPDATE In order to gain the requested result I ended with this code:
var array = new List<Example>
{
new Example{ Name = "Bob", Id = 1, Data = "A" },
new Example{ Name = "Jim", Id = 2, Data = "A" },
new Example{ Name = "Sam", Id = 3, Data = "A" },
new Example{ Name = "Bob", Id = 1, Data = "B" },
new Example{ Name = "Bob", Id = 1, Data = "C" },
new Example{ Name = "Sam", Id = 3, Data = "B" },
new Example{ Name = "Bob", Id = 4, Data = "Notice this is a different Bob" },
};
// group entries by name so we know we will have [nameCount,cols] dimensional result
var groups = array.GroupBy(x => x.Name).ToDictionary(x => x.Key, x => x.OrderBy(x => x.Id).ToArray());
// Initialize result as multidimensional list
var output = groups.Select(x => new List<Example>()).ToList();
foreach (var group in groups)
{
int j = 0; // <= index of item in output
for (int i = 0; i < group.Value.Length; i++)
{
// check if output[j] doesn't contain item with same name and id
if (output[j].FirstOrDefault(x => x.Name == group.Value[i].Name && x.Id == group.Value[i].Id) == null)
{
output[j].Add(group.Value[i]);
j++;
}
else
{
j++;
i--;
}
if (j == output.Count) j = 0; // <= reset counter
}
}

LINQ: Get an item that is not in the records - C#

This is an extended question back from my POST
class EmployeeSchedule
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime WorkDate { get; set; }
public bool isOff { get; set; }
}
Now in this case i would like to find an Id whose WorkDate is not found in 4/12/2016 from the List as my criteria.
List<Staff> workers = new List<Staff>()
{
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/11/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/12/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/13/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/14/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/15/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/16/2016"), IsOff = false},
new Staff { Id = 1, Name = "Emp 1", WorkDate = Convert.ToDateTime("4/17/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/11/2016"), IsOff = false},
// new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/12/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/13/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/14/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/15/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/16/2016"), IsOff = false},
new Staff { Id = 2, Name = "Emp 2", WorkDate = Convert.ToDateTime("4/17/2016"), IsOff = false},
};
So far i have tried this code:
var notInDate = workers.Where(x => !workers.Any(y => Convert.ToDateTime("4/12/2016") != x.WorkDate));
foreach (var item in notInDate)
{
Console.WriteLine(item.Id + " " + item.WorkDate.Date);
}
Which has the output of: ID :1 WorkDate: 4/12/2016 12:00:00 AM
And this one with this output:
var notInDate = workers.Where(x => workers.Any(y => Convert.ToDateTime("4/12/2016") != x.WorkDate));
ID :1 WorkDate: 4/11/2016 12:00:00 AM
ID :1 WorkDate: 4/13/2016 12:00:00 AM
ID :1 WorkDate: 4/14/2016 12:00:00 AM
ID :1 WorkDate: 4/15/2016 12:00:00 AM
ID :1 WorkDate: 4/16/2016 12:00:00 AM
ID :1 WorkDate: 4/17/2016 12:00:00 AM
ID :2 WorkDate: 4/11/2016 12:00:00 AM
ID :2 WorkDate: 4/13/2016 12:00:00 AM
ID :2 WorkDate: 4/14/2016 12:00:00 AM
ID :2 WorkDate: 4/15/2016 12:00:00 AM
ID :2 WorkDate: 4/16/2016 12:00:00 AM
ID :2 WorkDate: 4/17/2016 12:00:00 AM
The second example just removed the record whose WorkDate is 4/12/2016
I would like to get whose Id = 2 because that Id do not have the WorkDate of 4/12/2016
If you want all entries from people that did not work on that day, you need to group by id (to get a group per person) and filter on individual groups that don't contain the date. Something like;
var notInDate =
// Create a group per employee
workers.GroupBy (x => x.Id)
// Keep only groups that didn't work
.Where (x => !x.Any (y => y.WorkDate == Convert.ToDateTime ("4/12/2016")))
// Get only the key for each group
.Select (x => x.Key);
You could do this, use GroupBy to group on Id and then validate whether staff worked on that date using Any extension..
var notInDate = workers.GroupBy(g=>g.Id)
.Where(x => !x.Any(y => DateTime.ParseExact("4/12/2016","M/d/yyyy", null) == y.WorkDate))
.FirstOrDefault(); // Get the first matching staff.
if(notInDate != null)
{
id = notInDate.Key; // StaffId
}

Sorting an IEnumerable in LINQ

How to sort the given examples.
IEnumerable<extra> eList = new List<extra>()
{
new extra{ id = 1, text = "a"},
new extra{ id = 2, text = "g"},
new extra{ id = 3, text = "i"},
new extra{ id = 4, text = "e"},
new extra{ id = 5, text = "f"},
new extra{ id = 6, text = "d"},
new extra{ id = 7, text = "c"},
new extra{ id = 8, text = "h"},
new extra{ id = 9, text = "b"}
};
IEnumerable<sample> sam = new List<sample>()
{
new sample{ id = 1, name = "sample 1", list = new List<int>{1,5,6}},
new sample{ id = 2, name = "sample 1", list = new List<int>{2,9}},
new sample{ id = 3, name = "sample 1", list = new List<int>{8,3,7}},
new sample{ id = 4, name = "sample 1", list = new List<int>{3,4,8}},
new sample{ id = 5, name = "sample 1", list = new List<int>{1,5,7}},
new sample{ id = 6, name = "sample 1", list = new List<int>{6,9,7}}
};
I have this code to sort and join the sample list to the extra object above.
var s2 = (from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select new {
id = d2, text = e.text
}
).OrderBy(item => item.text.FirstOrDefault())
});
The code above works fine, it joined the two data and sorted the values for the list. But what I want is the output above 's2' will be sorted again by its 'list' value by 'list.text'.
So possible output above must be:
{ id = 1, name = "sample 1", list = {'a','f','d'}},
{ id = 5, name = "sample 1", list = {'a','f','c'}},
{ id = 2, name = "sample 1", list = {'g','b'}},
{ id = 4, name = "sample 1", list = {'i','e','h'}},
{ id = 6, name = "sample 1", list = {'d','b','c'}},
{ id = 3, name = "sample 1", list = {'h','i','c'}},
Is this possible in LINQ?
thanks
var newsam = sam.Select(s => new
{
id = s.id,
name = s.name,
list = s.list
.Select(l => eList.FirstOrDefault(e => e.id == l).text)
.OrderBy(e => e)
.ToList()
}
).OrderBy(s => s.list.FirstOrDefault())
.ToList();
EDIT
So, the inner lists are sorted by the text value of the eList; and the outer list is sorted by the first element of the inner list
EDIT
var s2=(from d1 in sam
select new
{
name = d1.name,
id = d1.id,
list =
(
from d2 in d1.list
join e in eList on d2 equals e.id
select e.text
).OrderBy(item => item).ToList()
}).OrderBy(item => item.list.FirstOrDefault());

Categories