Register to post in forums, or Log in to your existing account
 

Play RetroMUD
Post new topic  Reply to topic     Home » Forums » CMUD General Discussion
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Fri Jun 01, 2012 12:20 am   

Sorting A Database Record Variable by Value
 
Hello -

I am trying to do as the title suggests. There were a couple of old posts (zMUD era) that regarded this topic, but unfortunately they don't seem to work in CMUD 3.34.

I have a variable @test (type database record) that stores the Key as a name and the Value as a number.

Code:
#SHOW @test


results in:

Code:
Alpha=15.2|Bravo=7.5|Charlie=9.51


What I need is:

Code:
Bravo=7.5|Charlie=9.51|Alpha=15.2


I've tried %sort(@test), does nothing. #SORT just sorts by the Key, not the Value. Is there a workaround I am missing?

Also, as a quick side-question - the number in the Value field can be up to 2 decimal places long from the data I'm storing, but if the Value is a whole number (1 instead of 1.23 for example), it will display the value as "1." instead of "1.00". Is there a way to add those 00's back on for display purposes? Don't care if the db record stores them as 1.00 or not, as long as I can do a %replace or something to get the display of 1.00 instead. I did try %replace, but was unsuccessful.

Thank you for your time.
Reply with quote
Rahab
Wizard


Joined: 22 Mar 2007
Posts: 2320

PostPosted: Fri Jun 01, 2012 2:25 am   
 
No, you cannot sort a database variable by value.

If you want to display the numbers with 2 decimals even if they are zeros, use the %format() function.
Reply with quote
MattLofton
GURU


Joined: 23 Dec 2000
Posts: 4834
Location: USA

PostPosted: Fri Jun 01, 2012 2:46 am   
 
Sorting datarecords by value requires a user function (this is by design, not a workaround). Vijilante might have one, and perhaps there are more in the Package Library, but essentially you would create variables to hold the stinglist returned by %dbkeys() and by %dbvalues(), run a standard sorting algorithm on the %dbvalues() list and taking care to swap the equivalent element positions in both stringlists. Once sorted, you would then #loop through the now-sorted %dbkeys() list and #ADDKEY the appropriate data to the source variable (make sure to blank the source first, or else you lose the sorted result.)

For display purposes, you can use %float(). An alternative would be &x.yf in the %format() function's format string, where x = the number of integer digits and y is the number of decimal digits (there's also an alternative syntax to %format() dedicated to floating-point numbers). If the result amount of decimal places is less than the format string amount (ie, putting 10.2 in &0.4f), the function will automatically include the padding 0's. Which you use depends on where you need the floating-point equivalent of the integer (%float() is generally for calculation, %format() is generally for printing to the screen or assigning to a variable).
_________________
EDIT: I didn't like my old signature
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Fri Jun 01, 2012 2:48 am   
 
Two different threads seemed to have done it successfully on the forum, in zMUD. Is this just no longer possible in CMUD..?

Threads:
http://forums.zuggsoft.com/forums/viewtopic.php?t=19800
and
http://forums.zuggsoft.com/forums/viewtopic.php?t=31091

The first one, the guy was looking to do exactly what I'm trying to do. The response was this:

Code:

#ALIAS {valsort} {
#VAR input_db %1
#VAR val_db %null
#LOOPDB @input_db {
#VAR val_db %concat(@val_db,%repeat("0",10-%len(%val)),%val,%char(30),%key,"|")}
#VAR val_db %leftback(@val_db,1)
#VAR val_db %sort(@val_db)
#VAR val_db %replace(@val_db,"|",%char(29))
#VAR output_db %null
#LOOPDB @val_db {#VAR output_db %concat(@output_db,%val,%char(30),%eval(%key),%char(29))}
#VAR output_db %leftback(@output_db,1)
#SHOWDB @output_db
#UNV val_db
#UNV input_db
#UNV output_db}


... which unfortunately does not seem to work in CMUD. I can't imagine functionality was actually reduced with CMUD..

As for the %format() function, I am very familiar with it, but using it on a database record variable doesn't do anything, since it isn't trying to format just the number, but whole "Alpha=15.2|Bravo=7.5|Charlie=9.51".

Again, thank you for your time.
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Fri Jun 01, 2012 2:55 am   
 
Oh sorry Mr. Lofton, I had only seen Rahab's reply before my post. I had never heard of the Package Library before, so I checked the forums and just found the poll and instructions regarding it.. No dice, though, my MUD is somewhat obscure and nothing in All or Core for 'sort database' or similar..
Reply with quote
Rahab
Wizard


