Sort groups based on values within groups - c#

I am trying to sort an array that contains logical groups of people, and the people's scores.
Name | Group | Score
----------------------
Alfred | 1 | 3
Boris | 3 | 3
Cameron| 3 | 1
Donna | 1 | 2
Emily | 2 | 2
The people should be sorted by group, based on the lowest score in the group. Therefore, group 3 is first, because it contains the person with the lowest score. Then the people in group 1 because it has the person with the next lowest score (and a lower group number than group 2).
So the result would be: Cameron, Boris, Donna, Alfred, Emily
I have accomplished this, but I am wondering if there is a better way of doing it. I receive an array, and end up sorting the array in the correct order.
I use LINQ (mostly obtained from Linq order by, group by and order by each group?) to create a target sorting array that maps where a person should be, compared to where they currently are in the array.
I then use Array.Sort using my target sorting array, but the array the LINQ statement creates is "reversed" in terms of indices and values, so I have to reverse the indices and values (not the order).
I have attached my code below. Is there a better way of doing this?
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sorter
{
class Program
{
static void Main(string[] args)
{
// Sample person array.
// Lower score is better.
Person[] peopleArray = new Person[]
{
new Person { Name = "Alfred", Group = "1", Score = 3, ArrayIndex = 0 },
new Person { Name = "Boris", Group = "3", Score = 3, ArrayIndex = 1 },
new Person { Name = "Cameron", Group = "3", Score = 1, ArrayIndex = 2 },
new Person { Name = "Donna", Group = "1", Score = 2, ArrayIndex = 3 },
new Person { Name = "Emily", Group = "2", Score = 2, ArrayIndex = 4 }
};
// Create people list.
List<Person> peopleModel = peopleArray.ToList();
// Sort the people based on the following:
// Sort people into groups (1, 2, 3)
// Sort the groups by the lowest score within the group.
// So, the first group would be group 3, because it has the
// member with the lowest score (Cameron with 1).
// The people are therefore sorted in the following order:
// Cameron, Boris, Donna, Alfred, Emily
int[] targetOrder = peopleModel.GroupBy(x => x.Group)
.Select(group => new
{
Rank = group.OrderBy(g => g.Score)
})
.OrderBy(g => g.Rank.First().Score)
.SelectMany(g => g.Rank)
.Select(i => i.ArrayIndex)
.ToArray();
// This will give the following array:
// [2, 1, 3, 0, 4]
// I.e: Post-sort,
// the person who should be in index 0, is currently at index 2 (Cameron).
// the person who should be in index 1, is currently at index 1 (Boris).
// etc.
// I want to use my target array to sort my people array.
// However, the Array.sort method works in the reverse.
// For example, in my target order array: [2, 1, 3, 0, 4]
// person currently at index 2 should be sorted into index 0.
// I need the following target order array: [3, 1, 0, 2, 4],
// person currently at index 0, should be sorted into index 3
// So, "reverse" the target order array.
int[] reversedArray = ReverseArrayIndexValue(targetOrder);
// Finally, sort the base array.
Array.Sort(reversedArray, peopleArray);
// Display names in order.
foreach (var item in peopleArray)
{
Console.WriteLine(item.Name);
}
Console.Read();
}
/// <summary>
/// "Reverses" the indices and values of an array.
/// E.g.: [2, 0, 1] becomes [1, 2, 0].
/// The value at index 0 is 2, so the value at index 2 is 0.
/// The value at index 1 is 0, so the value at index 0 is 1.
/// The value at index 2 is 1, so the value at index 1 is 2.
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
private static int[] ReverseArrayIndexValue(int[] target)
{
int[] swappedArray = new int[target.Length];
for (int i = 0; i < target.Length; i++)
{
swappedArray[i] = Array.FindIndex(target, t => t == i);
}
return swappedArray;
}
}
}

As I understand, you want to sort the input array in place.
First, the sorting part can be simplified (and made more efficient) by first OrderBy Score and then GroupBy Group, utilizing the defined behavior of Enumerable.GroupBy:
The IGrouping<TKey, TElement> objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping<TKey, TElement>. Elements in a grouping are yielded in the order they appear in source.
Once you have that, all you need is to flatten the result, iterate it (thus executing it) and put the yielded items in their new place:
var sorted = peopleArray
.OrderBy(e => e.Score)
.ThenBy(e => e.Group) // to meet your second requirement for equal Scores
.GroupBy(e => e.Group)
.SelectMany(g => g);
int index = 0;
foreach (var item in sorted)
peopleArray[index++] = item;

