32
\$\begingroup\$

Introduction

My grandpa is a fan of James Bond, but he is always unsure on how to rank his favourite actors. As such, he is always making lists, which is a lot of work. He asked me to produce a program that will make his life easier, but I do not have time for that, I have to work! So I will count on you guys.

Challenge

The challenge is simple. The input will consist of a list, in the following format:

<number> <space> <actor's name> <newline>

Your task is to sort them based on the number at the beginning of the line, starting from the last, and ending with the first. All numbers should be removed.

However, my grandpa sometimes makes mistakes. As such, you will need to validate the data. If one of the names on the list doesn't refer to one of the actors who played Bond, you need to discard it. In case of repetitions, repeats should be removed, and the name should maintain the lowest weight it was associated with (example #3).

There is no limit to how many lines there may be.

The output only needs to be a list of some sort, whether it is an array, a comma separated string, just values separated by spaces, or something else entirely, i.e.

Pierce Brosnan, Sean Connery, David Niven

A trailing newline or space is allowed.

Example Input and Output

Input:

1 Sean Connery

2 Emma Watson

5 Timothy Dalton

4 Roger Moore

3 Daniel Craig

Output:

Timothy Dalton, Roger Moore, Daniel Craig, Sean Connery

Input:

2 Timothy Dalton

4 George Lazenby

5 George Lazenby

3 Bob Simmons

Output:

George Lazenby, Bob Simmons, Timothy Dalton

Input:

3 Sean Connery

2 Pierce Brosnan

1 Sean Connery

Output:

Pierce Brosnan, Sean Connery

As this is a code golf, shortest code (in bytes) wins!

Appendix

List of actors who played the role of Bond:

  • Barry Nelson
  • Bob Simmons
  • Sean Connery
  • Roger Moore
  • David Niven
  • George Lazenby
  • Timothy Dalton
  • Pierce Brosnan
  • Daniel Craig
\$\endgroup\$
12
  • 3
    \$\begingroup\$ Welcome to PPCG, and nice challenge! Note that Sean Connery appears twice on your list. \$\endgroup\$ Commented Feb 11, 2016 at 12:57
  • \$\begingroup\$ @DenhamCoote Fixed that and the mistake in the example output. \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 12:59
  • 2
    \$\begingroup\$ Can we assume all possible actors will be identified by two words (first name and last name)? \$\endgroup\$
    – Luis Mendo
    Commented Feb 11, 2016 at 17:11
  • 17
    \$\begingroup\$ Emma Watson was great as James Bond. \$\endgroup\$
    – Alex A.
    Commented Feb 11, 2016 at 18:11
  • 4
    \$\begingroup\$ hmm my answer is echo Sean Connery because everyone knows, there is only one bond \$\endgroup\$
    – user902383
    Commented Feb 12, 2016 at 14:04

8 Answers 8

12
\$\begingroup\$

Retina, 201 197 191