Joined: 22 Mar 2007
Posts: 2320

PostPosted: Fri Jun 01, 2012 1:20 pm   
 
Cmud codes database variables in a completely different way than Zmud did, so solutions that worked for Zmud will not work for Cmud. Many things are very different between Zmud and Cmud. It is not that functionality was reduced, since sorting by value was not an intentional feature of Zmud. Database variables in Cmud actually have a lot more functionality than Zmud.

Matt's solution is interesting, and he would know better than I whether it would work. You would have to write your own sorting algorithm, which shifts both elements in the value stringlist and the corresponding elements in the keys stringlist. Actually, here's an alternative method. Follow Matts suggesction except that, rather than create a keys stringlist and a values stringlist, you create a new db variable in which the keys are the old values and the values are the old keys. In otherwords, create a new db variable with the values and keys swapped with each other. Sort that, and then create your final version by swapping keys and values back again.
Reply with quote
Daern
Sorcerer


Joined: 15 Apr 2011
Posts: 809

PostPosted: Fri Jun 01, 2012 7:41 pm   
 
Internally, database variables use a hash table, so they're not actually sorted at all, by keys or by values. You might notice that when you add a record to a database variable, it doesn't always go to the end of the variable. There's no way you can keep your records in any particular order. The #sort command (or enabling sorting in the package editor) doesn't actually sort the variable internally. CMUD will display the records in sorted order when looking at it in the package editor or with various commands/functions (#showdb, %expanddb, etc), but internally the order is still based on the hash.

So now the solution really depends on what you were trying to do. If your goal was to get it to stay in sorted order by value in the database variable, there's no way you can do that. Matt's suggestion would get you two lists, one with the values in sorted order and the other with the corresponding keys in the same positions, but as soon as you add them to the database variable, the order cannot be guaranteed. Rahab's solution could kinda work, but you'll have the same issue when you try to flip the keys and values back. Even if they are in sorted order when the values are the keys, and you add them to the other variable in the same order as they appear in the sorted one, the order cannot be guaranteed later. If you don't mind working with it flipped though, you could keep it that way. Also, a problem arises if you have any duplicate values. Keys must be unique, while values can be duplicated, so you could end up with a loss of data if you try to make your values keys.

If all you wanted to do was display them in sorted order, not have them stay that way in a database variable, that becomes much easier. Something like this should work (untested):
Code:
$keys = %dbkeys(@test)
$values = %dbvalues(@test)
#while (%numitems($keys) > 0) {
  $index = %ismember(%min($values), $values) // find the position of the lowest value in the list
  #show %format("&-10s - &5.2f", %item($keys, $index), %item($values, $index)) // this should take care of formatting the numbers to two decimal places
  #delnitem $keys $index
  #delnitem $values $index
}


If you're trying to do something else, let us know, I'm sure something can be worked out.
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Fri Jun 01, 2012 8:55 pm   
 
Daern - I don't need to store the keys/values in any form of sorted order, because for my purposes, every time I need to display the information, I can just use the sorting function/alias. The piece you posted works perfectly as long as the values don't end in a decimal. The reason I say this is because values like:

Code:
Alpha=15.2|Bravo=7.5|Charlie=9.51


work fine, but if

Code:
Alpha=15.2|Bravo=7.5|Charlie=9.51|Delta=4.


is added, the alias spams the screen with 0.00 until CMUD hangs and must be closed from the Task Manager. Sounds dumb that I'm trying to sort a 'number' like "4." I know, but it's actually supposed to be 4.00 - the database automatically removes the 00, even if I type it in manually in to the record. If possible, I would also like the sort to stay on a single line, like

Code:
Delta=4.0|Bravo=7.5|Charlie=9.51|Alpha=15.2


.. if that isn't too much of a headache to adjust. I think I can make it work by using another local variable and just #ADDITEM'ing the sort results to it, maybe?
Reply with quote
Daern
Sorcerer


Joined: 15 Apr 2011
Posts: 809

PostPosted: Fri Jun 01, 2012 9:26 pm   
 
Hmm, how are you adding the numbers to the database? I tried #addkey test Delta 4.0 and it was converted to "4", not "4.", and %format correctly displayed it as 4.00. It sounds like you're somehow adding a string to the database instead of a number, though I couldn't get it to fail with strings at all either (#show %format("&5.2f", %min("4.", 8)) shows 4.00, so both %min and %format must be correctly converting the string to a number). You could try using %float on it (already done in my code below).

