Sorting range of list using LINQ - c#

I was wondering if it is possible to do ranged sort using LINQ, for example i have list of numbers:
List< int > numbers = new List< int >
1
2
3
15 <-- sort
11 <-- sort
13 <-- sort
10 <-- sort
6
7
etc.
Simply using numbers.Skip(3).Take(4).OrderBy(blabla) will work, but it will return a new list containing only those 4 numbers. Is is somehow possible to force LINQ to work on itself without returning a new "partial" list or to receive complete one with sorted part?
Thanks for any answer!

Try something like this:
var partiallySorted = list.Where(x => x < 11)
.Concat(list.Where(x => x >= 11 && x <=15).OrderBy(/*blah*/)))
.Concat(list.Where(x => x > 15));

List<int> list = new List<int>() {1,2,3,15,11,13,10,6,7};
list.Sort(3, 4,Comparer<int>.Default);

Simply get the required range based on some criteria and apply the sort on the resultant range using Linq.
List<int> numbers = new List<int>() { 15, 4, 1, 3, 2, 11, 7, 6, 12, 13 };
var range = numbers.Skip(3).Take(4).OrderBy(n => n).Select(s => s);
// output: 2, 3, 7, 11

No, the Linq extension methods will never modify the underlying list. You can use the method List<T>.Sort(int index, int counter, IComparer<T> comparer) to do an in-place sort:
var list = new List<int> {1, 2, 3, 15, 11, 13, 10, 6, 7};
list.Sort(3, 4, null);

Use this for default inline List Sort:
Syntax: List.Sort(start index, number of elements, Default Comparer)
List<int> numbers = new List<int> { 1, 2, 3, 15, 11, 13, 10, 6, 7 };
numbers.Sort(3, 6, Comparer<int>.Default);
If you want to sort by [properties/attributes] of the element or precisely something else use the below method,
I had sorted the string by number of characters, and also from 2nd element to end of List.
Syntax: List.Sort(start index, number of elements, Custom Comparer)
List<string> str = new List<string> { "123", "123456789", "12", "1234567" };
str.Sort(1, str.Count - 1, Comparer<string>.Create((x, y) => x.Length.CompareTo(y.Length)));

Related

C# Select some lists in many lists that look like a list

Sorry if the header made you confused.
This thread looks similar header but that is actually different Selecting some lists from a list of lists.
I want to Select some lists in many lists that look like a list
Sample:
// data source
List<List<int>> sources = new List<List<int>>();
sources.Add(new List<int>(){1, 2, 3, 4});
sources.Add(new List<int>(){1, 2, 3, 4, 5});
sources.Add(new List<int>(){1, 2, 3, 4, 5, 6});
sources.Add(new List<int>(){1, 2, 99, 3, 4, 5, 6});
sources.Add(new List<int>(){1, 3, 99, 2, 4, 5});
sources.Add(new List<int>(){5, 4, 3, 2, 1});
sources.Add(new List<int>(){1, 2, 4, 5, 6});
sources.Add(new List<int>(){1, 2, 69, 3, 4, 5});
// the list that we want to find lists similar to this
List<int> current = new List<int>() {1, 2, 3, 4, 5};
The list contain not-important element, can be ignored. Updated! In the case its elements are not appeared in current:
List<int> flexible = new List<int>() {99, 66, 123123, 2};// <= updated!
The function I want to write:
void FilterA(List<int> current, List<List<int>> sources, List<int> flexible) {}
How to make FilterA output these list (Lists chosen)? Printing functions are not required.
Lists chosen
1 2 3 4 5 // exactly the same !
1 2 3 4 5 6 // same first 5 elements, the rests are not important
1 2 99 3 4 5 6 // 99 is in flexible list, after ignored that is 1 2 3 4 5 6
// Updated! Ignore 99 because it is not in list current
Lists ignored
1 2 3 4 // missing 5 in current
1 3 99 2 4 5 // 99 is in flexible list, after ignored that is 1 3 2 4 5
5 4 3 2 1 // wrong order
1 2 4 5 6 // missing 3 in current
1 2 69 3 4 5 // 69 is not in flexible list
Thank you very much!
--- Updated ---
If elements in list flexible appeared in list current, they must not be excluded.
The answer of #Sweeper is nice.
p/s: In the case not any element of flexible appear in current, #TheGeneral 's answer is great, runs great performance.
Update after clarification
The premise is, remove flexible with Except, Take n to then compare with SequenceEqual.
Note : All three methods have linear time complexity O(n)
var results = sources.Where(x =>
x.Except(flexible)
.Take(current.Count)
.SequenceEqual(current));
Output
1, 2, 3, 4, 5
1, 2, 3, 4, 5, 6
1, 2, 99, 3, 4, 5, 6
Full demo here
Additional Resources
Enumerable.Except
Produces the set difference of two sequences.
Enumerable.Take
Returns a specified number of contiguous elements from the start of a
sequence.
Enumerable.SequenceEqual
Determines whether two sequences are equal according to an equality
comparer.
You should write a method that determines whether one list (candidate) should be chosen:
public static bool ShouldChoose(List<int> candidate, List<int> current, List<int> flexible) {
int candidateIndex = 0;
foreach (int element in current) {
if (candidateIndex >= candidate.Count) {
return false;
}
// this loop looks for the next index in "candidate" where "element" matches
// ignoring the elements in "flexible"
while (candidate[candidateIndex] != element) {
if (!flexible.Contains(candidate[candidateIndex])) {
return false;
}
candidateIndex++;
}
candidateIndex++;
}
return true;
}
Then you can do a Where filter:
var chosenLists = sources.Where(x => ShouldChoose(x, current, flexible)).ToList();
foreach (var list in chosenLists) {
Console.WriteLine(string.Join(", ", list));
}
This works for me:
var results =
sources
.Where(source => source.Except(flexible).Count() >= current.Count())
.Where(source => source.Except(flexible).Zip(current, (s, c) => s == c).All(x => x))
.ToList();

