1

I am creating an application which uses the AMB MyLaps decoder P3 Protocols.

I can't get my head around a way to sort the racers out based on laps and lap times. For example, the person in 1st has done 3 laps, the person in 2nd has done 2 laps. But then how do I order a situation where 2 people are on the same lap?

This is the record I'm using to hold the information:

type
  TTimingRecord = record
    position: integer;
    transId: integer;
    racerName:  string;
    kartNumber: integer;
    lastPassingN: integer;
    laps: integer;
    lastRTCTime:  TDateTime;
    bestTimeMs: Extended;
    lastTimeMs: Extended;
    gapTimeMs:  Extended;
    splitTimeMs:  Extended;
    timestamp:  TDateTime;
  end;

A new record is created for each racer.

The code I'm currently using is:

procedure sortRacers();
var
  Pos, Pos2: Integer;
  Temp: TTimingRecord;
  GapTime: Extended;
begin
  for Pos := 0 to length(DriversRecord)-1 do
  begin
    for Pos2 := 0 to Length(DriversRecord)-2 do
    begin
      if(DriversRecord[Pos2].laps < DriversRecord[Pos2+1].laps)then
      begin
        Temp := DriversRecord[Pos2];
        DriversRecord[Pos2] := DriversRecord[Pos2+1];
        DriversRecord[Pos2+1] := Temp;
      end
      else if DriversRecord[Pos2].laps = DriversRecord[Pos2+1].laps then
           begin
             if DriversRecord[Pos2].lastRTCTime > DriversRecord[Pos2+1].lastRTCTime then
             begin
              Temp := DriversRecord[Pos2];
              DriversRecord[Pos2] := DriversRecord[Pos2+1];
              DriversRecord[Pos2+1] := Temp;
             end;
           end;
    end;
  end;

  for pos := 1 to length(DriversRecord) -1 do   //Gap Time
  begin
    if DriversRecord[Pos].laps = DriversRecord[0].laps then
    begin
      DriversRecord[Pos].gapTimeMs := DriversRecord[Pos].lastRTCTime - DriversRecord[0].lastRTCTime;

      DriversRecord[Pos].splitTimeMs := DriversRecord[Pos].lastRTCTime - DriversRecord[Pos-1].lastRTCTime;
    end;
  end;
end;

But doesn't work too well :)

ain
  • 22,394
  • 3
  • 54
  • 74
Jake Evans
  • 978
  • 5
  • 13
  • 33
  • 1
    First of all you need to separate sorting from comparing. Write (or find) a method to sort. Then write a method to compare. Then put the two together. You'll get nowhere blending them together like this. – David Heffernan Apr 02 '13 at 13:44
  • So I rewrote the sort method using a simple bubble sort... `procedure sortRacers2(); var i,x: integer; Temp: TTimingRecord; begin for i := 0 to length(DriversRecord)-2 do begin for x := i to length(DriversRecord)-1 do begin if DriversRecord[i].laps < DriversRecord[x].laps then begin Temp := DriversRecord[i]; DriversRecord[i] := DriversRecord[x]; DriversRecord[x] := Temp; end; end; end; end;` How would I now go about taking into consideration those on the same lap using lastRTCTime? – Jake Evans Apr 02 '13 at 14:11

4 Answers4

2

I'm assuming from your comment to the question, that you have decomposed the problem into sorting and comparing, and that you have got the sorting part covered. Which leaves order comparison.

You need a function that will perform a lexicographic order comparison based first on the number of laps completed, and secondly on the time since the start of this lap. Basically it will look like this:

function CompareRacers(const Left, Right: TTimingRecord): Integer;
begin
  Result := CompareValue(Left.laps, Right.laps);
  if Result=0 then
    Result := CompareDateTime(Left.lastRTCTime, Right.lastRTCTime);
end;

You'll find CompareValue in Math and CompareDateTime in DateUtils.

What I'm not sure about is what the sense of the lastRTCTime values is. You may need to negate the result of the call to CompareDateTime to get the result you desire.

Result := -CompareDateTime(Left.lastRTCTime, Right.lastRTCTime);

Also, what happens if there is overtaking during the lap? Presumably you won't be able to detect that until the racers complete the current lap.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • David, thanks for your input. I probably should of elaborated a bit more. There is one "detection loop" on the track, so yes, in terms of overtaking, it won't be until the end of that lap in which it is sorted. lastRTCTime is a dateTime stamp which indicates when the racer last crossed the detection loop. So if two people are on lap 2, the person who crossed the line first will be in front. – Jake Evans Apr 02 '13 at 17:21
  • In that case you'll need to negate the sign as I mentioned. I think you've got an answer now. Is there anything more that you need? – David Heffernan Apr 02 '13 at 17:33
  • Yes I've just implemented it and it works.. almost! The array of TimingRecords is in ascending order, and it needs to be in descending. I presume this is down to my sort loop? – Jake Evans Apr 02 '13 at 17:48
  • No. Just reverse the sign of the value returned by the compare function. – David Heffernan Apr 02 '13 at 18:02
