JavaScript Algorithms – Efficient Algorithm to Sort HTML List of Players

algorithmsclient-sidehtmljavascript

We have list of players, which is represented in form like this:

<div class="players_list">
   <div class="player" data-points="500">
        <div class="name">Player1</div>
        <div class="position">1</div>
   </div>     
   <div class="player" data-points="400">
        <div class="name">Player2</div>
        <div class="position">2</div>
   </div>
   <div class="player" data-points="300">
        <div class="name">Player3</div>
        <div class="position">3</div>
   </div>
   <div class="player" data-points="200">
        <div class="name">Player4</div>
        <div class="position">4</div>
   </div>  
</div> 

Something happens, and Player 4 recieves 600 points. So it goes on 1st place.
With him – player3 recieves 300 points and goes on 2nd place.

It happens after same event – let's assume, that they get these points from server, but that's all load that is allowed on server and server does not calculate positions.

What is most efficent way to get view like this:

<div class="players_list">
   <div class="player" data-points="800">
        <div class="name">Player4</div>
        <div class="position">1</div>
   </div>
   <div class="player" data-points="700">
        <div class="name">Player3</div>
        <div class="position">2</div>
   </div>

   <div class="player" data-points="500">
        <div class="name">Player1</div>
        <div class="position">3</div>
   </div>     
   <div class="player" data-points="400">
        <div class="name">Player2</div>
        <div class="position">4</div>
   </div>
</div> 

It's not question about code, it's question about:

  • What kind of task it is? Just sort?
  • Is it ok to use JavaScript to solve this task if lists are less than 500 rows?
  • Is it ok to re-render whole players list, or there are some ways to just change div element position(index in DOM or something like this)?
  • Are there any tools and libraries around, that can help solve this task efficiently?

P.S.: If we want to animate all this story – we should create a queue of animation tasks each time scores for all players recalculates?

Update:

I have followed @Jalayn's answer, and stuck on following problem:

Let's assume that list [1,2,3,4] transforms to [2,3,4,1]. If we map it, we will get following position diffs [+1, +1, +1, -3].. Now, if we move one by one elements to it's position change, all will be ok, till we get to negotiate position diff – 1st position diff can't move down any more..

So, my update to question – how we can animate rows more than twice?

And final problem, on which I am stucked:

Not sure, if it most be solved in this question. BTW, if 2 players have same points – animation plan brakes and element_ids duplicates.

