Reforming integer range to a smaller integer range (Unity (C#)) - c#

So, i have a set of integers in an list
public List<int> numbers = new List<int>() { 3, 7, 6, 9, 8, 10, 11 }
what i am wanting to do is change those numbers so they are ordered between 0 and 6, to set as siblingindexs.
and then would be changed to become
public List<int> newArrangedNumbers = new List<int>() {0, 2, 1, 4, 3, 5, 6}
But im really not sure how to do this... Anyone know?
P.S. i cant rearrange the numbers because then i would lose track of the game objects since the numbers themselves aren't actually in an array, but i have gameobjects in an array, and i find the "SortIndex" of each gameobject, which are the numbers from above, the order of the numbers in the array is actually the order of GameObjects in the array, which i need to keep the same.
Edit: i also cannot change them to float values because for some reason, when using SetSiblingIndex(int), you have to integers, you cant use floats
Edit 2: i am NOT trying to sort the numbers, i am trying to CONVERT the numbers from 3-11 into 0-6 in ORDER
From:
{3, 7, 6, 9, 8, 10, 11}
To:
{0, 2, 1, 4, 3, 5, 6}
Edit 3: Here is my script for testing
List<int> Indexs = new List<int>() { 4, 7, 56, 9, 65, 67, 8, 3, 6 };
var sorted = Indexs.Select((x, i) => new KeyValuePair<int, int>(x, i)).OrderBy(x => x.Key).ToList();
List<int> newArrangedNumbers = sorted.Select(x => x.Value).ToList();
for(int i = 0; i < newArrangedNumbers.Count; i++)
{
Debug.Log(Indexs[i] + " : " + newArrangedNumbers[i]);
}
When i only had 7 (0-6) indexs in the "Indexs" List it worked fine, but when i added any more, it started giving me the incorrect numbers
This is what it gives with this

Here is a good method to achieve your desired output from this stack form C# Sort list while also returning the original index positions?
The modified code for your solution is below.
//The original list
List<int> numbers = new List<int>() { 3, 7, 6, 9, 8, 10, 11 };
var sorted = numbers
.Select((x, i) => new KeyValuePair<int, int>(x, i))
.OrderBy(x => x.Key)
.ToList();
//The sorted list
List<int> numbersSorted = sorted.Select(x => x.Key).ToList();
//List of indexes sorted based on the list above
List<int> newArrangedNumbers = sorted.Select(x => x.Value).ToList();
Edit
Since you sort the list, but also retrieve the sorted indexes based on the list you just sorted, you aren't going to have any mixup with your game objects.

It sounds like you want to find positions of each element in a sorted list of the same items.
So sort and find where element is and assign the index. Code sample below assumes unique numbers:
var sorted = numbers.OrderBy(x=>x).ToArray();
var result = new int[numbers.Count];
for (var i = 0; i < numbers.Count; i++)
{
var index = number.IndexOf(sorted[i]);
result[index] = i;
}
Notes
for anything about 5-10 items I'd use dictionary instead of IndexOf if you want to stick with this code.
if numbers are not unique or performance is critical you need to use solution by Aaron Jones that eventually will track original indexes even if it is harder to understand.

Related

Efficient way to get index to sorted distinct index array from a huge data array

To simplify the question suppose that I have an array of numbers like
[3, 7, 8, 3, 9, 9, ...]
Now I want to get an array of the index of
array.Distinct().OrderBy(x=>x)
For the example above we first get the result sorted array of [3, 7, 8, 9]. Then we can go through original array, find the index of the result sorted array, finally we get
[0, 1, 2, 0, 3, 3, ...]
This can be achieved as
var array = new[] {3, 7, 8, 3, 9, 9};
var sortedArray = array.Distinct().OrderBy(x => x).ToList();
var result = array.Select(x => sortedArray.IndexOf(x)).ToArray();
However when I have a HUGE array this will be extremely slow.
Is there a more efficient way to get the same result?
Note the sortedArray is huge too. (Data range is large)
Thanks.
You can convert the sorted array to a dictionary and search the index from it.
var i = 0;
var sortedDict = array.Distinct().OrderBy(x => x).ToDictionary(x => x, x => i++);
var result = array.Select(x => sortedDict[x]).ToArray();
If you want to save some memory spaces, you can also try BinarySearch.
var result = array.Select(x => sortedArray.BinarySearch(x)).ToArray();

PriorityQueue containing array C#

I would like to create a PriorityQueue to store int[]. The first element in the array is gonna be the criteria for the comparisons.
I could do that easily in Java, though I could not convert it to C#. Could you please guide me?
Priority queues don't work the same way in both languages. What you're trying to do is the Java way of giving PQ a lambda (function) to compare any two elements. In C#, you give each element a priority when adding it to the queue, and then make a comparer to compare different priorities.
PriorityQueue<int[], int> pq = new(Comparer<int>.Create((a, b) => a - b));
// The Comparer compares the *priorities*, not the elements
pq.Enqueue(new int[] { 1, 2, 3, 4 }, 5);
pq.Enqueue(new int[] { 1, 2, 3, 4 }, 0); // This has more priority
while (pq.TryDequeue(out int[]? arr, out int priority))
{
Console.WriteLine(priority); // 0; 5
}
You may be interested in just a simple List and LINQ:
using System.Linq; // at the top of your code to include LINQ
List<int[]> list = new();
list.Add(new int[] { 1, 2, 3, 4 });
list.Add(new int[] { 5, 2, 3, 4 });
IEnumerable<int[]> ordered = list.OrderBy(x => x[0]); // orders by the first element

Group elements of the data set if they are next to each other with LINQ

I have a data set (ex. 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7) and I want to group items of the same value but only if they are next to each other minimum 3 times.
Is there a way?
I've tried using combinations of Count and GroupBy and Select in every way I know but I can't find a right one.
Or if it can't be done with LINQ then maybe some other way?
I don't think I'd strive for a 100% LINQ solution for this:
var r = new List<List<int>>() { new () { source.First() } };
foreach(var e in source.Skip(1)){
if(e == r.Last().Last()) r.Last().Add(e);
else r.Add(new(){ e });
}
return r.Where(l => l.Count > 2);
The .Last() calls can be replaced with [^1] if you like
This works like:
have an output that is a list of lists
put the first item in the input, into the output
For the second input items onward, if the input item is the same as the last int in the output, add the input item to the last list in the output,
Otherwise make a new list containing the input int and add it onto the end of the output lists
Keep only those output lists longer than 2
If he output is like:
[
[2,2,2],
[6,6,6]
]
Aggregate can be pushed into doing the same thing; this is simply an accumulator (r), an iteration (foreach) and an op on the result Where
var result = source.Skip(1).Aggregate(
new List<List<int>>() { new List<int> { source.First() } },
(r,e) => {
if(e == r.Last().Last()) r.Last().Add(e);
else r.Add(new List<int>(){ e });
return r;
},
r => r.Where(l => l.Count > 2)
);
..but would you want to be the one to explain it to the new dev?
Another LINQy way would be to establish a counter that incremented by one each time the value in the source array changes compared to the pervious version, then group by this integer, and return only those groups 3+, but I don't like this so much because it's a bit "WTF"
var source = new[]{1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7};
int ctr = 0;
var result = source.Select(
(e,i) => new[]{ i==0 || e != source[i-1] ? ++ctr : ctr, e}
)
.GroupBy(
arr => arr[0],
arr => arr[1]
)
.Where(g => g.Count() > 2);
You could consider using the GroupAdjacent or the RunLengthEncode operators, from the MoreLinq package. The former groups adjacent elements in the sequence, that have the same key. The key is retrieved by invoking a keySelector lambda parameter. The later compares the adjacent elements, and emits a single KeyValuePair<T, int> for each series of equal elements. The int value of the KeyValuePair<T, int> represents the number of consecutive equal elements. Example:
var source = new[] { 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7 };
IEnumerable<IGrouping<int, int>> grouped = MoreLinq.MoreEnumerable
.GroupAdjacent(source, x => x);
foreach (var group in grouped)
{
Console.WriteLine($"Key: {group.Key}, Elements: {String.Join(", ", group)}");
}
Console.WriteLine();
IEnumerable<KeyValuePair<int, int>> pairs = MoreLinq.MoreEnumerable
.RunLengthEncode(source);
foreach (var pair in pairs)
{
Console.WriteLine($"Key: {pair.Key}, Value: {pair.Value}");
}
Output:
Key: 1, Elements: 1, 1
Key: 4, Elements: 4
Key: 6, Elements: 6
Key: 3, Elements: 3, 3
Key: 1, Elements: 1
Key: 2, Elements: 2, 2, 2
Key: 6, Elements: 6, 6, 6
Key: 7, Elements: 7
Key: 1, Value: 2
Key: 4, Value: 1
Key: 6, Value: 1
Key: 3, Value: 2
Key: 1, Value: 1
Key: 2, Value: 3
Key: 6, Value: 3
Key: 7, Value: 1
Live demo.
In the above example I've used the operators as normal methods, because I am not a fan of adding using MoreLinq; and "polluting" the IntelliSense of the Visual Studio with all the specialized operators of the MoreLinq package. An alternative is to enable each operator selectively like this:
using static MoreLinq.Extensions.GroupAdjacentExtension;
using static MoreLinq.Extensions.RunLengthEncodeExtension;
If you don't like the idea of adding a dependency on a third-party package, you could grab the source code of these operators (1, 2), and embed it directly into your project.
If you're nostalgic and like stuff like the Obfuscated C code contest, you could solve it like this.(No best practice claims included)
int[] n = {1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7};
var t = new int [n.Length][];
for (var i = 0; i < n.Length; i++)
t[i] = new []{n[i], i == 0 ? 0 : n[i] == n[i - 1] ? t[i - 1][1] : t[i - 1][1] + 1};
var r = t.GroupBy(x => x[1], x => x[0])
.Where(g => g.Count() > 2)
.SelectMany(g => g);
Console.WriteLine(string.Join(", ", r));
In the end Linq is likely not the best solution here.
A simple for-loop with 1,2,3 additional loop-variables to track the "group index" and the last value makes likely more sense.
Even if it's 2 lines more code written.
I wouldn't use Linq just to use Linq.
I'd rather suggest using a simple for loop to loop over your input array and populate the output list. To keep track of which number is currently being repeated (if any), I'd use a variable (repeatedNumber) that's initially set to null.
In this approach, a number can only be assigned to repeatedNumber if it fulfills the minimum requirement of repeated items. Hence, for your example input, repeatedNumber would start at null, then eventually be set to 2, then be set to 6, and then be reset to null.
One perhaps good use of Linq here is to check if the minimum requirement of repeated items is fulfilled for a given item in input, by checking the necessary consecutive items in input:
input
.Skip(items up to and including current item)
.Take(minimum requirement of repeated items - 1)
.All(equal to current item)
I'll name this minimum requirement of repeated items repetitionRequirement. (In your question post, repetitionRequirement is 3.)
The logic in the for loop goes a follows:
number = input[i]
If number is equal to repeatedNumber, it means that the previously repeated item continues being repeated
Add number to output
Otherwise, if the minimum requirement of repeated items is fulfilled for number (i.e. if the repetitionRequirement - 1 items directly following number in input are all equal to number), it means that number is the first instance of a new repeated item
Set repeatedNumber equal to number
Add number to output
Otherwise, if repeatedNumber has value, it means that the previously repeated item just ended its repetition
Set repeatedNumber to null
Here is a suggested implementation:
(I'd suggest finding a more descriptive method name)
//using System.Collections.Generic;
//using System.Linq;
public static List<int> GetOutput(int[] input, int repetitionRequirement)
{
var consecutiveCount = repetitionRequirement - 1;
var output = new List<int>();
int? repeatedNumber = null;
for (var i = 0; i < input.Length; i++)
{
var number = input[i];
if (number == repeatedNumber)
{
output.Add(number);
}
else if (i + consecutiveCount < input.Length &&
input.Skip(i + 1).Take(consecutiveCount).All(num => num == number))
{
repeatedNumber = number;
output.Add(number);
}
else if (repeatedNumber.HasValue)
{
repeatedNumber = null;
}
}
return output;
}
By calling it with your example input:
var dataSet = new[] { 1, 1, 4, 6, 3, 3, 1, 2, 2, 2, 6, 6, 6, 7 };
var output = GetOutput(dataSet, 3);
you get the following output:
{ 2, 2, 2, 6, 6, 6 }
Example fiddle here.

How to get the number of elements before the second last element in an array C#

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.

Deleting array elements between X and Y

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

Categories

Resources