How to sort a list of int list

Please let me know how can I sort a List which contains lists of integers.
List<List<int>> numberLists = new List<List<int>>();
numberLists.Add(new List<int>() { 6, 8, 9 });
numberLists.Add(new List<int>() { 2, 4, 7 });
numberLists.Add(new List<int>() { 4, 7, 8 });
numberLists.Add(new List<int>() { 2, 3, 9 });
How to sort the above List to have following result?
2, 3, 9
2, 4, 7
4, 7, 8
6, 8, 9
Thank you in advance!
You can do this
var results = numberLists.OrderBy(x => x[0])
.ThenBy(x => x[1])
.ThenBy(x => x[2]);
foreach (var result in results)
{
foreach (var subresult in result)
{
Console.Write(subresult + " ");
}
Console.WriteLine();
}
Output
2 3 9
2 4 7
4 7 8
6 8 9
Full Demo here
Additional Results
Enumerable.OrderBy Method (IEnumerable, Func)
Sorts the elements of a sequence in ascending order according to a
key.
Enumerable.ThenBy Method (IOrderedEnumerable, Func)
Performs a subsequent ordering of the elements in a sequence in
ascending order according to a key.
You can do it in a simple way base on your criteria
var sortedLists = numberLists.OrderBy(x => string.Join(",", x.ToArray())).ToList();
just convert it to string then compare as string

Efficient way to replace int in list with first occurrence index

I am looking for a way to replace every int element in a list with the index of its first occurrence in a separate Distinct list. For example, take the following list:
<50, 2, 4, 5, 43, 42, 44, 14, 50, 44, 23, 2, 16, 4, 5, 23, 33 ... >
The distinct list would read like this:
<50, 2, 4, 5, 43, 42, 44, 14, 50, 23, 33 ... >
I would like for the list to read as follows:
<0, 1, 2, 3, 4, 5, 6, 7, 0, 6, 8, 1, 9, 3, 4, 8, 10 ...>
I am new to LINQ operators, but I have the following code:
//Retrieve original list:
List<int> SubFaceList = FaceList.GetRange (faceBounds [fb], faceBounds [fb + 1]);
// Get unique face list entries:
List<int> UniqueFaceList = SubFaceList.Distinct ().ToList ();
//Replace values with first ocurrence:
List<int> RestructuredList = SubFaceList.Select ((vx,ix) => UniqueFaceList.IndexOf(vx)).ToList();
I am doing this, in order to split up a large Collada triangular 3D mesh, into smaller submeshes. The arrays are face construction order, which reference an index of vertices in a separate array, so numbers could be referenced multiple times in the face array. However, upon executing my code, some meshes take upwards of 30 minutes to process.
I also have the following code, which extracts the given vertices from the main vertex array, which does not seem to suffer from the same crippling inefficiency:
List<Vector3> subVertices = Vertices.Where ((vx, ix) => UniqueFaceList.Contains (ix)).Select (vx => vx).ToList();
Am I going about this wrong, and is there a potential way that I could extract the index based on the current vertex being processed in this statement? I know there has to be a more efficient way to get the information that I need, but I am not quite sure how.
It's possible with LINQ, but if you want maximum performance, you could use Dictionary and simple for / foreach loop (you'll find that the code is not so bigger than the most efficient LINQ solution):
var indexMap = new Dictionary<int, int>();
var RestructuredList = new List<int>(SubFaceList.Count); // final capacity
foreach (var item in SubFaceList)
{
int index;
if (!indexMap.TryGetValue(item, out index))
indexMap.Add(item, index = indexMap.Count);
RestructuredList.Add(index);
}
Store value and index to dictionary.
var faceList = new List<int>{ 50, 2, 4, 5, 43, 42, 44, 14, 50, 44, 23, 2, 16, 4, 5, 23, 33};
var uniqueList = new Dictionary<int,int>();
var restructuredList = new List<int>();
var i = 0;
faceList.ForEach(f =>
{
if (uniqueList.ContainsKey(f))
{
restructuredList.Add(uniqueList[f]);
return;
}
restructuredList.Add(i);
uniqueList.Add(f, i++);
});

Create multiple lists of unique entries from master list in c#

