Sorting lists in c# - c#

I have two lists in C#.
public List<MyClass> objectList = new List<MyClass>(); // it is filled with MyClass objects
public List<int> numberList = new List<int>(); // it is filled with numbers
The index of numbers in the numberList correspond to object index in the objectList: For example: objectList[0] = o1 and numberList[0] = 3 ;
objectList[1] = o2 and numberList[1] = 5 ...
objectList: |o1 | o2 | o3 | o4 | o5 | ...
numberList: 3 5 6 1 4 ...
I want to sort the numbers in the numberList in ascending order and I want for the objetcs in objectList to move with them:
After sorting:
objectList: |o4 | o1 | o5 | o2 | o3 | ...
numberList: 1 3 4 5 6 ...
In practical use I need this for implementing the Hill climbing algorithm on a N queen problem. In the objectList I store positions of all the queens on the board and in the numberList I store the calculated heuristics for the positions. Then I want to sort the numberList so I get the position with the lowest heuristic value. The goal is to move to the position with the lowest heuristic value.

Transform your object list into a sequence of items paired with their indices:
var pairs = objectList.Select(item, index) => new { item, index };
Now you have something you can use to do an ordering:
var orderedPairs = pairs.OrderBy(pair => numberList[pair.index]);
Now you have an ordered list of pairs. Turn that back into an ordered list of items:
var ordered = orderedPairs.Select(pair => pair.item);
and turn it into a list:
var orderedList = ordered.ToList();
Note that your original lists are not altered. This creates a new list that is in the order you want.
Of course you can do it all in one expression if you like:
objectList = objectList
.Select((item, index) => new { item, index } )
.OrderBy(pair => numberList[pair.index])
.Select(pair => pair.item)
.ToList();
Now, all that said: it sounds like you're doing too much work here because you've chosen the wrong data structure. It sounds to me like your problem needs a min heap implementation of a priority queue, not a pair of lists. Is there some reason why you're not using a priority queue?

Related

Find the Biggest List in a Dictionary of Lists

Lets say I have:
Dictionary<string, List<string>> ourGroups = new Dictionary(string, List<string>>();
Dictionary contents (Key is based on a specific letter position as shown):
Key | Value | Total
-O- | "DOC", "HOP", "POP" | Total = 3
--- | "SIP", "PLZ", "SUZ", "UPS" | Total = 4
So key 2 contains our biggest list of values.
How would I place the biggest key Values into a separate list? What I tried:
List<string> finalGroup = new List<string>();
finalGroup = ourGroups.Values.Max();
Obviously I'm not doing this correctly. A bit of explanation on how this works would be appreciated.
You can use LINQ to sort by Values and select the first one like below .
var result = ourGroups.OrderByDescending(s => s.Value.Count()).First().Value;
If you intend to get the Key, which has Items in its Value (list), you could use
var keyWithMaxValue = ourGroups.Aggregate((l, item) => l.Value.Count > item.Value.Count ? l : item).Key;
If you need the Value (list)
var maxValue = ourGroups.Aggregate((l, item) => l.Value.Count > item.Value.Count ? l : item).Value;

Combine multiple values into one in c#

I have a dictionary having ids as key and value will be the ids to which it will be clubbed ( which is actually the ref data).
There is one more list which only contains the ids but for specific name.
First list have 100 different ids from 1 to 100.
I am checking whether the ids for specific name in list 2 is present in list 1.If they are present then saving those ids. But there is a special condition in it.
e.g. If ids present in list 2 have clubbed ids (which we are checking from the ref dictionary) then we have to save only the clubbed id
Let us suppose the ids in list 2 is 1,10,21. 1 and 10 are clubbed with 21.Therefore, I only have to save one id which is the clubbed one i.e. 21 but not to save 1 and 10. In this case we are saving only 1 instead of 3.
If these ids dont have any clubbed id then 3 ids will be saved (1,10,21).
Updated:
Dictionary have 1 to 100 ids and some of the ids have clubbed ids and some dont
Dictionary<int,string> dict = new Dictionary<int,string>();
//Key is id and the value is clubbedid
dict.Add(1,"21");
dict.Add(10,"21");
dict.Add(21,"None");
// etc
//In the list 2 we have ids for specific name
List<int> list2 = new List<int>();
list2.Add(1);
list2.Add(10);
list2.Add(21);
First i will check whether all the three ids in list 2 are in reference dictionary.And then will assign the value in other list of object in the field Id.
foreach(int value on list2)
{
if(dict.ContainsKey(value))
{
List<class> list3 = new List<class> list3();
list3.Id = value;
}
}
So I have added all the three ids 1,10,21 one by one in id field of list3. Now the list3 contains three ids. In the simple case it is correct where none of the id have clubbed id.
But as you can see in my reference dictionary, ids 1 and 10 have clubbed id as 21.Therefore, in the list3 I have to store only one value which is 21 (only clubbed id removing the other one 1 and 10)
Any help.
Your question is not particularly clear - as per the comments currently.
To have a stab at this - assuming list1 is an IEnumerable<int> and list2 is a Dictionary<int,int[]> then I think what you're trying to do is along the lines of the following
// numbers 1-100
var list1 = Enumerable.Range(1,100).ToList();
// 3 entries
var list2 = new Dictionary<int,int[]>(){
{1,new[]{21}},
{10,new[]{21}},
{21,new int[0]}
};
var result = list2.SelectMany(item => {
if(!list1.Contains(item.Key))
return Enumerable.Empty<int>();
if(item.Value != null && item.Value.Length>0)
return item.Value;
return new[]{item.Key};
}).Distinct();
Live example: http://rextester.com/RZMEHU88506
Having updated your question, this might work for you:
var list3 = list2.Select(x => {
int value = 0;
// if the dict contains the key and the value is an integer
if(dict.ContainsKey(x) && int.TryParse(dict[x], out value))
return value;
return x;
})
.Distinct()
.Select(x => new MyClass(){ Value = x })
.ToList();
Live example: http://rextester.com/KEEY8337

Sort groups based on values within groups

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

max amount of same item in a list with C#

Let's say I have this list:
1
1
1
1
2
2
2
3
I want to narrow it down with C# to a list with a maximum of two same items in a list so it would look like this:
1
1
2
2
3
I used to use 'distinct' like this:
string[] array = System.IO.File.ReadAllLines(#"C:\list.txt");
List<string> list = new List<string>(array);
List<string> distinct = list.Distinct().ToList();
but don't have an idea on how it could bring a max number of same values
You could do it with Linq as follows.
var Groups = Input.GroupBy( i => i );
var Result = Groups.SelectMany( iGroup => iGroup.Take(2) ).ToArray();

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;
}

Categories