Not sure if I really understood what the wished outcome should be, but this at least gives same order as mentioned in example in comments:
var sortedNames = peopleArray
// group by group property
.GroupBy(x => x.Group)
// order groups by min score within the group
.OrderBy(x => x.Min(y => y.Score))
// order by score within the group, then flatten the list
.SelectMany(x => x.OrderBy(y => y.Score))
// doing this only to show that it is in right order
.Select(x =>
{
Console.WriteLine(x.Name);
return false;
}).ToList();

int[] order = Enumerable.Range(0, peopleArray.Length)
.OrderBy(i => peopleArray[i].Score)
.GroupBy(i => peopleArray[i].Group)
.SelectMany(g => g).ToArray(); // { 2, 1, 3, 0, 4 }
Array.Sort(order, peopleArray);
Debug.Print(string.Join(", ", peopleArray.Select(p => p.ArrayIndex))); // "3, 1, 0, 2, 4"

If your desired result is less line of codes. How about this?
var peoples = peopleModel.OrderBy(i => i.Score).GroupBy(g =>
g.Group).SelectMany(i => i, (i, j) => new { j.Name });
1) Order list by scores
2) Group it by grouping
3) Flatten the grouped list and create new list with "Name" property using SelectMany
For information using anonymous type
https://dzone.com/articles/selectmany-probably-the-most-p

Related

Where clause only returning a subset of products matching index array

I have a list of ordered products. I also have a list of index values. I want to pull out all products whose index is in the list of indexes. Right now I'm doing this:
var indexes = new List<int> { 1, 2, 5, 7, 10 };
var myProducts = orderedProducts.Where((pr, i) => indexes.Any(x => x == i)).ToList();
However, myProducts only has 2 elements in it: The products with indexes 1 and 2. It completely misses 5, 7, and 10. What is going on? How do I fix this?
Note: orderedProducts.Count is always greater than the maximum value of the indexes list.
orderedProducts is formed from the following:
orderedProducts = productDictionary[fam.Key]
.ToList()
.OrderBy(g => g.factor)
.ToList();
where g.factor is an int, fam.Key is an int key for the product dictionary. I've checked myProducts and it is indeed a List<Product> ordered by factor ascending.
prodDictionary is a Dictionary<int?, List<Product>>.
You missed something in your testing. You said "myProducts.Count is always greater than the maximum value of the indexes list". That makes no sense considering you said "myProducts only has 2 elements". orderedProducts.Count must be < 6. Thus the problem. You are pulling out elements by simply comparing the indexes in the List. You can "fix" the problem by adding more products to the list.
void Main()
{
var indexes = new List<int> { 1, 2, 5, 7, 10 };
var orderedProducts = new List<Product>();
orderedProducts.Add(new Product());
orderedProducts.Add(new Product());
orderedProducts.Add(new Product());
orderedProducts.Add(new Product());
orderedProducts.Add(new Product());
//orderedProducts.Add(new Product());//Add this in and you will see a result at index 5
//orderedProducts.Add(new Product());
//orderedProducts.Add(new Product());//7
//orderedProducts.Add(new Product());
//orderedProducts.Add(new Product());
//orderedProducts.Add(new Product());//10
var myProducts = orderedProducts.Where((pr, i) => indexes.Any(x => x == i)).ToList();
}
public class Product
{
}
Uncomment the products and you get 5 results as expected. Index of 10 on a 0 based array.
why not just indexes.Where(i => i < orderedProducts.Count).Select(i => orderedProducts[i]).ToList();?

C# List 1 has some records that list 2 contains list 3 must have 0 1 correspondingly