My question now is how to make following transformation of structures list:
[(#id1, 1, 100),(#id2, 2, 50), (#id3, 2, 50), (#id4, 4, 25)] => [(#id3, 1, 150), (#id1, 2, 100), (#id4, 3, 75), (#id2, 4, 50)]

For now my solution is to refresh whole list, but not completly regenerate it – just move all elements to the first position in order they sorted in backed JSON. Animation is too complex to be done cause of final problem.

Best Answer

Well I hope, I'll answer all your questions and be precise enough.

The answer to all your questions is yes, it is possible to do all of that in Javascript:

What kind of task it is? Just sort?

You have to sort data, but you also have to update some data (points, name?, position) and display it to the user.

Is it ok to use JavaScript to solve this task if lists are less than 500 rows?

You can rearrange tables composed of 500 rows with JavaScript, no problem (using a bit of JQuery), and I don't think you would have any performance trouble with that. The only problem you could have is if you attempt to animate 500 moving rows (more on that later)

Is it ok to re-render whole players list, or there are some ways to just change div element position(index in DOM or something like this)?

Well, I think it is ok to re-render the whole players list, but if you do that, how will perform position-change animations ?

Are there any tools and libraries around, that can help solve this task efficiently?

Obvious answer: JQuery and JQuery UI (or your favorite JavaScript framework for performing animations)

If we want to animate all this story - we should create a queue of animation tasks each time scores for all players recalculates?

I think you should perform the animations one by one after you know the final position of each player. Otherwise, you could perform animations when nothing has changed (player3 may move from 3 to 1, then player2 moves from 2 to 1, then player1 moves from 1 to ... 1, nothing has changed!)

DOM only vs DOM backed by a JSON Data Model

Now, regardless of the tools, or algorithm you are going to use, if you work only with DOM elements, without having a "backend" JSON data model (please someone complete here if you see more problems):

  • Finding the new position of a given player will be a pain
  • Updating the positions of every player will be a pain

If you work with DOM elements for rendering, backed by a JSON data model, and provided you make the following changes to your individual player display, you will have a much easier time:

   <div class="player" id="player2">
        <div class="name">Player2</div>
        <div class="points" id="playerPoints2">400</div>
        <div class="position" id="playerPosition2">2</div>
   </div>
  • Notice that I've added an id attribute to the player <div> and to the elements displaying the points ("playerPoints2") and the position ("playerPosition2"). Doing that will allow you to quickly find the position of a player or its points, given its unique id. You could do without, but that would affect performance and you would have to use more complex queries (see JQuery's .find())

My attempt:

In short, what I try to do is moving as few DOM elements as possible, and decorrelating the data from the display.

Imagine having the following JSON data model in addition to your DOM model for displaying the players:

  • player (array of players)
    • id
    • position (you can write a custom function for sorting the array given the "position" attribute if each item of the array)
    • points
    • name

and the following JSON data model for performing animations later:

  • playerChanges (array of player changes)
    • id
    • deltaPosition (example: +3 = 3 ranks higher than before) . * isComputed (boolean)

After receiving data from the server, here's what I would do:

  • sort the list of backend players, given their respective position. The first element of the array should be the top ranked player.
  • loop over the data received from the server
  • if the points of a player change, then:
    • look up the player in the array
    • update its points
    • move the player to the position in the array where it should fit, but don't change the "position" attribute
    • create a new entry in the "playerChanges" array, storing only the id of the player (we will compute the delta only at the end)
  • loop on the array of backend players, and for each player in "playerChanges" (this is important) that is nit yet conputed, compute the position attribute regarding to the position of the player in the array. Since you have his "old" position, and the position in the array, you can compute the "deltaPosition". Edit: after you compute the delta, mark the "playerChange" as computed and restart looping on the array of backend players. Do that as long as there is an uncomputed ""playerChange"" in the array.

In the end you should have an array of "playerChanges". This is the list of rows that you should move, given the "deltaPosition" of each player. Notice that it could be 0, which means that the player received new points, but his position did not change. To perform the animation, I would suggest looking at this brilliant answer.

After you have done these animations, you can loop again on the list of backend players in order to update the displayed position ("playerPositionXXX" where XXX is the id of the player) of each player.

What I've attempted to do here is to decorrelate the "update and sort" from what happens on the screen, the animation that the user sees.

Edit: here is a working code sample (on Chrome at least) of an animation with JQuery and JQueryUI.

<html>
    <head>
        <title>Temp test</title>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
        <style type="text/css">
            * { font-family: Calibri;}
            .player { border: 1px solid grey; background: #f0f0f0; width: 300px; 
                        padding: 10px; margin-bottom: 5px; margin-top: 5px; }
                .name { margin-left: 30px; font-size: 20px; font-weight: bold; }
                .points { float: right; color: whitesmoke; font-size: 24px; font-weight: bold; margin-top: -30px; 
                            background-color: #999; padding: 3px;}
                .position { clear: both; display: block; float: left; 
                            font-size: 20px; font-weight: bold; margin-top: -29px; padding-right: 10px; 
                            border-right: 1px solid black;}
        </style>
        <script type="text/javascript">

            function movePlayer(playerToMoveId, delta) {
                var $old = $(playerToMoveId);
                var $new = $old.clone();

                // TODO: here I don't take into account the delta
                // 
                // you need to write a simple method for determining the player
                // that will be replaced (here I hardcoded "p4"). 
                $("#p4").before($new);

                var newOffset = $new.offset();
                //Get the old position relative to document
                var oldOffset = $old.offset();
                //we also clone old to the document for the animation
                var $temp = $old.clone().appendTo('body');
                //hide new and old and move $temp to position
                //also big z-index, make sure to edit this to something that works with the page
                $temp
                  .css('position', 'absolute')
                  .css('left', oldOffset.left)
                  .css('top', oldOffset.top)
                  .css('zIndex', 1000);
                $new.hide();
                $old.hide();
                //animate the $temp to the position of the new img
                $temp.animate( {'top': newOffset.top, 'left':newOffset.left}, 'slow', function(){
                   //callback function, we remove $old and $temp and show $new
                   $new.show();
                   $old.remove();
                   $temp.remove();
                });
            }
        </script>
    </head>
    <body>
        <div class="players_list" id="plist">
           <div class="player" id ="p4">
                <div class="name" id="pName4">Player4</div>
                <div class="points" id="pPts4">800</div>
                <div class="position" id="pPos4">1</div>
           </div>
           <div class="player" id="p3">
                <div class="name" id="pName3">Player3</div>
                <div class="points" id="pPts3">500</div>
                <div class="position" id="pPos3">2</div>
           </div>

           <div class="player" id="p1">
                <div class="name" id="pName1">Player1</div>
                <div class="points" id="pPts1">300</div>
                <div class="position" id="pPos1">3</div>
           </div>     
           <div class="player" id="p2">
                <div class="name" id="pName2">Player2</div>
                <div class="points" id="pPts2">250</div>
                <div class="position" id="pPos2">4</div>
           </div>
        </div> 

        <input type="button" onclick="movePlayer($('#p2'), 3)" value="Animate"/>
    </body>
</html>
Related Topic