Sort the climbing grades

34

2

My first code golf post, apologies for any mistakes...

Context

In rock climbing (bouldering specifically), the V/Vermin (USA) climbing grades start at 'VB' (the easiest grade), and then go 'V0', 'V0+', 'V1', 'V2', 'V3', 'V4', 'V5' etc. up to 'V17' (the hardest grade).

Task

You will take as input a list/array of climbing grades and you have to to return or print a list/array of the grades sorted from easiest to hardest.

If the input is empty, return an empty data structure; otherwise the input will always be a valid.

Test cases

Input | Output
[] |  []
['V1'] |  ['V1']
['V7', 'V12', 'V1'] | ['V1', 'V7', 'V12']
['V13', 'V14', 'VB', 'V0'] |  ['VB', 'V0', 'V13', 'V14']
['V0+', 'V0', 'V16', 'V2', 'VB', 'V6'] | ['VB', 'V0', 'V0+', 'V2', 'V6', 'V16']

This is a challenge.

Chris_Rands

Posted 2017-08-02T08:54:25.747

Reputation: 887

Next time, post this on the sandbox to get feedback before posting it. Secondly, should you really answer your own challenge?

– Ian H. – 2017-08-02T08:55:54.360

Will duplicate grades appear in the input? – Mr. Xcoder – 2017-08-02T09:01:13.580

@Mr.Xcoder No duplicates – Chris_Rands – 2017-08-02T09:02:17.267

@IanH. I see, thanks for the tips, well I deleted my own solution and will post it again in a few days (based on the feedback from others) – Chris_Rands – 2017-08-02T09:06:23.167

7Welcome to PPCG! Pretty clear and nice for a first question. (y) – officialaimm – 2017-08-02T09:33:48.730

3Very nice first question! The answers it has lead to are so varying and creative. :) – Lynn – 2017-08-02T10:04:44.570

A good variation would be if you could handle both Vermin and Font grades – Jimmy – 2017-08-02T19:28:37.613

Answers

24

Python 2, 58 54 bytes

lambda x:sorted(x,key=lambda y,B10=0:eval(y[1:]+'10'))

Try it online!

How it works

y         y[1:]+'10'   eval(y[1:]+'10')
=======================================
VB        B10          0  (a variable we defined)
V0        010          8  (an octal literal)
V0+       0+10         10
V1        110          110
V2        210          210
...       ...          ...
V17       1710         1710

Lynn

Posted 2017-08-02T08:54:25.747

Reputation: 55 648

Looks like porting this to ES6 doesn’t beat Arnauld’s approach: a=>a.sort((a,b,B10=0)=>(g=s=>eval(s.slice(1)+10))(a)>g(b)) is 58 bytes. – Lynn – 2017-08-02T10:23:59.003

1a=>a.sort((a,b)=>(g=s=>eval(s.slice(B10=1)+10))(a)-g(b)) is 2 bytes shorter, but that's still too long. – Arnauld – 2017-08-02T10:27:26.360

@GB I think it was valid, but now it’s definitely valid. – Lynn – 2017-08-02T10:45:23.247

Why use '10' and not something shorter? For example '2' saves 2 bytes. – G B – 2017-08-02T13:31:09.537

1@GB The trick is to trigger the translation from the octal notation "010" to 8 as decimal for "V0". With 2, you'd get "02" = 2, which is the same as "0+2". – Arnauld – 2017-08-02T13:39:09.143

This is very clever, and much better than my own Python solution, just un-deleted https://codegolf.stackexchange.com/a/137152/72698

– Chris_Rands – 2017-08-03T10:10:35.800

15

JavaScript (ES6) / Firefox, 53 bytes

a=>a.sort((a,b)=>(g=s=>parseInt(s,32)%334+s)(a)>g(b))

Test cases

For Firefox:

let f =

a=>a.sort((a,b)=>(g=s=>parseInt(s,32)%334+s)(a)>g(b))

console.log(JSON.stringify(f([])))
console.log(JSON.stringify(f(['V1'])))
console.log(JSON.stringify(f(['V7', 'V12', 'V1'])))
console.log(JSON.stringify(f(['V13', 'V14', 'VB', 'V0'])))
console.log(JSON.stringify(f(['V0+', 'V0', 'V16', 'V2', 'VB', 'V6'])))

For Chrome or Edge (+4 bytes):

let f =

a=>a.sort((a,b)=>(g=s=>parseInt(s,32)%334+s)(a)>g(b)||-1)

