0
\$\begingroup\$

No, I'm not talking about foreach. I have the following list:

public List<float> angles = new List<float>(new float[] { 0, 90, 180, 270 });

This list can vary depending on the object I use, having only 0 and 90 or all except 270 for example.

What I want to do is have a rotClockwise and rotCounterClockwise function that grabs the value before or after a given value. Here's some pseudo code:

function float rotClockwise(angle) {
    return angles.grabNextAngle(angle);
}

function float rotCounterClockwise(angle) {
    return angles.grabPrevAngle(angle);
}

So calling rotClockwise(90) would return 180, rotCounterClockwise(90) would return 0, rotClockwise(270) would return 0, etc.

How can I accomplish this? Is there a list type that allows me to loop back to the beginning when I reached the end?

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

AFAIK there is no class in the C# standard which works like a circular list. However, it is quite easy to build such a class yourself:

public class RotatingFloatList {

    private int current = 0;
    private float[] values = new float[] { 0, 90, 180, 270 };

    public float GetCurrent() {
        return values[current];
    }

    public float GetNext() {
        current++;
        if (current >= values.Length) current = 0;
        return GetCurrent();
    }   

    public float GetPrevious() {
        current--;
        if (current < 0) current = values.Length - 1;
        return GetCurrent();
    }
}

Try it online!

Exercises for the reader to make this class more reusable:

  • Have it take the values from a constructor.
  • Turn this into a generic class so it can be used for other things than floats.
\$\endgroup\$
1
\$\begingroup\$

Add extra values so it seems to wrap and use IndexOf in the correct range to avoid overflow.

public List<float> angles =new List<float>(new float[] { 0, 90, 180, 270, 0 });

float nextAngle(angle){
    return angles[angles.indexOf(angles, 0) + 1]; //looks from the start and 
    //will never pick the last element because it's a duplicate of an earlier one
}

float previousAngle(angle){
    return angles[angles.indexOf(angles, 1) - 1]; // looks from index 1 and will never pick 0
}

However using floats like that isn't ideal because if you ever get a rounding error then indexOf will return -1.

Using an enum NORTH, EAST, SOUTH, WEST for this is a better idea with a conversion to the actual angle or orientation when needed.

\$\endgroup\$
0
\$\begingroup\$

A circular buffer is simple to implement, you can do it as Philipp suggest or you may - for example - use index % array.Length to query (unless the index is negative).

Remember that accesing a negative index of the array is an IndexOutOfRangeException.

However, what you want is something slighly different, since you want to find the previous and next items given a value.

Assuming we start with a sorted array, we can do this with Array.BinarySearch. We will not use IndexOf for reasons that will become clear later.

First, our array:

var array = new float[] { 0, 90, 180, 270 };

And what we are going to be looking for:

var query = 10;

I will add rotClockwise and rotCounterClockwise to store the adjacent values...

float rotClockwise;
float rotCounterClockwise;

Then, we execute the Array.BinarySearch...

float rotClockwise;
float rotCounterClockwise;
var searchResult = Array.BinarySearch(array, query);

Ah, but it would return negative if it is not found, we need to check that...

float rotClockwise;
float rotCounterClockwise;
var searchResult = Array.BinarySearch(array, query);
if (searchResult < 0)
{
    // ...
}
else
{
    // ...
}

If the result is positive, it is easy, just add or substract 1 to searchResult:

float rotClockwise;
float rotCounterClockwise;
var searchResult = Array.BinarySearch(array, query);
if (searchResult < 0)
{
    // ...
}
else
{
    rotClockwise = array[(searchResult + 1) % array.Length];
    rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
}

Now, when it is negative...

Remember that accesing a negative index of the array is an IndexOutOfRangeException.

Array.BinarySearch is actually telling us where we would, in theory, insert the new value... (This is why we do not use IndexOf) we have to start by getting the bitwise complement of the result:

float rotClockwise;
float rotCounterClockwise;
var searchResult = Array.BinarySearch(array, query);
if (searchResult < 0)
{
    searchResult = ~searchResult;
    // ...
}
else
{
    rotClockwise = array[(searchResult + 1) % array.Length];
    rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
}

Now, since that is the index where it would be inserted, it is also the index of the first item that is larger than the query... unless the query is larger than all, in which case we want to get the first item. In either case index % array.Length will do.

Ah, and for the last item larger than the query, we just substract 1.

float rotClockwise;
float rotCounterClockwise;
var searchResult = Array.BinarySearch(array, query);
if (searchResult < 0)
{
    searchResult = ~searchResult;
    rotClockwise = array[searchResult % array.Length];
    rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
}
else
{
    rotClockwise = array[(searchResult + 1) % array.Length];
    rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
}

And there you go. Well, almost, the code is not aware that these are angles...

You can transform the input like this (not optimized):

if (query < 0)
{
    query += (1 -(int)(query / 360)) * 360;
}
if (query >= 360)
{
    query -= (int)(query / 360) * 360;
}

... and optimize, error check, encapsulate as needed.


I have adapted the code like this:

public static void Main()
{
    var array = new float[] { 0, 90, 180, 270 };
    var queries = new float[] {-1000, -15, 0, 1, 10, 90, 95, 300, 270, 780};
    foreach (var item in queries)
    {
        var query = item;
        if (query < 0)
        {
            query += (1 -(int)(query / 360)) * 360;
        }
        if (query >= 360)
        {
            query -= (int)(query / 360) * 360;
        }
        var tuple = Rot(array, query);
        Console.WriteLine(item + " = " + query + " Clockwise: " + tuple.Item1 + " CounterClockwise: " + tuple.Item2);
    }
}

public static Tuple<float, float> Rot(float[] array, float query)
{
    float rotClockwise;
    float rotCounterClockwise;
    int searchResult = Array.BinarySearch<float>(array, query);;
    if (searchResult < 0)
    {
        searchResult = ~searchResult;
        rotClockwise = array[searchResult % array.Length];
        rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
    }
    else
    {
        rotClockwise = array[(searchResult + 1) % array.Length];
        rotCounterClockwise = array[searchResult == 0 ? array.Length - 1 : searchResult - 1];
    }
    return new Tuple<float, float>(rotClockwise, rotCounterClockwise);
}

Output:

-1000 = 80 Clockwise: 90 CounterClockwise: 0
-15 = 345 Clockwise: 0 CounterClockwise: 270
0 = 0 Clockwise: 90 CounterClockwise: 270
1 = 1 Clockwise: 90 CounterClockwise: 0
10 = 10 Clockwise: 90 CounterClockwise: 0
90 = 90 Clockwise: 180 CounterClockwise: 0
95 = 95 Clockwise: 180 CounterClockwise: 90
270 = 270 Clockwise: 0 CounterClockwise: 180
300 = 300 Clockwise: 0 CounterClockwise: 270
360 = 0 Clockwise: 90 CounterClockwise: 270
365 = 5 Clockwise: 90 CounterClockwise: 0
780 = 60 Clockwise: 90 CounterClockwise: 0

Try it online!

Addendum:

Of course, this code can be made generic. You may not want to deal with float. You may also be interested in supporting an IComparer<T> interface, there you can check with your prefered method of comparing floating point numbers.

\$\endgroup\$

You must log in to answer this question.