1

Instead of doing the sort algorithm yourself, try this technique (if you have a Delphi version compatible) : Best way to sort an array

And your function could look like this :

uses Types;

function CustomSort(const Left, Right: TTimingRecord): Integer
begin
  If (left.laps > right.laps) then
     result := GreaterThanValue
  else 
  if (left.laps < right.laps) then
      result := LessThanValue
  else 
  begin
      // Same laps count... Test on LastRTCTime 
      if (left.lastRTCTime < right.lastRTCTime) then
          result := GreaterThanValue1
      else
      if (left.lastRTCTime > right.lastRTCTime) then
          result := LessThanValue
      else
          result := EqualsValue; 
  end;
end));
Community
  • 1
  • 1
Greg M.
  • 229
  • 1
  • 9
0

It might be easier to look at this as 2 separate sorts.

Obviously you know the bubble-sort method, so I will not go into that.

Make 2 passes on your sorting. 1st, you sort the laps.

2nd, you run through the entire list of sorted laps. find begin point and end point in array for identical lap-values. Do the sorting again from begin and end points, but this time compare only the secondary value. iterate through all identical secondary values if the count of identical values are larger than 1.

  • That's way more complex than single pass – David Heffernan Apr 04 '13 at 00:07
  • One thing about making computer programs is to make reuseable code. Any specialized routine for only one kind of sorting with secondary sorting is not likely to be used again at a later stage.> That slows progress and new development down. By making one working routine for sorting arrays, this routine can also be reused for the second pass, and all later needs for sorting. – Morten Brendefur Apr 07 '13 at 21:25
  • What do you mean? Secondary compare is much easier in comparison function. Your way involves writing a sort that requires knowledge of the primary compare in order to perform the secondary sort. Try and sketch it out. – David Heffernan Apr 07 '13 at 21:48
  • Hmmm.. My answer would not fit in here. I will try to add another answer :) – Morten Brendefur Apr 08 '13 at 03:01
0

This code is about sorting data using an Index. Way faster than bubble-sort. It is dynamic and provides for ability to sort from a start-point to an end-point in an array. The code itself is bigger than Bubble-Sort, but not many algorithms can compare on speed. The code (when understanding how it works) can easily be modified to suit most kinds of sorting. On an array of 65536 strings, it only need to do 17 compares (or there about) Some more CPU Cycles per compare cycle compared with Bubble Sort, but still among the fastest methods. To search is equally as fast as BTREE. The actual sorting is perhaps slower, but the data is easier manageable afterwards with no real need for balancing the tree.

Enjoy.

Note: The routine is not the full solution to the actual problem, but it provides the beginning of an extreemely fast approach.


TYPE
  DynamicIntegerArray = ARRAY OF INTEGER;
  DynamicStringArray  = ARRAY OF STRING;

VAR
  BinSortLo, BinSortMid, BinSortHi : INTEGER; 

FUNCTION FindMid:INTEGER;
  BEGIN
    FindMid:=BinSortLo+((BinSortHi-BinSortLo) DIV 2);
  END;

PROCEDURE ShiftIndexUpAndStorePointer(VAR ArrParamIndex: DynamicIntegerArray; HighBound:INTEGER);
  VAR
    x : INTEGER;
  BEGIN
    FOR x:=HighBound-1 DOWNTO BinSortMid DO ArrParamIndex[x+1] := ArrParamIndex[x];// Shift the index.
    ArrParamIndex[BinSortMid]:=HighBound;// Store the pointer to index at its sorted place
  END;

PROCEDURE BinarySortUp(CONST ArrParam:DynamicStringArray; VAR ArrParamIndex: DynamicIntegerArray; CONST LoBound,HighBound:INTEGER); OVERLOAD;
  VAR
    TempVar : STRING;
  BEGIN
    BinSortLo:=LoBound; BinSortHi:=HighBound; BinSortMid:=FindMid;
    TempVar := ArrParam[HighBound];
    REPEAT
  IF TempVar>ArrParam[ArrParamIndex[BinSortMid]] THEN BinSortLo:=BinSortMid ELSE BinSortHi:=BinSortMid;
  BinSortMid:=FindMid;
    UNTIL (BinSortMid=BinSortLo); {OR (BinSortMid=BinSortHi);}
    IF TempVar>ArrParam[ArrParamIndex[BinSortMid]] THEN INC(BinSortMid);// We always need a last check just in case.
    ShiftIndexUpAndStorePointer(ArrParamIndex,HighBound);
  END;