console.log(JSON.stringify(f([])))
console.log(JSON.stringify(f(['V1'])))
console.log(JSON.stringify(f(['V7', 'V12', 'V1'])))
console.log(JSON.stringify(f(['V13', 'V14', 'VB', 'V0'])))
console.log(JSON.stringify(f(['V0+', 'V0', 'V16', 'V2', 'VB', 'V6'])))

How?

We apply 3 successive transformations that lead to lexicographically comparable strings.

s     | Base32 -> dec. | MOD 334 | +s
------+----------------+---------+---------
"VB"  |           1003 |       1 | "1VB"
"V0"  |            992 |     324 | "324V0"
"V0+" |            992 |     324 | "324V0+"
"V1"  |            993 |     325 | "325V1"
"V2"  |            994 |     326 | "326V2"
"V3"  |            995 |     327 | "327V3"
"V4"  |            996 |     328 | "328V4"
"V5"  |            997 |     329 | "329V5"
"V6"  |            998 |     330 | "330V6"
"V7"  |            999 |     331 | "331V7"
"V8"  |           1000 |     332 | "332V8"
"V9"  |           1001 |     333 | "333V9"
"V10" |          31776 |      46 | "46V10"
"V11" |          31777 |      47 | "47V11"
"V12" |          31778 |      48 | "48V12"
"V13" |          31779 |      49 | "49V13"
"V14" |          31780 |      50 | "50V14"
"V15" |          31781 |      51 | "51V15"
"V16" |          31782 |      52 | "52V16"
"V17" |          31783 |      53 | "53V17"

Arnauld

Posted 2017-08-02T08:54:25.747

Reputation: 111 334

Did you come up with the base conversion/modulo idea? Brilliant! – kamoroso94 – 2017-08-02T13:14:17.020

1

@kamoroso94 FWIW, here is the code that I wrote to find the base and the modulo. It gives some other possible answers (with m < 1000).

– Arnauld – 2017-08-02T13:28:46.983

I tried a=>a.sort((a,b)=>(g=s=>parseInt(s,32)%334+s)(a)>g(b)) on Chrome, it doesn't give the correct answer to f(["VB","V0","V0+","V1","V2","V3","V4","V5","V6","V7","V8","V9","V10","V11","V12","V13","V14","V15","V16","V17"]) I'm not sure why; the edge-compatible version works fine on chrome. – Ra8 – 2017-08-02T15:46:26.870

1@Ra8 Ah, yes. It appears to be unstable for Chrome as well. Returning a boolean from a sort() callback is just a hack that happens to work in Firefox, but we really are supposed to return a signed value. Thanks for your feedback! – Arnauld – 2017-08-02T16:02:26.153

12

Husk, 5 bytes

ÖiÖm±

Try it online! The results are printed one per line, but internally this is a function that takes and returns a list of strings.

Explanation

This is surprisingly similar to Martin's Retina answer. First we do Öm±, meaning "order by mapping is-digit". This puts VB, V0 and V0+ in the correct order, since they are compared as [0,0], [0,1] and [0,1,0]. Next we do Öi, meaning "order by integer value". Given a string, i returns the first sequence of digits occurring in it as an integer, or 0 if one is not found. The three strings above are all mapped to 0 and the sort is stable, so they will be in the correct order in the output.

Zgarb

Posted 2017-08-02T08:54:25.747

Reputation: 39 083

11

Retina, 14 bytes

B
!
O`
!
B
O#`

Try it online!

Explanation

B
!

Replace B with ! so that the lexicographic order of grades puts VB (or then V!) in front of all the numeric grades.

O`

Sort all input lines lexicographically. This doesn't give the right result but it does order V! < V0 < V0+ correctly.

!
B

Turn V! back into VB.

O#`

Sort the lines numerically. Retina simply looks for the first decimal number in a string to determines its sort key. If there is no number (such as for VB), it sets the value to 0. That means all of VB, V0 and V0+ have the same sort key. But Retina's sort is stable and we've already put them in the correct relative order.

Martin Ender

Posted 2017-08-02T08:54:25.747

Reputation: 184 808

6

V, 3 bytes

Úún

Try it online!

How does it work?

ú   # Sort on...
 n  #   the first decimal number on the line

This command is almost a valid solution, since every line that can't be sorted by numbers (AKA, VB) will be placed at the beginning, without the order changed. However, since it's only looking at numbers, it can't distinguish between V0 and V0+. Since Vim uses a stable sort, whichever of these came first will remain first after sorting it. So...