I need to process an outbound SMS queue and create batches of messages. The queued list might contain multiple messages to the same person. Batches do not allow this, so I need to run through the main outbound queue and create as many batches as necessary to ensure they contain unique entries.
Example:
Outbound queue = (1,2,3,3,4,5,6,7,7,7,8,8,8,8,9)
results in...
batch 1 = (1,2,3,4,5,6,7,8,9)
batch 2 = (3,7,8)
batch 3 = (7,8)
batch 4 = (8)
I can easily check for duplicates but I'm looking for a slick way to generate the additional batches.
Thanks!
Have a look at this approach using Enumerable.ToLookup and other LINQ methods:
var queues = new int[] { 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 8, 8, 8, 9 };
var lookup = queues.ToLookup(i => i);
int maxCount = lookup.Max(g => g.Count());
List<List<int>> allbatches = Enumerable.Range(1, maxCount)
.Select(count => lookup.Where(x => x.Count() >= count).Select(x => x.Key).ToList())
.ToList();
Result is a list which contains four other List<int>:
foreach (List<int> list in allbatches)
Console.WriteLine(string.Join(",", list));
1, 2, 3, 4, 5, 6, 7, 8, 9
3, 7, 8
8
8
Depending on the specific data structures used, the Linq GroupBy extension method could be used (provided that the queue implements IEnumerable<T> for some type T) for grouping by the same user; afterwards, the groups can be iterated separately.
A naive approach would be to walk over the input, creating and filling the batches as you go:
private static List<List<int>> CreateUniqueBatches(List<int> source)
{
var batches = new List<List<int>>();
int currentBatch = 0;
foreach (var i in source)
{
// Find the index for the batch that can contain the number `i`
while (currentBatch < batches.Count && batches[currentBatch].Contains(i))
{
currentBatch++;
}
if (currentBatch == batches.Count)
{
batches.Add(new List<int>());
}
batches[currentBatch].Add(i);
currentBatch = 0;
}
return batches;
}
Output:
1, 2, 3, 4, 5, 6, 7, 8, 9
3, 7, 8
8
8
I'm sure this can be shortened or written in a functional way. I've tried using GroupBy, Distinct and Except, but couldn't figure it out that quickly.

get "running average" of a list

Say I've got a list:
List<int> numbers = new List<int>();
and I'm trying to get the "running averages" of it. (Sorry, I don't really know how to call it).
For instance:
The first item in this list is 5, the average of 5 is 5, so the first average is 5.
The second item in this list is 7, the average of 5 and 7 is 6, so the second average is 6.
The third item in this list is 10, the average of 5, 7 and 10 is 7.3, so the third average is 7.3
And so on.
Those first average, second average etc are the averages I'm trying to get. How would I go about doing this? I've been searching the internet but honestly I'm not quite sure what I should be looking for. :(
try this:
string st = Console.ReadLine();
string[] strs = st.Split(' ');
List<int> numbers = new List<int>();
List<double> averages = new List<double>();
for (int i = 0; i < strs.Length; i++)
{
numbers.Add(int.Parse(strs[i]));
averages.Add(numbers.Average());
}
this will read the numbers from the standard input, the numbers are separated by space in input.
You can try something like this:
var averages = Enumerable.Range(1, numbers.Count)
.Select(x => numbers.Take(x).Average())
.ToList();
This will generate a sequence from 1 to numbers.Count. Then using Take it will get X element at each time (you can think X as an index, only difference is it starts from 1 and increases one by one up to the numbers.Count) starting from the first element then get their average.Put them into a list.
The lists
List<int> numbers = new List<int>();
List<double> averages = new List<double>();
test data
numbers.AddRange(new int[]{5, 7, 10});
// get average of current List
averages.Add(numbers.Average());
Such a list of averages all by itself usually doesnt mean much without some other data like number of elements, duration of time or something to qualify it.
This method is better suited when the moving average is not one for one with the values. For instance, the app stores values. Then periodically, say once a minute, the average is calculated and stored.
You need the "Running Average", not the average from the start over all items I assume?
The running average needs a number to tell how far back to look and one to tell how far forward to look.
This will give you the running average:
public List<double> GetRunningAverage(List<double> SourceList, int ItemsBefore, int ItemsAfter)
{
List<double> TargetList = new List<double>() { };
// Only makes sense if the list is > 1 of course
if (SourceList.Count > 1)
{
for (int i = 0; i < SourceList.Count; i++)
{
int LookBack =
(ItemsBefore > i ? i : ItemsBefore);
int LookForward =
(ItemsAfter < SourceList.Count - i
? ItemsAfter : SourceList.Count - i);
TargetList.Add(SourceList.GetRange(i - LookBack, LookBack + LookForward).Average());
}
}
else
{
TargetList.AddRange(SourceList);
}
return TargetList;
}
You can then use it like this:
List<double> FullList = GetRunningAverage(
new List<double>() { 100, 5, 5, 6, 23, 10, 56, 32, 54, 1, 3, 85, 65, 49, 22, 65, 32, 5, 2, 4, 5, 89, 110, 55, 6, 56, 57 },
3, // Looking back 3 items
3); // Looking forward 3 items

Categories