\d+
$0$*1
G`^1+ (Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$
+`\b((1+)\D*)¶(\2.+)
$3¶$1
+s`1+(\D+)¶(.*\1)
$2
1+ 

Try it online!

6 bytes saved thanks to Martin!

Whee, bubble sort with regex. Note that ten bytes are spent doing decimal to unary conversion at the beginning, if unary input is OK then that isn't needed. Also, if numbers can't be in people's names, then a couple more bytes can be saved by moving the line that removes non-Bond actors to the end and removing the 1+ (untested with \D version).

Explanation:

A Retina program is made up of several stages, so I will explain each stage separately.

Stage 1:

\d+
$0$*1

Replaces the numbers in the input with unary. This uses Retina's special replacement token: $* which repeats the character after a number of times equal to the preceding token's base 10 value.

Stage 2:

G`^1+ (Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$

The stuff before a ` in a stage changes the mode being used. This turns on grep mode, which means that each line that doesn't match the regex is discarded. The anchors are necessary to prevent near matches from slipping by.

Stage 3:

+`\b((1+)\D*)¶(\2.+)
$3¶$1

This is the sorting stage. The + in the mode signifies that this stage should be repeated until the replacement makes no change when applied (i.e. we reach a fixed point). The regex finds a word boundry, followed by some number of 1s and then all the rest of the line up to the newline. Then, if the next line has more 1s than it, the regex will match and we swap the lines.

Stage 4:

+s`1+(\D+)¶(.*\1)
$2

This stage uses the + mode again, but also uses s to make the . meta-character also match newlines. This removes duplicate lines, by matching for exact duplicates after the 1s and capturing the stuff after the first duplicate to replace the whole match with it. This will work without needing to consider the order of tie breaking, because the names are already sorted appropriately, with the larger numbers above, therefore we will always keep the smaller values.

Stage 5:

1+ 

Really simple one here, everything is in order, except we have a bunch of 1s in front of our Bonds, so we replace them and the space after them with nothing.

\$\endgroup\$
1
  • \$\begingroup\$ ...Damn, this language impresses me more and more every day. Well done, Martin! \$\endgroup\$
    – anon
    Commented Feb 11, 2016 at 18:01
6
\$\begingroup\$

TSQL 426 bytes (includind data + input)

Golfed solution:

create table A(Name varchar(99))insert into A values('Barry Nelson'),('Bob Simmons'),('Sean Connery'),('Roger Moore'),('David Niven'),('George Lazenby'),('Timothy Dalton'),('Pierce Brosnan'),('Daniel Craig')declare @I as table (R int, N varchar(99))insert into @I values(3,'Sean Connery'),(2,'Pierce Brosnan'),(1,'Sean Connery')select N from(select N,min(R) R from @I where N in (select N from A) group by N) x order by R desc

Try it here

SQL excels (no pun intended) in this kind of task: relating sets, ordering, cutting off duplicates etc.

All you need is to create and populate a table of Actors like this:

create table Actor (Name varchar(99))
insert into Actor values
 ('Barry Nelson')
,('Bob Simmons')
,('Sean Connery')
,('Roger Moore')
,('David Niven')
,('George Lazenby')
,('Timothy Dalton')
,('Pierce Brosnan')
,('Daniel Craig')

Now if we use a table-variable as input we just need to get the intersection of both sets. Removing duplicates and ordering in SQL are really easy.

Example 1:

declare @Input as table (Rnk int, Name varchar(99))
insert into @Input values
 (1,'Sean Connery')
,(2,'Emma Watson')
,(5,'Timothy Dalton')
,(4,'Roger Moore')
,(3,'Daniel Craig')

select Name
from
(
    select Name, min(Rnk) as R
    from @Input
    where Name in (select Name from Actor)
    group by Name
) x
order by R desc

Example 2:

declare @Input as table (Rnk int, Name varchar(99))
insert into @Input values
 (2,'Timothy Dalton')
,(4,'George Lazenby')
,(5,'George Lazenby')
,(3,'Bob Simmons')

select Name
from
(
    select Name, min(Rnk) as R
    from @Input
    where Name in (select Name from Actor)
    group by Name
) x
order by R desc

The golfed version is just the full thing for example input 3

As a plus this SQL can work for older DBMS versions (even be rewrite to ANSI SQL) and run without problem in older computers than most languages.

\$\endgroup\$
6
  • \$\begingroup\$ Does it work with any number at the beginning of the line, or only single digits? \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 22:45
  • 1
    \$\begingroup\$ @MKII I used INT type so it ill accept anything in the range –2,147,483,648 to 2,147,483,647 also it ill accept that number of rows too =) \$\endgroup\$
    – jean
    Commented Feb 12, 2016 at 9:32
  • \$\begingroup\$ You don't need a subselect. You can just use order by min(R) desc with the inner select and remove the min(R) from the select. That should save 21 bytes. \$\endgroup\$
    – raznagul
    Commented Feb 12, 2016 at 10:59
  • \$\begingroup\$ Also there are some unnecessary spaces in the golfed version. \$\endgroup\$
    – raznagul
    Commented Feb 12, 2016 at 11:10
  • \$\begingroup\$ Using char instead of varchar will save another 6 bytes. \$\endgroup\$
    – raznagul
    Commented Feb 12, 2016 at 11:26
5
\$\begingroup\$

Perl, 242 179 217 bytes

print reverse grep{/^(Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$/&&!$s{$_}++}map{s/\d+ //;$_}sort{($a=~/(\d+)/)[0]<=>($b=~/(\d+)/)[0]}<>;

Nicer formatted version, with comments:

print
     # reverse ranking order
     reverse
     # filter entries...
     grep {
         # only actual bonds
         /^(Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$/
         # only new bonds
         && !$s{$_}++
     } map {s/\d+ //;$_}         # remove leading digits+space
     # sort according to embedded numbers
     sort {($a=~/(\d+)/)[0] <=> ($b=~/(\d+)/)[0]}
     <>;                        # slurp input as list (list context)

Most of the size is the list of Bonds; I can't find a nice way of compressing that regex without allowing false positives.

\$\endgroup\$
8
  • \$\begingroup\$ Welcome to Programming Puzzles and Code Golf. Brilliant answer, +1. I was going to suggest that you add an explanation, but then I saw the edit. Maybe it's possible to compress the list of actors somehow... \$\endgroup\$
    – wizzwizz4
    Commented Feb 11, 2016 at 18:40
  • \$\begingroup\$ @wizzwizz4 I tried a few things to make that regex smaller, but the decoding always seems to cost more than you save---it's too sparse in what it accepts. \$\endgroup\$ Commented Feb 11, 2016 at 21:52
  • \$\begingroup\$ Sadly, it needs to work with numbers, not only single digits. I am sorry, but I used the wrong term in the question. \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 22:46
  • \$\begingroup\$ @MKII aww, that costs me 38 bytes :( \$\endgroup\$ Commented Feb 11, 2016 at 23:01
  • \$\begingroup\$ If there's an eval in Perl, and a built-in compression system... \$\endgroup\$
    – wizzwizz4
    Commented Feb 12, 2016 at 16:39
4
\$\begingroup\$

Python 2, 250 bytes:

lambda I:zip(*sorted({k:v for v,k in[x.split(' ',1)for x in I.split('\n')]if k in'Barry Nelson,Bob Simmons,Sean Connery,Roger Moore,David Niven,George Lazenby,Timothy Dalton,Pierce Brosnan,Daniel Craig'.split(',')}.items(),key=lambda t:-int(t[1])))[0]

Demo:

>>> L = ["Barry Nelson",
...     "Bob Simmons",
...     "Sean Connery",
...     "Roger Moore",
...     "David Niven",
...     "George Lazenby",
...     "Timothy Dalton",
...     "Pierce Brosnan",
...     "Daniel Craig"]

>>> I="""2 Timothy Dalton
... 4 George Lazenby
... 5 George Lazenby
... 3 Bob Simmons"""
>>> F(I,L)
('George Lazenby', 'Bob Simmons', 'Timothy Dalton')

>>> I = """1 Sean Connery
... 2 Emma Watson
... 5 Timothy Dalton
... 4 Roger Moore
... 3 Daniel Craig"""
>>> 
>>> F(I,L)
('Timothy Dalton', 'Roger Moore', 'Daniel Craig', 'Sean Connery')
\$\endgroup\$
4
  • \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$
    – Riker
    Commented Feb 11, 2016 at 16:18
  • \$\begingroup\$ I just used dictionary comprehension in order to preserve the unique names, instead of set comprehension. \$\endgroup\$
    – Mazdak
    Commented Feb 11, 2016 at 16:33
  • 10
    \$\begingroup\$ I'd pay to see Emma Watson as James Bond. \$\endgroup\$ Commented Feb 11, 2016 at 16:47
  • \$\begingroup\$ Does it work with any number at the beginning of the line, or only single digits? \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 22:43
2
\$\begingroup\$

Pyth, 136 132 bytes

_{mtcd\ f}stcTdc"BarryNelson BobSimmons SeanConnery RogerMoore DavidNiven GeorgeLazenby TimothyDalton PierceBrosnan DanielCraig"dS.z

Try it here!

Explanation

_{mtcd\ f}stcTdc"BarryNelson BobSimmons ..."dS.z   # .z=list of all input lines
                                             S.z   # Sort input ascending
        f                                          # filter sorted lines with T being the current line
            cTd                                    # Split a line on spaces
          st                                       # Dicard the number and join first and last name
               c"BarryNelson BobSimmons ..."d      # Split the bond actor list on spaces...
         }                                         # only keep the lines which are in the actor list
   mtcd\                                           # remove the number from the filtered lines
_{                                                 # Remove duplicates from the mapping result and reverse the result

\$\endgroup\$
2
  • \$\begingroup\$ Small flaw, the ordering is the wrong way around (it's supposed to go from last to first, while yours is first to last). \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 14:44
  • \$\begingroup\$ @MKII Guess I overread that part...Fixed it! \$\endgroup\$
    – Denker
    Commented Feb 11, 2016 at 14:48
2
\$\begingroup\$

PowerShell v3+, 227 219 bytes

$a=$args-split"`n"|sort|%{$c,$b=-split$_;$b-join' '}|?{$_-in('Barry Nelson,Bob Simmons,Sean Connery,Roger Moore,David Niven,George Lazenby,Timothy Dalton,Pierce Brosnan,Daniel Craig'-split',')}|select -u
$a[$a.count..0]

121 bytes of that is just the list of actors ...

Takes input $args and -splits it on newlines with `n. Pipe that to sort, which will sort the entries numerically ascending, which is OK for now. We pipe those to a foreach loop |%{...}, each iteration take the entry, -split it on spaces, then -join the second half back together with a space (i.e., stripping the numbers off the beginning). Those (ascending) sorted names are now left on the pipeline. We pipe those through a where with ? that ensures they're -in the approved list of actors. Finally, we select only the -unique entries, which for duplicates will select the first one it encounters (i.e., the lowest-weighted one) and discard the rest. We store the resultant array of names into $a.

So, now we've got a sorted ascending list of actors. Since the challenge requires descending, we do an in-place reversal operation on $a by indexing from $a.count down to 0.

Example

PS C:\Tools\Scripts\golfing> .\sort-these-james-bond-ratings.ps1 "1 Sean Connery`n2 Emma Watson`n5 Daniel Craig`n4 Roger Moore`n3 Daniel Craig"
Roger Moore
Daniel Craig
Sean Connery

Edit -- don't need to use [array]::Reverse() when indexing will do

\$\endgroup\$
3
  • \$\begingroup\$ Can you not just use sort -Des rather than the array reversal? Granted, this could be broken in later versions of PowerShell, but I don't think it likely or a real issue ;) \$\endgroup\$ Commented Feb 12, 2016 at 13:31
  • \$\begingroup\$ @VisualMelon I had considered that, but then the select -u would grab and retain the highest-valued ordering, rather than the lowest, so for my example the positions of Daniel Craig and Roger Moore would swap. My attempts to fix that resulted in longer code than the array reversal. \$\endgroup\$ Commented Feb 12, 2016 at 14:00
  • \$\begingroup\$ ah, yes, that makes sense, I wasn't able to run it and missed that completely - it's a shame there is so much waste just for that reversal... \$\endgroup\$ Commented Feb 12, 2016 at 15:23
2
\$\begingroup\$

Python 309 286 bytes

import sys
i='Barry Nelson.Bob Simmons.Sean Connery.Roger Moore.David Niven.George Lazenby.Timothy Dalton.Pierce Brosnan.Daniel Craig'.split('.')
print ', '.join(i.pop(i.index(x)) for x in zip(*sorted((x.strip().split(' ',1) for x in sys.stdin),None,lambda x:int(x[0]),1))[1] if x in i)
\$\endgroup\$
3
  • \$\begingroup\$ Does it work with any number at the beginning of the line, or only single digits? \$\endgroup\$
    – MKII
    Commented Feb 11, 2016 at 22:44
  • \$\begingroup\$ it didn't, it does now though :) \$\endgroup\$
    – mtp
    Commented Feb 12, 2016 at 11:19
  • \$\begingroup\$ Looks like you can get rid of some extra spaces in here, for example after print or after a ) or ] \$\endgroup\$
    – wnnmaw
    Commented Feb 12, 2016 at 14:24
1
\$\begingroup\$

JavaScript (ES6), 232 bytes

s=>s.split`
`.sort((a,b)=>(p=parseInt)(a)<p(b)).map(l=>l.replace(/\d+ /,"")).filter(l=>!p[l]&/^(Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$/.test(p[l]=l))

Explanation

var solution =

s=>
  s.split`
`
  .sort((a,b)=>                 // sort the list by the number
    (p=parseInt)(a)<p(b)        // parseInt reads only the first number in a string
                                // the variable p also holds names that appeared in the
                                //     list previously
  )
  .map(l=>l.replace(/\d+ /,"")) // remove the number at the beginning of each line
  .filter(l=>
    !p[l]&                      // remove duplicates
    
    // Bond actor regex
    /^(Barry Nelson|Bob Simmons|Sean Connery|Roger Moore|David Niven|George Lazenby|Timothy Dalton|Pierce Brosnan|Daniel Craig)$/
    
    .test(p[l]=l)               // test for bondness and add the line to p
  )
<textarea id="input" rows="6" cols="40">1 Sean Connery
2 Emma Watson
5 Timothy Dalton
4 Roger Moore
3 Daniel Craig</textarea><br />
<button onclick="result.textContent=solution(input.value)">Go</button>
<pre id="result"></pre>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.