Ú   # Sort lexicographically (will place 'V0' before 'V0+')
 ú  # Sort by...
  n #   The first number on the line

James

Posted 2017-08-02T08:54:25.747

Reputation: 54 537

2How appropriate that V does well on this challenge :P – Business Cat – 2017-08-03T00:35:34.877

5

C#, 121 83 82 83 bytes

Saved 39 bytes thanks to TheLethalCoder and LiefdeWen

a=>a.OrderBy(x=>x[1]>65?-1:x=="V0+"?0.5:int.Parse(x.Remove(0,1)))

Try it online!

Bytecount includes using System.Linq.


How?

  • Gets an array of strings as input.
  • If the input is equal to VB, set the value to -1, if it's equal to VB0+, set the value to 0.
  • Order the input based on the number value that comes after the V.

Might be a bit of a hack, but it works! :)

Ian H.

Posted 2017-08-02T08:54:25.747

Reputation: 2 431

94 bytes – LiefdeWen – 2017-08-02T09:22:45.920

@LiefdeWen You don't need the ToArray() an IOrderedEnumerable should be fine. – TheLethalCoder – 2017-08-02T09:24:06.347

Sorry accidently removed System.Linq reference, fixed it – LiefdeWen – 2017-08-02T09:24:09.207

@TheLethalCoder You are right as always, 84 bytes

– LiefdeWen – 2017-08-02T09:26:33.330

@LiefdeWen .Remove(0,1) for additional -1 byte :) – Ian H. – 2017-08-02T09:27:34.563

Save a byte by using 'B's char code. You might even be able to use > or < there too but I haven't checked – TheLethalCoder – 2017-08-02T09:29:12.867

@IanH. Awesome, didn't know that – LiefdeWen – 2017-08-02T09:29:21.427

@TheLethalCoder Thats actually amazing. Using >65 (B is 66), works because numbers come before that. – Ian H. – 2017-08-02T09:35:47.390

This doesn’t work for the input { "V1", "V0+" }, as you map both of those to 1. – Lynn – 2017-08-02T11:06:50.637

@Lynn Yeah, I had 0.5 in my original version to replace V0+, but apparently it got golfed out somewhere. Updated it at the cost of 2 bytes. – Ian H. – 2017-08-02T11:28:24.780

4

Ruby, 52 42 41 bytes

->x{[?B,0,"0+",*1..17].map{|a|"V#{a}"}&x}

Try it online!

How it works:

Turn the problem around, produce the full sorted list, then get the intersection with our input.

Thanks Lynn for saving 1 byte.

G B

Posted 2017-08-02T08:54:25.747

Reputation: 11 099

Clever! ->x{[?B,0,"0+",*1..17].map{|a|"V#{a}"}&x} saves a byte. – Lynn – 2017-08-02T11:12:04.013

2

To kick things off here is my Python 3 solution... Apologies, posted this too soon against convention, now re-posting...

Python 3, 69 67 bytes

lambda l:sorted(l,key=lambda x:'B00+'.find(x[1:])+1or int(x[1:])+3)

Try it online!

Chris_Rands

Posted 2017-08-02T08:54:25.747

Reputation: 887

5It's discouraged to answer your own challenge immediately. Give some time for some other people to answer, at least 48 hours, probably longer. – TheLethalCoder – 2017-08-02T08:56:46.670

@TheLethalCoder Oh right, on Stack Overflow such behavior is encouraged! Should I delete my answer? – Chris_Rands – 2017-08-02T09:01:22.023

@Chris_Rands Yes, I suggest you to delete it. – Mr. Xcoder – 2017-08-02T09:01:58.557

9@Downvoter: Downvoting a new member for doing something they didn't know was frowned upon ain't cool; much better to simply point out that they shouldn't, as Lethal did. – Shaggy – 2017-08-02T09:02:31.660

Note though if someone doesn't post your solution you're welcome to do so. After waiting of course – TheLethalCoder – 2017-08-02T09:05:04.257

If you choose not to delete the answer, you can drop the f= in front of the lambda – Mr. Xcoder – 2017-08-02T09:05:12.663

@Mr.Xcoder I've now un-deleted and dropped the f=, thanks! – Chris_Rands – 2017-08-03T10:07:38.257

This looks good now. We are just waiting for the downvoter to retract their vote. – Mr. Xcoder – 2017-08-03T10:08:51.943

2

Pyth, 16 bytes

Port of Python answer by OP.

o|hx"B00+"tN+3st

Test suite.

Leaky Nun

Posted 2017-08-02T08:54:25.747