PROCEDURE DynamicCreateIndex(CONST ArrParam:DynamicStringArray; VAR ArrParamIndex: DynamicIntegerArray; CONST LoBound,HighBound:INTEGER);
  VAR
    x : INTEGER;
  BEGIN
    FOR x:=LoBound TO HighBound DO
    BinarySortUp(ArrParam,ArrParamIndex,LoBound,x);
  END;


BEGIN
{
1. Create your STRING ARRAY as a DynamicStringArray. 
2. Create your INDEX ARRAY as a DynamicIntegerArray.
3. Set the size of these arrays to any INTEGER size and fill the strings with data.
4. Run a call to DynamicCreateIndex(YourStringArray,YourIndexArray,0,SizeOfArray
Now you have a sorted Index of all the strings.

}
END.
  • How can it be possible to sort 65536 strings with only 17 comparisons? You cannot possibly mean that. – David Heffernan Apr 08 '13 at 19:15
  • Sorry. I realize that my claim was a bit vague and not detailed enough. What I mean is that the routine need only 17 comparisons in order to add one more string into an already indexed array of 65536 strings. It starts off using very few compareson the actual data. The fewer strings to sort, the fewer comparisons. The unmodified bubblesort need about 65535 comparisons on an array of 256 strings (before everything is sorted). the routine presented here (my own) will need 1-8 compares per string, sorting 256 strings in about 1023 comparisons (not really counted, just educated guess). – Morten Brendefur Apr 08 '13 at 21:03
  • It's insertion sort? Why didn't you say so? Nobody thinks bubble sort is any good. Quicksort would be my choice here. But again this answer misses the point by a long way. – David Heffernan Apr 08 '13 at 21:09
  • I don't put my routine into a fixed category as it is an incredible fast universal approach for all kinds of sorting. Although not having tested it against a good implementation of QuickSort, I think it outperforms QuickSort. My answer does not miss the point. It just does not spell it out. The storage of data makes this routine usable as it is with comparing arrays of strings. When storing the laps and times, store them as a string like "lap,hh:mm:ss:hs". Make sure the lapnumber always have 3 characters. 001, 002 etc. Being insertion sort or not, it can sort anything :) – Morten Brendefur Apr 09 '13 at 00:19
  • Insertion sort is typically slower than quicksort. Restricting use to strings is rather limiting. It means you cannot use it to solve the problem in this question directly. Instead of trying to teach us, why don't you read the accepted answer and try to learn a new trick. – David Heffernan Apr 09 '13 at 07:23
  • 1. I never disagreed with the accepted answer. 2. I only provided a second solution. A solution that is all my own work, as in one of the fastest implementations of "Insert Sort" available. 3. For this kind of sorting where laps are driven, and data are updated one record at a time, "Insert Sort" is way faster than QuickSort. 4. The suggestion of adding the data to a String was for the simple approach only. It is only one of my tricks. As for learning new tricks. I was done using BubbleSort 30 years ago. I have written a variation of "Insert Sort" that "always" outperform QuickSort :) – Morten Brendefur Apr 09 '13 at 10:10
  • You need to use name for me to get notified. I'm not sure that data are updated one record at a time. How can you tell that? I am sure that performance of the sort is not critical. How many racers do you think there are? And the question was about sorting on primary and secondary key. And the easiest way to do that is with a pass sort. – David Heffernan Apr 10 '13 at 22:49
  • @DavidHeffernan As all the racers can not complete the race instantly, the data have to be somehow added electronically by sensors or manually by a person typing in the results. No matter which way one look at it, this is One record at a time as the results ticks in. Mind you. The routine I provided can easily be modified to build an Index from the results of the accepted answer. It is only to use that array and function instead of my compare of the strings. Then you have the fast implementation of "insert" sort and the use of original data-record. + the index. – Morten Brendefur Apr 11 '13 at 02:18
  • On small amount of data to be moved back and forth by "QuickSort", I do agree that QuickSort is a good choice. However, When there is a lot of more data in a record, then the fastest approach would be to use an index. On todays computers it does not really mean much. They are fast enough to do this kind of sorting millions of times over, almost no matter which routine one uses on such a limited dataset a race would provide. – Morten Brendefur Apr 11 '13 at 02:26