My C# list contains object:
class Bot {
string name;
string oid;
}
new List with 2 bots named:
0, name = Pro, oid = 100
1, name = Noob, oid = 200
2, name = Tard, oid = 300
I have also a list which contains list of OID (selected via some odd way)
List is this:
0, 200
1, 300
I need to create a new list like this:
0,0
1,1
2,1
Where list 1 index is maintained and list 2 is matched with the OID of list 1 and if it matches it must display 1 else 0.
How should i begin to do this?
You can use the overload from Select that also returns an index:
var result = myBots.Select((x, i) => new
{
Index = i,
Value = list2.Any(y => y.oid == x.oid) ? 1: 0
});
However you may consider to use a boolean instead of a either zero or one that indicates if the element matches or not:
var result = myBots.Select((x, i) => new
{
Index = i,
ContainedInOtherlist = list2.Any(y => y.oid == x.oid)
});

Using a nested foreach to store one foreach value into an array

Alright so I didn't really know how to word this question, but I did my best. The goal I am trying to accomplish is to go through categories using a foreach loop. Inside the category foreach loop another foreach loop will go through Numbers. Right now it is grabbing ever value in the tables and storing them into an array. My goal is to only store the highest number in each category into the array.
Here is how the tables would look:
Category Table
Title NumberId
Type 1
Priority 2
Likelihood 3
Numbers Table
Order NumberId
3 1
2 1
1 1
3 2
2 2
1 2
3 3
2 3
1 3
So my goal would be instead of storing every order value into the array. I would like to store the highest number according to each number id. So there array would include 3,3,3.
This is what I have that stores every number into an array:
int[] values = new int[count];
foreach(var x in Category)
{
foreach(var w in x.Numbers)
{
values[y] = w.Order;
y++;
}
}
Solution:
int[] values = new int[count];
foreach(var x in Category)
{
foreach(var w in x.Numbers)
{
values[y] = x.Numbers.Select(o => o.Order).Max();
y++;
break;
}
}
You can use IEnumerable.Max() :
foreach(var x in Category)
{
values[y] = x.Numbers.Select(o => o.Order).Max();
y++;
}
This can be accomplished relatively easily through LINQ as:
int[] values = new int[count];
foreach(var x in Category)
{
values.[y] = x.Numbers.OrderBy(w => w.Order).Reverse().First();
y++;
}
This orders the x.Numbers by their ascending order, reverses the order (to place the highest value first in the order), and then selects the first value.
Ensure with this method that you've actually got a value for x.Number, else you'll get an exception thrown by the .First() call.
If you're unable to use LINQ (e.g. if you're on .NET 2.0), then consider using a Dictionary with the Category as the key, and the highest Number as the value:
Dictionary<int, int> categoriesByHighestOrders = new Dictionary<int, int>();
foreach(var x in Category)
{
if (!categoriesByHighestOrders.Keys.Contains[x.SomeIndetifier])
categoriesByHighestOrders.Add(x.SomeIdentifier, 0);
foreach(var w in x.Numbers)
{
if (categoriesByHighestOrders[x.SomeIndetifier] < w.Order
categoriesByHighestOrders[x.SomeIndetifier] = w.Order;
}
}

Items Common to Most Lists

Given a list of lists (let's say 5 lists, to have a real number with which to work), I can find items that are common to all 5 lists with relative ease (see Intersection of multiple lists with IEnumerable.Intersect()) using a variation of the following code:
var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };
var intersection = listOfLists.Aggregate((previousList, nextList) => previousList.Intersect(nextList).ToList());
Now let's say that intersection ends up containing 0 items. It's quite possible that there are some objects that are common to 4/5 lists. How would I go about finding them in the most efficient way?
I know I could just run through all the combinations of 4 lists and save all the results, but that method doesn't scale very well (this will eventually have to be done on approx. 40 lists).
If no item is common to 4 lists, then the search would be repeated looking for items common to 3/5 lists, etc. Visually, this could be represented by lists of grid points and we're searching for the points that have the most overlap.
Any ideas?
EDIT:
Maybe it would be better to look at each point and keep track of how many times it appears in each list, then create a list of the points with the highest occurrence?
You can select all numbers (points) from all lists, and group them by value. Then sort result by group size (i.e. lists count where point present) and select most common item:
var mostCommon = listOfLists.SelectMany(l => l)
.GroupBy(i => i)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.First();
// outputs 3
Instead of taking only first item, you can take several top items by replacing First() with Take(N).
Returning items with number of lists (ordered by number of lists):
var mostCommonItems = from l in listOfLists
from i in l
group i by i into g
orderby g.Count() descending
select new {
Item = g.Key,
NumberOfLists = g.Count()
};
Usage (item is a strongly-typed anonymous object):
var topItem = mostCommonItems.First();
var item = topItem.Item;
var listsCount = topItem.NumberOfLists;
foreach(var item in mostCommonItems.Take(3))
// iterate over top three items
You can first combine all the lists, then find the Mode of the list using a dictionary strategy as follows. This makes it pretty fast:
/// <summary>
/// Gets the element that occurs most frequently in the collection.
/// </summary>
/// <param name="list"></param>
/// <returns>Returns the element that occurs most frequently in the collection.
/// If all elements occur an equal number of times, a random element in
/// the collection will be returned.</returns>
public static T Mode<T>(this IEnumerable<T> list)
{
// Initialize the return value
T mode = default(T);
// Test for a null reference and an empty list
if (list != null && list.Count() > 0)
{
// Store the number of occurences for each element
Dictionary<T, int> counts = new Dictionary<T, int>();
// Add one to the count for the occurence of a character
foreach (T element in list)
{
if (counts.ContainsKey(element))
counts[element]++;
else
counts.Add(element, 1);
}
// Loop through the counts of each element and find the
// element that occurred most often
int max = 0;
foreach (KeyValuePair<T, int> count in counts)
{
if (count.Value > max)
{
// Update the mode
mode = count.Key;
max = count.Value;
}
}
}
return mode;
}

best algorithm for search between arrays

I have a problem I need to solve in the best algorithm I can find.
Let me describe the problem first.
I have a class A with number of Hashset<int> with Z number of items
A -> {x,y,z | x = {0,1,2} , y = {-1,0,9} ... }
B -> {x,y,z,k | x = {0,1,-2} , y = {-1,0,19} ... }
...
with an input of a new array of int { ... } entered by the user, the result should be the group with the most hashset with matching numbers between the input and the groups.
For example :
A : {[1,2,3][2,3,8][-1,-2,2]}
B : {[0,-9,3][12,23,68][-11,-2,2]}
Input :
[2,3,-19]
result A : {[2,3][2,3][2]}
result B : {[3][][2]}
A : 3
B : 2
A is the correct answer.
Or something like that .
Yes, I know it's a subjective question but it's for a good cause.
Assuming you have an unknown number of samples to check on the input set, this Linq query should do the trick.
from sample in samples
let intersectedSets =
from set in sample
let intersection = input.Intersect(set)
where intersection.Count() > 0
select intersection
orderby intersectedSets.Count() descending
select intersectedSets;
The top-most element is your desired sample, thus yourCollection.First() will yield your result set - In your given example:
var samples = new[] {
new[]{
new[]{1, 2, 3},
new[]{2, 3, 8},
new[]{-1, -2, 2}
},
new[]{
new[]{0, -9, 3},
new[]{12, 23, 68},
new[]{-11, -2, 2}
}
};
var input = new[]{2, 3, -19};
var result =
(from sample in samples
let intersectedSets =
from set in sample
let intersection = input.Intersect(set)
where intersection.Count() > 0
select intersection
orderby intersectedSets.Count() descending
select intersectedSets).First();
result.Dump(); // LINQPad extension method
apparently you want to use C# to implement this. I don't know if this is the best algorithm (in whatever context) but you could use LINQ to write it down very plain and simple:
int[][] arrays = new[] { new[] { 1, 2 }, new[] { 2, 3 }, new[] {3, 4} };
int[] input = new[] { 1, 4 };
Console.WriteLine(arrays.Count((itemarray) => itemarray.Any((item) => input.Contains(item))));
in an array of int arrays this finds the number of arrays that have at least one of the values of the input array. this is what you're doing, though I'm not sure if it's what you're asking of us.
Given a sample class HashHolder and an instance A of it:
public class HashHolder
{
public HashHolder()
{
Hashes = new List<HashSet<int>>();
}
public List<HashSet<int>> Hashes { get; set; }
}
You can group by hashset and take the maximum count between all groups:
var maxHash = A.Hashes.GroupBy(h => h)
.Select(g => new { Hash = g.Key, Count = input.Count(num => g.Key.Contains(num)) })
.OrderByDescending(g => g.Count)
.FirstOrDefault();
The result will then be maxHash.Hash if maxhHash is not null.

Categories