Reputation: 45 011

2

05AB1E, 17 13 8 bytes

Σþï}„VB†

Try it online!

Erik the Outgolfer

Posted 2017-08-02T08:54:25.747

Reputation: 38 134

Ha... is better than D"VB"åiÁ by a loong-shot. – Magic Octopus Urn – 2017-08-16T01:36:10.050

2

Jelly, 9 bytes

Ḋv-.F+LµÞ

A monadic link taking a list of lists of characters and returning the sorted list.

Try it online! (the footer formats the result nicely)

How?

Ḋv-.F+LµÞ - Link: list of lists of characters
       µÞ - sort by key:
Ḋ         -   dequeue (remove the 'V' from the item)
  -.      -   literal -0.5
 v        -   evaluate as Jelly code with argument -0.5
          -   ...this means `VB` and `V0+` become -0.5
          -      (to binary and addition respectively)
          -      while others become their literal numbers
    F     -   flatten
     +L   -   add the length of the item
          -   ...'VB', 'V0', 'V0+', 'V1', 'V2'... -> 1.5, 2, 2.5, 3, 4, ...

Jonathan Allan

Posted 2017-08-02T08:54:25.747

Reputation: 67 804

2

Haskell, 55 bytes

f l=['V':x|x<-"B":"0":"0+":map show[1..17],_:y<-l,x==y]

Try it online!

xnor

Posted 2017-08-02T08:54:25.747

Reputation: 115 687

1

Swift 3, 102 bytes

var r={String((Int($0,radix:32) ?? 992)%334)+$0};func f(l:[String]){print(l.sorted(by:{r($0)<r($1)}))}

This is a function. You can call it as such:

f(l:["V0","VB","V13","V0+"])

Try it online!


How does this work?

This is basically a port of the amazing Javascript answer by @Arnauld, but optimized for Swift.

It maps each of the values to lexicographically orderable Strings as shown in the table below:

Initial String -> Result

V1  ->  325V1
V10 ->  46V10
V11 ->  47V11
V12 ->  48V12
V13 ->  49V13
V14 ->  50V14
V15 ->  51V15
V16 ->  52V16
V17 ->  53V17
V2  ->  326V2
V3  ->  327V3
V4  ->  328V4
V5  ->  329V5
V6  ->  330V6
V7  ->  331V7
V8  ->  332V8
V9  ->  333V9
V0+ ->  324V0+
V0  ->  324V0
VB  ->  1VB

Code Explanation

  • String((Int($0,radix:32) ?? 992)%334) - Converts each String from a base-32 Number to Decimal. In case the value is "V0+", the call to Int(_:radix:) will return nil, and we take the value of "V0", 992. We additionally take the result of mod 334, and finally convert it to String.

  • +$0 - Adds the current value to the String created above. For instance, if the String is V9, the function above returns 333 and we add V9, resulting in 333V9.

  • var r={...} - Declares a variable r to an anonymous closure, because it saves lots of bytes since it's used twice.

  • func f(l:[String]) - Defines a function f with a parameter l, a list of Strings.

  • print(l.sorted(by:{r($0)<r($1)})) - Prints the result of sorting the given list, with the key being the variable r defined above.

Mr. Xcoder

Posted 2017-08-02T08:54:25.747

Reputation: 39 774

1

PowerShell, 45 bytes

param($a)'B',0,'0+'+1..17|%{"V$_"}|?{$_-in$a}

Try it online!

Uses the same process as G B's Ruby answer to construct the full argument list in sorted order, then select out those that are -in the input list.

AdmBorkBork

Posted 2017-08-02T08:54:25.747

Reputation: 41 581

1

Google Sheets, 142 bytes

=ArrayFormula(If(A1="","",Sort(Transpose(Split(A1,",")),Transpose(IfError(Find(Split(A1,","),"VBV0V0+"),Value(Mid(Split(A1,","),2,3))+9)),1)))

Input is a string in A1 with each entry separated by a comma.
Output is the formula's cell plus the n-1 cells below it where n is the number of entries in A1.

Result

