C
C#2y ago
Rhythmic

❔ Most efficient way to retrieve a range of objects from an array

Imagine you have some sort of array/list/dict/whatever to store every note in a song. Each note has a note-timestamp (when it appears in the song). You also have a current-timestamp, which could be i.e. the current playback position in the song. What's an efficient way to find all notes within, say, a 5 second range of the current-timestamp? An example of an inefficient way would be to iterate through every note, evaluate whether its note-timestamp is within 5 seconds of the custom-timestamp, and then save a reference if it's true. The goal is to match up player input notes with a song's notes for a rhythm game. Every time an input is received, the game needs to find out which note to match up the input with, but I think it would be too inefficient to check every note every time. So I'm wondering if there's a better way.
12 Replies
mtreit
mtreit2y ago
If the data is sorted by timestamp you can use binary search
Rhythmic
Rhythmic2y ago
Alright, I managed to create and sort a List (List<HitNote>) for the data by timestamp. However, I don't quite understand how to use myList.BinarySearch() to find i.e. all the items with a timestamp within 5 seconds of the current-timestamp. myList is a list consisting of instances of class HitNote, which is a class that I have defined (has many properties). As I understand it, when I use myList.BinarySearch(item), it makes a search for this exact item. But I don't know what the exact item is, I just want to extract it if the timestamp condition is met.
mtreit
mtreit2y ago
You might need to pass a custom comparer that finds an item that meets your criteria, then you can take a window on either side of that item that matches the number of things you want... In other words: binary search to get you close without having to do a linear scan, then you will need to implement some logic to get exactly what you want from that rough part of the list.
Rhythmic
Rhythmic2y ago
I don't fully understand how to make a custom comparer like this. The way I understand what you're saying, the custom comparer is used to find the item that meets my criteria. So, in order for the custom comparer to find said item, it uses BinarySearch() . But the only way I know how to use BinarySearch is this: myList.BinarySearch(theItemImLookingFor) <-- So it seems like I need to have a reference to the item I'm looking for in order find its index. But how do I get a reference to the item without doing a linear search? I hope it's clear what confuses me. If my goal is to get an index and a reference to an item that matches the criteria, it seems that I need to already have a reference to said item to find it - which is a circularity.
mtreit
mtreit2y ago
There is an overload on the BinarySearch method that takes a comparer as the second parameter.
Rhythmic
Rhythmic2y ago
So if I understand it correctly, I need to define a new public class HitNoteComparer: IComparer<HitNote>{ public int Compare(HitNote HN1, HitNote HN2){ // algorithm that returns negative number if HN1 has an earlier timestamp than HN2, etc. } } Then pass myList.BinarySearch(theHitNoteImLookingFor, new HitNoteComparer()) ? If that's what I need to do, wouldn't I still need a reference to theHitNoteImLookingFor in order to find it with my own comparer? All I know is the current timestamp, I don't have a reference to any HitNote, I just have a list of HitNotes sorted by timestamp.
mtreit
mtreit2y ago
I don't see why. You can put whatever logic you want in your comparer. It will be passed a hit note and even if you only have a timestamp you can check if that timestamp is "close enough" that you return 0 to say you have a match. Now you have an index that is close to where you want, and it will be found very efficiently due to binary search. Binary search is just a technique to halve your search space on each iteration instead of having to scan using a linear search. Whatever logic you would use in a linear search still applies.
Rhythmic
Rhythmic2y ago
Hmm... For the first of BinarySearch's arguments - can I put in a HitNote instance that isn't necessarily in the myList? And then have a comparer that just finds the closes instance in the list? Because the problem that got me confused is that I need to know the item in order to find the item
mtreit
mtreit2y ago
Yes, to use the actual BinarySearch method on List you would pass in a dummy item with the timestamp you care about, I think. You could also always implement binary search yourself. I suggested it for the overall technique itself, not necessarily for exactly how it looks on List<T>.
Rhythmic
Rhythmic2y ago
Ah, I think I get it now I will try to implement it
Rhythmic
Rhythmic2y ago
I made it work. Thanks for the help
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.