I have a collection(the collection is sort of big > 100K custom complex items in it and adding new items happens really often).
I need to sort it just once - before showing it.
To simplify my question lets say that I have a collection of integers that I need to sort:
private static void Main(string[] args)
{
var collection = new Collection<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var array = new [] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
// How to apply Array.Sort to Collection<T> ?
list.Sort(); //ok
Array.Sort(array); //ok
}
Basically list.Sort(); uses Array.Sort<T>(this._items, index, count, comparer);
How can I sort my Collection<T> ?
(without copying)
There's no convenient way to do this; Collection<T> doesn't provide raw access to the internal buffer, even for sub-classes (protected). Without that, you can't do a clean in-place sort.
You could manually implement a sort on the underlying .Items (protected), but it is a lot of work and will be inefficient.
What you could also do is:
lease an array from the array pool
copy the data from the local collection to the leased array
sort that array
either clear and re-add everything from the now-sorted array, or overwrite the items index-by-index from the now-sorted-array
return the leased array to the array pool
However, personally I'd probably say "if you need to sort, use List<T>" - it'll save you a lot of work.
Related
I've been trying to write a program, in which I want to use the intersection of two HashSets. Therefore I wrote the following code (for test purposes):
HashSet<int> test1 = new HashSet<int>() { 1, 3, 5, 7, 9 };
HashSet<int> test2 = new HashSet<int>() { 1, 2, 3, 4, 5, 6};
HashSet<int> intersect = new HashSet<int>();
intersect = test1.Intersect(test2);
Line 5 shows an error (code CS0266) which - that's C#'s suggestion - can be corrected by change the line to:
intersect = (HashSet<int>)test1.Intersect(test2);
But when I run the program, the error appears again. I literally have no clue why, even after searching for an answer.
I want to achieve a intersection in the mathematical sense, so that the result for the variable intersect should be { 1, 3, 5}.
And what I found out - but couldn't test - is, that after using the intersect-method on test1, it changes the list in test1 to the intersection, is that true? If yes, is there any chance to avoid this? In my real program I don't want the variable to change into the intersection.
Should I just make a for-loop with an if-statement, to make my own intersection-method, or does this make the code worse?
As said, I tried to make use of C#'s suggestion, but this doesn't work either.
Because I'm a programming-beginner, I'm not really able to understand the definition of the intersect-method (because of this IEnumerable thing...), so I can't solve the problem using existend methods. And because I think my own method could be very inefficient, I don't to it my own. Furthermore I just want to understand, what's the problem. There are two HashSets, both containing integers, which should be intersected and saved in an extra variable...
Intersect() returns a IEnumerable<T>. You can use IntersectWith(), which modifies the current HashSet<T> object to contain only elements that are present in that object and in the specified collection:
HashSet<int> test1 = new HashSet<int>() { 1, 3, 5, 7, 9 };
HashSet<int> test2 = new HashSet<int>() { 1, 2, 3, 4, 5, 6};
test1.IntersectWith(test2); // we are altering test1 here
// test1 contains now [1, 3, 5]
or use the side-effect free Linq Intersect() to get an IEnumerable<T> and if you want it to be a new HashSet<T> just use a constructor:
HashSet<int> test1 = new HashSet<int>() { 1, 3, 5, 7, 9 };
HashSet<int> test2 = new HashSet<int>() { 1, 2, 3, 4, 5, 6};
HashSet<int> intersect = new HashSet<int>(test1.Intersect(test2));
// intersect contains now [1, 3, 5]
Remarks (from MSDN)
If the collection represented by the other parameter is a HashSet<T> collection with the same equality comparer as the current HashSet<T> object, this method is an O(n) operation. Otherwise, this method is an O(n + m) operation, where n is Count and m is the number of elements in other.
Basically in your case IntersectWith() is going to be more efficient!
Complete demo:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
HashSet<int> test1 = new HashSet<int>() {1, 3, 5, 7, 9};
HashSet<int> test2 = new HashSet<int>() {1, 2, 3, 4, 5, 6};
HashSet<int> intersect = new HashSet<int>(test1.Intersect(test2));
intersect.Dump();
test1.IntersectWith(test2);
test1.Dump();
}
}
Try it Online!
Enumerable.Intersect is a LINQ extension method that works with any kind of IEnumerable<T>. It returns IEnumerable<T>, so not a HashSet<T>. But since you have alrady two sets you want to use HashSet.IntersectWith(more efficient since O(n)) with manipulates the first HashSet<T>:
test1.IntersectWith(test2); // test1 contains now [1, 3, 5]
This question already has answers here:
Removing a list of objects from another list
(5 answers)
Closed 1 year ago.
I have a List A of strings that I want to trim of all elements that also appear in List B, while keeping the duplicate values in List A.
Such that with an input like:
List A: [1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7]
List B: [2, 6, 8, 9, 10]
I am hoping to get an output like:
List C: [1, 3, 3, 4, 5, 7, 7, 7]
I originally thought this could be accomplished using ListA.Except(ListB), but that function leaves only one element of a duplicate value.
In the program, List B is much bigger than the example given and there are multiple instances of List A to go through, so I'd like to avoid nested for loops. I don't necessarily care about keeping the original order of List A either, since the output of this will be the input of a frequency dictionary.
Am I overlooking something? Is there a faster option than using nested for loops?
You can use List<T>.RemoveAll()
var a = new[] { 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7 }.ToList();
var b = new[] { 2, 6, 8, 9, 10 }.ToList();
var c = a.Select(i => i).ToList(); //make a copy of 'a'
c.RemoveAll(i => b.Contains(i));
You can just use a Where() clause with a Contains() in it. To avoid O(n²) complexity (which is really what you're trying to avoid when you say "nested for loops," you can create a HashSet out of List B.
var setB = listB.ToHashSet();
var aMinusB = listA.Where(item => !setB.Contains(item)).ToList();
You can use LINQ for this using any:
var result = listA.Where(el1 => !listB.Any(el2 => el2 == el1)).ToList();
Consider the int array below of n elements.
1, 3, 4, 5, 7. In this example the second last item is 5. I want to get the number of elements in this array before the second last value. There are 3 elements before the second last element. I will store the result in an int variable to use later. We obviously take into account that the array will have more than two element all the time.
This array will have different size everytime.
How can I achieve this in the most simplistic way?
The answer will always be n-2, so a very quick solution is to use .Length property and to subtract 2.
You can use Range from C# 8:
int[] arr = new int[]{1, 3, 4, 5, 7};
int[] newArr = arr.Length>=2 ? arr[..^2] : new int[0];
This will return all elements except the last 2, or an empty array if the lenght is less than 2. If it is guaranteed that the array will always have more than 2 elements, then you can simplify:
int[] newArr = arr[..^2];
If you are only interested about the quantity of the numbers then .Length-2 is the best way as it was stated by others as well.
If you are interested about the items as well without using C# 8 features then you can use ArraySegment (1).
It is really powerful, like you can reverse the items without affecting the underlying array.
int[] arr = new int[] { 1, 3, 4, 5, 7 };
var segment = new ArraySegment<int>(arr, 0, arr.Length - 2);
var reversedSegment = segment.Reverse(); //ReverseIterator { 4, 3, 1 }
//arr >> int[5] { 1, 3, 4, 6, 7 }
Please bear in mind that the same is not true for Span (2).
var segment = new Span<int>(arr, 0, arr.Length - 2);
segment.Reverse();
//arr >> int[5] {4, 3, 1, 6, 7 }
There is a ReadOnlySpan, which does not allow to perform such operation as Reverse.
If you would need that then you have to manually iterate through that in a reversed order.
I can do this with an integer:
int a;
a = 5;
But I can't do this with an integer array:
int[] a;
a = { 1, 2, 3, 4, 5 };
Why not?
To clarify, I am not looking for the correct syntax (I can look that up); I know that this works:
int[] a = { 1, 2, 3, 4, 5 };
Which would be the equivalent of:
int a = 5;
What I am trying to understand is, why does the code fail for arrays? What is the reason behind the code failing to be recognised as valid?
The reason there is a difference is that the folks at Microsoft decided to lighten the syntax when declaring and initializing the array in the same statement, but did not add the required syntax to allow you to assign a new array to it later.
This is why this works:
int[] a = { 1, 2, 3, 4, 5 };
but this does not:
int[] a;
a = { 1, 2, 3, 4, 5 };
Could they have added the syntax to allow this? Sure, but they didn't. Most likely they felt that this use-case is so seldom used that it didn't warrant prioritizing over other features. All new features start with minus 100 points and this probably just didn't rank high enough on the priority list.
Note that { 1, 2, 3, 4, 5 } by itself has no meaning; it can only appear in two places:
As part of an array variable declaration:
int[] a = { 1, 2, 3, 4, 5 };
As part of an array creation expression:
new int[] { 1, 2, 3, 4, 5 }
The number 5, on the other hand, has a meaning everywhere it appears in C#, which is why this works:
int a;
a = 5;
So this is just special syntax the designers of C# decided to support, nothing more.
This syntax is documented in the C# specification, section 12.6 Array Initializers.
The reason your array example doesn't work is because of the difference between value and reference types. An int is a value type. It is a single location in memory whose value can be changed.
Your integer array is a reference type. It is not equivalent to a constant number of bytes in memory. Therefore, it is a pointer to the bytes where that data is stored.
In this first line, you are assigning null to a.
int[] a;
In the next line, if you want to change the value of the array, you need to assign it to a new array.
a = new[] {1, 2, 3, 4, 5};
That is why you need the new[] before the list of values within the array if you strongly type your declaration.
int[] a = {1, 2, 3, 4, 5}; // This will work.
var a = {1, 2, 3, 4, 5}; // This will not.
However, as many of the other answers have said, if you declare it in a single line, then you do not need the new[]. If you separate the declaration and initialization, then you are required to use new[].
{} syntax is available for array initialization, not to be used after declaration.
To initialize an array you should try like this:
int[] a = { 1, 2, 3, 4, 5 };
Other ways to Initializing a Single-dimensional array:
int[] a = new int[] { 1, 2, 3, 4, 5 };
int[] a = new int[5] { 1, 2, 3, 4, 5 };
Have a look at this: different ways to initialize different kinds of arrays
How can we delete all array elements between 2 numbers?
For example : The array is {2,6,3,6,8,2,7,2}
The user writes in two numbers, let's say 2 and 4.
That causes the program to delete every array elements between the 2nd and 4th position.
In this case, it deletes : 3,6,8
You cannot delete items from an array. But you can create a new array that contains only the items that you want to keep. In your case you can use the following:
int[] array = {2, 6, 3, 6, 8, 2, 7, 2};
array = array.Where((_, i) => i < 2 || i > 4).ToArray();
By the way, if you use a List instead of an array, then you can remove items. Consider the following example:
List<int> list = new List<int>() {2, 6, 3, 6, 8, 2, 7, 2};
for(int i = 4; i >= 2 ; i--)
{
list.RemoveAt(i);
}
For lists, you can use RemoveRange to do exactly that. It’s just that instead of the (inclusive) end index, you need to pass the number of elements you want to delete. So for inclusive indexes start and end it would look like this:
list.RemoveRange(start, end - start + 1);
For arrays, you cannot really do this as once created arrays have a fixed size. If you really need an array, you could create a list from the array, remove the items, and then create an array again using ToArray.
As Yacoub Massad mentioned in his answer: You cannot delete items from an array. But you can create a new array that contains only the items that you want to keep. In this case I would use linq, it's super easy:
int[] array = {2, 6, 3, 6, 8, 2, 7, 2};
int x = 2;
int y = 4;
var array1 = array.ToList()
.Take(x).Concat(array.ToList().Skip(x+y-1))
.ToArray();
foreach(var i in array1)
{
Console.Write(i);
Console.Write(',');
}
Result:
2,6,2,7,2,
DotNetFiddle Example