It's a long, messy formula so let's unpack it.

  • If(A1="","",~) fixes the null input. Without this, an empty input returns a #VALUE! error because the Split function doesn't work on empty inputs.
  • Transpose(Split(A1,",")) splits A1 at the commas and transposes it into a column because the Sort function only works on columns.
  • Transpose(IfError(Find(),Value()+9)) is breaks into these pieces:
    • Find(Split(A1,","),"VBV0V0+") tries to find each parameter in that string. These first three are the only ones that must be sorted as strings so we use Find to get their sort order.
    • Value(Mid(Split(A1,","),2,3))+9 gets the numerical value of the grade. This only matters for V1 and higher so they sort numerically just fine. The +9 at the end is to ensure V1 comes after V0+ since its Find value would be 5. Technically, then, only +5 is required but it costs me no more bytes to make extra double sure it sorts correctly.
    • IfError(Find(~),Value(~)) returns the Find value if the string was found (i.e., the grade is VB, V0, or V0+). If it can't be found, it returns the numerical value of the grade plus nine.
    • Transpose(IfError(~)) again turns it into a column so Sort can use it.
  • Sort(Transpose(Split(~)),Transpose(IfError(Find(~),Value(~)+9)),1) wraps it all up by sorting the split input using the custom sort order ascending.
  • ArrayFormula(~) wraps the entire thing so it returns the results as an array instead of just returning the first value in that array. This is what causes the formula in one cell to fill the cells below it, too.

Engineer Toast

Posted 2017-08-02T08:54:25.747

Reputation: 5 769

I think this is the first time I've ever seen Google Sheets used. Kudos to you, and +1! – heather – 2017-08-03T15:42:55.350

1

Bash + coreutils, 21

tr B .|sort -V|tr . B

GNU sort's -Version sorting mode almost does what we want. Switch the B for a . and we're done.

Try it online.

Digital Trauma

Posted 2017-08-02T08:54:25.747

Reputation: 64 644

1

Haskell, 90 84 83 61 bytes

import Data.List
f"VB"=[]
f(_:'1':[a])='X':[a]
f x=x
sortOn f

Try it online!

f is a function that converts climbing grades to strings that can be compared. If converts VB to be the empty string so it gets the highest priority, it then replaces V1 with X in strings that are three long to lower the priority of V10-V17. For the remainder we do nothing.

To sort the list we use Data.Lists's sortOn function (as suggested by Lynn) to create a point-free function.

Post Rock Garf Hunter

Posted 2017-08-02T08:54:25.747

Reputation: 55 382

That’s just g=sortOn f, which is also in Data.List. – Lynn – 2017-08-03T12:18:50.030

1Also, f(_:'1':a)='X':a saves 4 bytes! – Lynn – 2017-08-03T12:22:31.340

1@Lynn The first suggestion works, however the second one does not, I need [a] otherwise V1 will be pattern matched which is the problem I am trying to circumvent. – Post Rock Garf Hunter – 2017-08-03T14:25:31.140

1

R, 45 bytes

l=paste0('V',c('B','0','0+',1:17));l[l%in%x]

How does this work?

  • Assign the correctly ordered vector of grades to 'l';
    • Use 'paste0' instead of 'paste' to avoiding making a 'sep=""' argument;
  • Index 'l' based on matches of 'l' in your input vector of mixed, unsorted grades.

Mendizale

Posted 2017-08-02T08:54:25.747

Reputation: 11

0

Jelly, 17 11 bytes

fØD
ṢÇÞÇV$Þ

Try it online!

Erik the Outgolfer

Posted 2017-08-02T08:54:25.747

Reputation: 38 134

0

Python2, 77 bytes

sorted(input(),key=lambda s:float(s[1:].replace("B","-1").replace("+",".5")))

Setop

Posted 2017-08-02T08:54:25.747

Reputation: 188

I think this counts as a snippet! Because you are neither printing the result nor this is a function definition. You can make it into a lambda or print the result though. – officialaimm – 2017-08-02T10:11:43.267

1@officialaimm nice try but does not work if V0+ s before V0. – Setop – 2017-08-02T10:45:02.587

0

TXR Lisp: 45 bytes

(op sort @1 :(ret`@(mod(toint @1 32)334)@1`))

Run:

1> (op sort @1 :(ret`@(mod(toint @1 32)334)@1`))
#<interpreted fun: lambda (#:arg-01-0168 . #:rest-0167)>
2> [*1 ()]
nil
3> [*1 (list "V0+" "V0" "V16" "V2" "VB" "V6")]
("VB" "V0" "V0+" "V2" "V6" "V16")

Kaz

Posted 2017-08-02T08:54:25.747

Reputation: 372

0

Perl 5, 56 + 1 (-a) = 57 bytes

map{$i=$_;s/V//;$a[/B/?0:/^0/?length:$_+2]=$i}@F;say"@a"

Try it online!

Xcali

Posted 2017-08-02T08:54:25.747

Reputation: 7 671