Related
I intend to have a field of type List<int[]> that holds some int array (used in Unity to record some grid positions), looks roughly like:
{
{0, 0},
{0, 1},
{0, 2}
}
But when I try to remove elements from this list, it seems to be having some difficulty doing so:
int[] target = new int[] {0, 0};
// Simplified, in the actual code I have to do some calculations.
// But during debug, I confirmed that the same array I want to remove
// is in the intArrayList
intArrayList.Remove(target);
// {0, 0} seems to be still here
Is that supposed to happen? If so, how can I fix that?
The problem is that you are deleting a different instance of the same list. Since C# array uses the default equality checker, the instance needs to be the same in order for the other array to get removed.
A quick fix is to write your own method that searches the list for the appropriate array first, and then removing the item at that index:
var target = new int[] {0, 0};
var indexToRemove = intArrayList.FindIndex(a => a.SequenceEqual(target));
if (indexToRemove >= 0) {
intArrayList.RemoveAt(indexToRemove);
}
A good fix is to stop using the "naked" array: wrap it into your own class with an appropriate comparison semantic, and use that class instead. This would make your code more readable, give you more control over what goes into the array, and let you protect the array from writing if necessary. It will also let you use the original removal code, which is a small bonus on top of the other great things you are going to get for writing a meaningful wrapper on top of the array.
Use the correct data structure!
What you have is a little bit like an XY Problem. You have an issue with the attempted solution using a bad data structure for what you are actually trying to achieve.
If this is about Unity grid positions as you say, do not use int[] at all!
Rather simply use Vector2Int which already provides a structure for two coupled int values (coordinates) and implements all the necessary interfaces for successfully compare it for equality:
List<Vector2Int> yourList = new List<Vector2Int>()
{
new Vector2Int(0, 0),
new Vector2Int(0, 1),
new Vector2Int(0, 2),
...
}
var target = new Vector2Int(0, 1);
yourList.Remove(target);
Since Vector2Int implements IEquatable<Vector2Int> and GetHashCode these kind of operations on lists and dictionaries can be done implicit.
You are trying to remove the array by ref and most likely your intention is to remove it by value.
this should work:
intArrayList.RemoveAll(p => p[0] == 0 && p[1] == 0);
Another option is to use records instead of int[2] arrays since they have built-in implementation of value equality
record GridPosition(int X, int Y);
//...
List<GridPosition> intArrayList = new();
intArrayList.Add(new GridPosition(0, 0));
intArrayList.Add(new GridPosition(1, 0));
var target = new GridPosition(0, 0);
intArrayList.Remove(target);
Assert.AreEqual(1, intArrayList.Count);
Let's say I have the following nested array:
[
[1, 2, 3],
[4, 7, 9, 13],
[1, 2],
[2, 3]
[12, 15, 16]
]
I only need the arrays with the most occurrences of the same numbers. In the above example this would be:
[
[1, 2, 3],
[4, 7, 9, 13],
[12, 15, 16]
]
How can I do this efficiently with C#?
EDIT
Indeed my question is really confusing. What I wanted to ask is: How can I eliminate sub-arrays if some bigger sub-array already contains all the elements of a smaller sub-array.
My current implementation of the problem is the following:
var allItems = new List<List<int>>{
new List<int>{1, 2, 3},
new List<int>{4, 7, 9, 13},
new List<int>{1, 2},
new List<int>{2, 3},
new List<int>{12, 15, 16}
};
var itemsToEliminate = new List<List<int>>();
for(var i = 0; i < allItems.ToList().Count; i++){
var current = allItems[i];
var itemsToVerify = allItems.Where(item => item != current).ToList();
foreach(var item in itemsToVerify){
bool containsSameNumbers = item.Intersect(current).Any();
if(containsSameNumbers && item.Count > current.Count){
itemsToEliminate.Add(current);
}
}
}
allItems.RemoveAll(item => itemsToEliminate.Contains(item));
foreach(var item in allItems){
Console.WriteLine(string.Join(", ", item));
}
This does work, but the nested loops for(var i = 0; i < allItems.ToList().Count; i++) and foreach(var item in itemsToVerify) gives it a bad performance. Especially if you know that the allItems array can contain about 10000000 rows.
I would remember the items that are already in the list.
First sort your lists by decreasing length, then check for each item if it's already present.
Given your algorithm, the array is not added if even a single integer is in the list already of known integers already.
Therefore I would use the following algorithm:
List<List<int>> allItems = new List<List<int>>{
new List<int>{1, 2, 3},
new List<int>{4, 7, 9, 13},
new List<int>{1, 2},
new List<int>{2, 3},
new List<int>{12, 15, 16}
};
allItems = allItems.OrderByDescending(x => x.Count()).ToList(); // order by length, decreasing order
List<List<int>> result = new List<List<int>>();
SortedSet<int> knownItems = new SortedSet<int>(); // keep track of numbers, so you don't have to loop arrays
// https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1?view=netframework-4.7.2
foreach (List<int> l in allItems)
{
// bool allUnique = true;
foreach (int elem in l)
{
if (knownItems.Contains(elem))
{
// allUnique = false;
break;
}
else
{
// OK, because duplicates not allowed in single list
// and because how the data is constrained (I still have my doubts about how the data is allowed to look and what special cases may pop up that ruin this, so use with care)
// this WILL cause problems if a list starts with any number which has not yet been provided appears before the first match that would cause the list to be discarded.
knownItems.Add(elem);
}
}
// see comment above near knownItems.Add()
/*
if (allUnique)
{
result.Add(l);
foreach (int elem in l)
{
knownItems.Add(elem);
}
}
*/
}
// output
foreach(List<int> item in result){
Console.WriteLine(string.Join(", ", item));
}
Instead of looping over your original array twice nestedly (O(n^2)), you only do it once (O(n)) and do a search in known numbers (binary search tree lookup: O(n*log2(n))).
Instead of removing from the array, you add to a new one. This uses more memory for the new array. The reordering is done because it is more likely that any subsequent array contains numbers already processed. However sorting a large amount of lists may be slower than the benefit you gain if you have many small lists. If you have even a few long ones, this may pay off.
Sorting your list of lists by the length is valid because
what is to happen if a list has items from different lists? say instead of new List{2, 3} it was new List{2, 4}?
That unexpected behavior. You can see the ints as an id of a person. Each group of ints forms, for example, a family. If the algorithm creates [2, 4], then we are creating, for example, an extramarital relationship. Which is not desirable.
From this I gather the arrays will contain subsets of at most only one other array or be unique. Therefore the Order is irrelevant.
This also assumes that at least one such array would contain all elements of such subsets (and therefore be the longest one and come first.)
The sorting could be removed if it were not so, and should probably be removed if in doubt.
For example:
{1, 2, 3, 4, 5} - contains all elements that future arrays will have subsets of
{1, 4, 5} - must contain no element that {1,2,3,4,5} does not contain
{1, 2, 6} - illegal in this case
{7, 8 ,9} - OK
{8, 9} - OK (will be ignored)
{7, 9} - OK (will be ignored, is only subset in {7,8,9})
{1, 7} - - illegal, but would be legal if {1,2,3,4,5,7,8,9} was in this list. because it is longer it would've been earlier, making this valid to ignore.
Consider the following method of shuffling, given an array of objects a
Take the first element from a and place it into b. Consider the index of this element inside b to be x.
Place the second element from a and place it in front of b[x], so that it is now in position b[x-1]
Place the third element from a and place it behind b[x], so that it is now in position b[x+1]
Place the fourth element from a and place it in front of b[x - 1], so that it is now in position b[x-2]
Place the firth element from a and place it behind b[x+1] so that it is now in position b[x+2]
Repeat this process until b has all of the elements from a in it in this new shuffled order.
I wrote some code which does this, shown below. It will continuously shuffle the array in the above process until the shuffled array matches the original array, and then return the number of shuffles.
public class BadShuffler
{
public BadShuffler(object[] _arrayToShuffle)
{
originalArray = _arrayToShuffle;
Arrays = new List<object[]>
{
originalArray
};
}
private object[] originalArray;
private int count;
public List<object[]> Arrays { get; set; }
public int Shuffle(object[] array = null)
{
if (array == null)
array = originalArray;
count++;
object[] newArray = new object[array.Length];
bool insertAtEnd = false;
int midpoint = newArray.Length / 2;
newArray[midpoint] = array[0];
int newArrayInteger = 1;
int originalArrayInteger = 1;
while (newArray.Any(x => x == null))
{
if (insertAtEnd)
{
newArray[midpoint + newArrayInteger] = array[originalArrayInteger];
newArrayInteger++;
}
else
{
newArray[midpoint - newArrayInteger] = array[originalArrayInteger];
}
originalArrayInteger++;
insertAtEnd = !insertAtEnd;
}
Arrays.Add(newArray);
return (newArray.All(x => x == originalArray[Array.IndexOf(newArray, x)])) ? count : Shuffle(newArray);
}
}
While not being the prettiest thing in the world, it does the job. Example shown below:
Shuffled 6 times.
1, 2, 3, 4, 5, 6
6, 4, 2, 1, 3, 5
5, 1, 4, 6, 2, 3
3, 6, 1, 5, 4, 2
2, 5, 6, 3, 1, 4
4, 3, 5, 2, 6, 1
1, 2, 3, 4, 5, 6
However, if I give it an array of [1, 2, 3, 3, 4, 5, 6] it ends up throwing a StackOverflowException. When debugging, however, I have found that it does actually get to a point where the new shuffled array matches the original array, as shown below.
This then goes on to call Shuffle(newArray) again, even though all values in the array match each other.
What is causing this? Why does the Linq query newArray.All(x => x == originalArray[Array.IndexOf(newArray, x)]) return false?
Here is a DotNetFiddle link, which includes the code I used to print out the result(s)
You are comparing objects. objects are compared using referential equality with ==, not value equality. Your example uses numbers, but those numbers are boxed to an object implicitly due to the way your code is laid out.
To avoid this, you should use the .Equals() function (when comparing Objects).
newArray.All(x => x.Equals(originalArray[Array.IndexOf(newArray, x)]))
You should also use generics in your class instead of littering object[] everywhere to ensure type safety - unless one of your aims with this shuffler is to allow the shuffler to shuffle arrays of mixed types (which seems doubtful since it would be hard to extract any useful information out of that).
Note that this behaviour is exhibited whenever you are comparing reference types; one way to only allow value types to be passed to your structure (i.e, only primitive values that can be compared by value equality rather than referential equality) is to use the struct generic constraint. As an example:
class BadShuffler<T> where T : struct
{
public bool Shuffle(T[] array)
{
...
return newArray.All(x => {
var other = originalArray[Array.IndexOf(originalArray, x)];
return x == other;
});
}
}
This would work as you expect.
SequenceEqual as mentioned in the comments is also a good idea, as your .All() call will say that [1, 2, 3] is equal to [1, 2, 3, 4], but [1, 2, 3, 4] will not be equal to [1, 2, 3] - both of these scenarios are incorrect and more importantly not commutative[1], which equality operations should be.
Just make sure you implement your own EqualityComparer if you go beyond using object[].
That said, I think you want to use a combination of both approaches and use SequenceEqual with my approach, unless you need to shuffle objects (I.e, a Deck of Cards) rather than numbers?
As a side note, I would generally recommend returning a new, shuffled T[] rather than modifying the original one in-place.
[1]: Commutative means that an operation done one way can be done in reverse and you get the same result. Addition, for example, is commutative: you can sum 1, 2 and 3 together in any order but the outcome will always be 6.
int[] listOfValues = {1, 2, 5, 2, 6};
I need to be able to find all pair combinations of this array, including repetitions. Each value in the array comes from a deck of cards. So, if the value "2" appears twice in the array, for example, we can assume that these are two different values, and as such, need to be treated separately.
Sample of expected pairs of cards:
{1, 2}
{1, 2}
{2, 1}
{2, 1}
{2, 2}
{1, 5}
{1, 6}
etc.......
These separate int[] results will then need to be added to a List (if you can even add duplicate int[] values to a list, that is!), once all possible values have been found.
I have looked for a few hours online, and can't seem to get any of the solutions working for my particular task.
Does anyone have any ideas please?
You should really do homework on your own. Or at least try it first. You haven't provided code, so I cannot ethically give you the full solution.
However, this will get you started:
Think about it as if you were to do it by hand. Most people would pick the first value and the second value and write them down. Then they would write that pair backwards. Then they would do the first value and the third, then backwards, and so on and so on.
It would look something like:
{1,2}
{2,1}
{1,5}
{5,1}
{1,2}
{2,1}
{1,6}
{6,1}
{2,5} -Now we iterate again, starting with the second value
So how would we express that in code? Nested loops!
Here is the skeleton of an algorithm to solve your problem:
List<int[]> pairs = new List<int[]>();
for(int x = 0; x < listOfValues.Length - 1; x++)
{
for(int y = x+1; y < listOfValues.Length; y++)
{
// Create an array of the [x] position and [y] position of listOfValues
// Create another array, except swap the positions {[y],[x]}
// Add both arrays to the "pairs" List
}
}
Try to understand what this code is doing. Then fill in the blanks. You should get the correct answer. Always make sure to understand why, though. Also, try to see if you can figure out any improvements to this code.
With linq you could do it this way.
int[] listOfValues = { 1, 2, 5, 2, 6 };
var combination = listOfValues.Select(i => listOfValues.Select(i1 => new Tuple<int, int>(i, i1)).ToList())
.ToList()
.SelectMany(list => list.Select(x => x)).ToList();
With thanks to Clay07g's post, I was able to resolve the problem with the following code:
public static List<int[]> getCardCombos(int[] values)
{
List<int[]> pairs = new List<int[]>();
for (int x = 0; x < values.Length - 1; x++)
{
for (int y = x + 1; y < values.Length; y++)
{
int firstValue = values[x];
int secondValue = values[y];
// Create an array of the [x] position and [y] position of listOfValues
int[] xAndY = { firstValue, secondValue};
// Create another array, except swap the positions {[y],[x]}
int[] yAndX = { secondValue, firstValue };
pairs.Add(xAndY);
pairs.Add(yAndX);
// Add both arrays to the "pairs" List
}
}
return pairs;
}
I know that they are used to store data, but I have a difficult time understanding exactly how to use them in a program.
In addition, I found this site with a tetris clone tutorial (the actual tutorial is missing). It uses arrays, but I can't really make sense of how it works.
Here's an example of the code -
public int[, ,] TShape = new int[4, 4, 2]
{
{{1, 0}, {0, 1}, {1, 1}, {2, 1}},
{{1, 0}, {0, 1}, {1, 1}, {1, 2}},
{{0, 0}, {1, 0}, {2, 0}, {1, 1}},
{{0, 0}, {0, 1}, {1, 1}, {0, 2}}};
Could it be that I'm looking too hard into this, or perhaps there's something I'm not grasping about it?
It would be clearer if formatted this way:
public int[, ,] TShape = new int[4, 4, 2]
{
{ {1, 0}, // <- this is int[2]
{0, 1},
{1, 1},
{2, 1} }, // <- the last four lines are an int[4,2]
{ {1, 0},
{0, 1},
{1, 1},
{1, 2} }, // <- another int[4,2]
{ {0, 0},
{1, 0},
{2, 0},
{1, 1} }, // <- third int[4,2]
{ {0, 0},
{0, 1},
{1, 1},
{0, 2} } // <- fourth and last int[4,2]
}; // <- The whole thing is int[4, 4, 2]
"Well I've been having a hard time
understanding how to use arrays. I
know they are used for storing data,
but I have yet to find any resource
that gives a clearer explanation than
that."
I'll try to give you an analogy: array is to programming like a file cabinet is to an office. The only difference is that a file cabinet is restricted to what it can hold: i.e. files... the only restriction for arrays is that it must hold items of the same type, but the actual type of the item can be nearly anything. Arrays can not only hold data, but objects, other arrays, other containers, etc.
So what can you do with an array in programming? Well a LOT of stuff! Let's look at several examples:
Financial: an array can hold the stock prices for today.
Gaming: an array can hold the 3D models that are used on a level.
Military: an array can hold all of the targets identified by a targeting system.
etc...
The main purpose of arrays is to contain various things and allow us to iterate over them. For example:
// Financial
int[] stockPrices = new int[4]{ 15, 14, 18, 16 }; // contains four prices
foreach( int price in stockPrices )
{
MakeTransaction(price);// calls a function that makes a transaction at the price: e.g. buy/sell
}
// Gaming
3DModel[] gameModels = new 3DModel[4]{ new Tank(), new Truck(), new Soldier(), new Building()}; // contains 3D models
foreach( 3DModel model in gameModels )
{
model.Draw();// calls a function of each 3DModel that draws the model on the screen
}
// Military
Target[] targets = new Target[4]{ new Tank(), new Helicopter(), new APC(), new Truck()}; // contains targets
foreach( Target target in targets )
{
Attack(target);// calls an attack function which initiates an attack on each target
}
The possibilities are endless! Arrays are a subset of containers and containers are an integral part of programming. Does that help?
Straight from the horse's mouth: http://msdn.microsoft.com/en-us/library/system.array.aspx
Those details will come in handy after you have gone through online tutorials such as this http://www.functionx.com/csharp/Lesson21.htm
Imagine you have 10 boxes. Now you want to clearly tell someone which box you are talking about. So you assign numbers to them, from 0 to 9. What you have now is an array of boxes, defined as:
box[ 10 ]
The number in the brackets tells you, how many of them are there. By default they are numbered from 0 to 9 (some languages allow to change this, but lets skip that for now). So if you are talking about the fifth box, its box[ 4 ] (since we index from 0!). Now imagine you open the box and see that there are five balls in it. This defines an array:
box[ 10 ][ 5 ]
A two dimensional array. How do you tell your friend which ball do you want to talk about? Again, you number them, this time from 0 to 4. How do you specify say the third ball in the seventh box? Simple: box[ 6 ][ 2 ]. And so on. Hope that helps.
In your question, you're stating the Tetris game which uses an array. That is a good use of arrays in whatever language you're using.
You have to see the Tetris play yard as the array. Let's say this play yard is a 20 spaces on the X axis, and 100 spaces on the Y axis. What tells you whether you have a piece on a space, is when you have the integer value 1, and you get an integer value of 0 for empty spaces. We then get:
var tetrisPlayYard = new int[20][100];
You now have to initialize each positions of the board to 0 in order to mark them as empty spaces, so no pieces have ever been placed already.
for(int xAxisIndex = 0; xAxisIndex < tetrisPlayYard.GetLength(0); ++xAxisIndex )
for (int yAxisIndex = 0; yAxisIndex < tetrisPlayYard.GetLength(1); ++ yAxisIndex)
tetrisPlayYard[xAxisIndex][yAxisIndex] = 0;
We now know that no pieces is on board as each array's positions are set to 0.
Then, when we place, let's say a four spaces straight line horizontally at the bottom-right of the board, we would then have to address 4 spaces, and set their values to 1 to indicate there is a piece on these spaces.
tetrisPlayYard[19][99] = 1;
tetrisPlayYard[18][99] = 1;
tetrisPlayYard[17][99] = 1;
tetrisPlayYard[16][99] = 1;
This tells that you have a four spaces straight line. Your algorithm should work around this logic to display the bars, the cubes, etc. while playing the game.
In conclusion, use arrays whenever a finite space is known, and that you know that it won't be required to resize it dynamically. Otherwise, a Collection should be used.