If you just want it to display in that format, I wouldn't trust using a string list and #additem, just in case it gets interpreted as a database variable and messes up the order. String concatenation will be safer. If you want to actually use list functions on it, it should work fine with a list too, and I'm sure you can figure out how to convert this.
Code:
$keys = %dbkeys(@test)
$values = %dbvalues(@test)
$out = ""
#while (%numitems($keys) > 0) {
  $index = %ismember(%min($values), $values) // find the position of the lowest value in the list
  $out = %concat($out, %item($keys, $index), "=", %format(2, %float(%item($values, $index))), "|")
  #delnitem $keys $index
  #delnitem $values $index
}
#show %leftback($out, 1) // remove the last | from the end
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Fri Jun 01, 2012 10:15 pm   
 
That piece sorts the record correctly and displays it exactly how I was hoping, thank you again. It still locks up CMUD if any of the values are "4." for example, but let me explain what I'm doing and maybe you can help me figure out where I'm making the mistake.

What I'm doing is tracking player's participation in killing a mob on my MUD and giving each a score based on that participation. Player Alpha hits the mob, doing 10 damage let's say, so I trigger off of that message with #ADD's that count the total number of that player's attacks, and in another record variable, the total damage of those attacks. Example:

Trigger:
Code:
^(%w) smashes * for 10 damage.$


Code:
#ADD test_total.%1 1
#ADD test_score.%1 5


Doing this gives me a record of each person's total number of attacks and their total damage. After the mob dies, it triggers:

Code:
#FORALL @party_members {party_calc %i}


Alias party_calc
Code:
#VAR test_calc.%i {%format( 2, %eval( %float( @test_score.%i)/%float( @test_total.%i)))}


The purpose of this is that each player's total damage is divided by their number of attacks to give an average. Sometimes the average comes out as a whole number still (say for example player Alpha does 3 attacks, each one doing 4 damage, then their average is still 4) - when this happens, the database record stores their average as "4." instead of "4.0" or just "4". I don't really care how it's stored, I just want to be able to sort them so I can see in order which players did the most, and display them cleanly. Would LIKE a "4" to be represented as "4.0" if possible, but that isn't vital.

Hopefully this makes more sense. From what you said last, Daern, I'm wondering if maybe when I divide test_score by test_total, it is converting the result in to a string instead of a number? I wouldn't think so, since numbers work, it is doing the math properly, but maybe?
Reply with quote
Daern
Sorcerer


Joined: 15 Apr 2011
Posts: 809

PostPosted: Fri Jun 01, 2012 10:43 pm   
 
Aha, I was able to reproduce it with that code! Changing the party_calc alias to #ADDKEY test_calc %1 (%float( @test_score.%1)/%float( @test_total.%1)) seems to fix it (it'll store "4" in test_calc, instead of "4." - the display code above will correctly format it as 4.00 for displaying).

EDIT: if anyone cares, the cause of the infinite loop was with the %ismember(%min($values), $values) line - when "4." was stored in $values, %min would return only "4", and the %ismember would not find it in the $values list.
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Sat Jun 02, 2012 1:14 am   
 
This works exactly as you described! I came across a problem of my own creation though - I plan to use this sorting alias in multiple different places, and for one of them I can't really modify the 'party_calc' equivalent, so although the db record still has the values stored properly (as 4 instead of 4.), as soon as I run it through the sorting alias, it spits out a properly-sorted list that does include the errant decimal, like

Code:
Alpha=2.67|Charlie=3.|Bravo=7.67


At this point in the script, the numbers no longer need to be modifiable or adjusted in any way except for some %replace stuff and formatting adjustments to make the output more palatable. That being said, since the numbers can now be treated as a string essentially, is there a simple way to just use %subregex for example, to find the (number).(not number) and replace it with (number).(zerozero)? I've been looking around trying to figure out how I can do this, but my regular expression knowledge is sorely lacking.
Reply with quote
Daern
Sorcerer


Joined: 15 Apr 2011
Posts: 809

PostPosted: Sat Jun 02, 2012 6:42 am   
 
Try this:
Code:
sortedstring = %subregex(@sortedstring, "(\d+)\.(?!\d)", "\1.00")
Reply with quote
JWhitney
Wanderer


Joined: 20 Oct 2006
Posts: 51

PostPosted: Sat Jun 02, 2012 9:45 am   
 
Worked perfectly. Thank you very much for taking so much of your time to help me out here, it is immensely appreciated.
Reply with quote
Display posts from previous:   
Post new topic   Reply to topic     Home » Forums » CMUD General Discussion All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

© 2009 Zugg Software. Hosted by Wolfpaw.net