12
\$\begingroup\$

Consider a list of subject, grade pairs. E.g.

[("Latin", "A"),  ("French", "A*"),  ("Math", "B"), ("Latin", "A*")]

The task is to return the same list but with each subject given at most once. Where a subject occurred more than once originally, the returned list should have the highest grade for that subject.

Using the UK system, "A*" is a better grade than "A" and of course "A" is better than "B" etc. Only "A" can be followed by an asterisk.

The output for this case should be:

[("Latin", "A*"),  ("French", "A*"),  ("Math", "B")]

Another example:

[("Latin", "A*"),  ("French", "A*"),  ("Math", "B"), ("French", "A*"), ("Math", "C")]

The output should be:

[("Latin", "A*"),  ("French", "A*"),  ("Math", "B")]

Your returned output list can be in any order you like.

This is , so the shortest code in bytes wins.

Languages I am hoping for:

  • Python, because I wrote a terrible solution in python which inspired this question. Done.
  • C. Because real men/women code in C when it makes no sense for the problem at hand. Done.
  • Julia. Because it's an awesome language.

Input assumptions

You can assume there are no more than 10 subject/grade pairs, the input is printable ASCII and there are no spaces within the subject names.

\$\endgroup\$
16
  • 1
    \$\begingroup\$ Can we take multiset as input or output set? \$\endgroup\$
    – l4m2
    Jan 7 at 6:50
  • 1
    \$\begingroup\$ Suggest some cases where multiple same score exist \$\endgroup\$
    – l4m2
    Jan 7 at 7:34
  • 13
    \$\begingroup\$ If you're going to use the UK grade system, please also use the UK subject name - it's called "maths" here. \$\endgroup\$
    – Neil
    Jan 7 at 8:29
  • 3
    \$\begingroup\$ ‮Write it right to left perhaps?‬ \$\endgroup\$
    – Neil
    Jan 7 at 19:13
  • 2
    \$\begingroup\$ Can we output the pairs in reverse order, with the grade first? \$\endgroup\$
    – Shaggy
    Jan 8 at 12:18

17 Answers 17

8
\$\begingroup\$

Python, 52 bytes

lambda x:dict(sorted(x,key=lambda n:n[1][~0])[::-1])

is there a way to golf the list reversal?

Attempt This Online!

\$\endgroup\$
6
  • 3
    \$\begingroup\$ Is ~0 the same as -1? \$\endgroup\$
    – Simd
    Jan 7 at 9:35
  • 2
    \$\begingroup\$ indeed, even though it doesn't shorten the code, it's an amusing property (~n is -n-1) \$\endgroup\$
    – asdf256
    Jan 7 at 9:40
  • \$\begingroup\$ You can sidestep the reversal altogether, but it's no shorter. \$\endgroup\$ Jan 7 at 9:49
  • 1
    \$\begingroup\$ 51 in python 2: lambda x:dict(sorted(x,key=lambda n:`n`[-3])[::-1]) \$\endgroup\$
    – tsh
    Jan 9 at 3:16
  • 2
    \$\begingroup\$ @Simd `x` in python 2 is similar to str(x) in python 3. It actually do the same thing as your currently one. \$\endgroup\$
    – tsh
    Jan 10 at 6:55
8
\$\begingroup\$

Haskell, 58 bytes

import Data.List
s=nubBy((.fst).(==).fst).sortOn(last.snd)

Try it online!

Haskell, 77 bytes

import Data.List
(c?f)a=c(f a).f
s=nubBy((==)?fst).sortBy(compare?(last.snd))

Try it online!

Haskell, 84 bytes

import Data.List;import Data.Function
s=nubBy(on(==)fst).sortBy(on compare$last.snd)

Try it online!

Haskell, 97 bytes

import Data.List;import Data.Function
g"A*"="@";g s=s
s=nubBy(on(==)fst).sortBy(on compare$g.snd)

Try it online!

\$\endgroup\$
3
8
\$\begingroup\$

Vyxal, 9 8 bytes

⁽hġ‡tṘv∵

Try it Online!

Sort by the reverse of each grade, group by subject, best of each subject group

-1 thanks to emanresuA

Explained

⁽hġ‡tṘv∵
⁽hġ       # Group subjects by their subject
      v∵  # Get the maximum of each group by
   ‡tṘ    # Reversed grade.
\$\endgroup\$
3
  • \$\begingroup\$ How did you resolve the order of A*? \$\endgroup\$
    – Simd
    Jan 7 at 7:08
  • 5
    \$\begingroup\$ By reversing each grade. *A comes before A lexographically in python, meaning it also comes before in Vyxal. \$\endgroup\$
    – lyxal
    Jan 7 at 7:09
  • 1
    \$\begingroup\$ 8 \$\endgroup\$
    – emanresu A
    Jan 8 at 7:22
7
+50
\$\begingroup\$

Elm, 82 bytes

import Dict
List.sortBy(Tuple.second>>String.reverse)>>List.reverse>>Dict.fromList

Takes a list and returns a dict.

>> is function composition in the reversed direction: f>>g in Elm is g.f in Haskell.

According to the source code of Elm's core package, Dict.fromList will keep the last value when there are duplicate keys.

You can try it here. Here is a full test program:

import Html exposing (text)

import Dict
f=List.sortBy(Tuple.second>>String.reverse)>>List.reverse>>Dict.fromList

main = 
  [("Latin", "A*"),  ("French", "B"),  ("Math", "A"), ("French", "A*"), ("Math", "C")]
  |> f
  |> Debug.toString
  |> text
\$\endgroup\$
6
\$\begingroup\$

Factor + sets.extras, 65 49 47 bytes

[ [ last last ] sort-with [ first ] unique-by ]

Attempt This Online!

  • [ last last ] sort-with Sort the input by the last character of each grade.
  • [ first ] unique-by Retain every entry that has a subject that has not been encountered before.
\$\endgroup\$
1
  • \$\begingroup\$ Factor looks very funny. \$\endgroup\$
    – gildux
    Jan 17 at 0:08
5
\$\begingroup\$

Charcoal, 30 bytes

≔⦃⦄ηFθF‹⮌§ι¹⮌∨§η§ι⁰Z§≔η§ι⁰§ι¹η

Attempt This Online! Link is to verbose version of code. Explanation:

≔⦃⦄η

Start with no grades.

Fθ

Loop over the input grades.

F‹⮌§ι¹⮌∨§η§ι⁰Z

If the reversed grade is less than the subject's reversed grade so far or Z if there is no grade yet, then...

§≔η§ι⁰§ι¹

... update the grade for that subject.

η

Output all of the grades in Python dict format.

\$\endgroup\$
5
\$\begingroup\$

Jelly, 10 9 7 5 bytes

ZḢ«/ƙ

Try it online!

    ƙ    Group pairs by
ZḢ       their first elements,
    ƙ    and for each group
   /     reduce by
  «      recursively vectorizing minimum.

The vectorizing minimum leaves the shared subject name unchanged while taking the minimum of the letter grades, and the vectorization mechanism's length mismatch handling adds an asterisk if any of the grades for that subject has an asterisk.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ The TIO output seems to contain multiple copies of the same subject \$\endgroup\$ Jan 8 at 15:48
  • 1
    \$\begingroup\$ @DominicvanEssen Those are from three separate test cases. I've updated the footer to make that clear. \$\endgroup\$ Jan 9 at 2:31
  • 1
    \$\begingroup\$ 5 is disturbingly short. How many 5 byte Jelly programs are there? \$\endgroup\$
    – Simd
    Jan 9 at 7:36
  • \$\begingroup\$ @Simd Somewhere in the ballpark of a trillion, depending on how broken something can be to be considered a program :P \$\endgroup\$ Jan 9 at 7:54
5
\$\begingroup\$

Japt, 9 7 (6?) bytes

The second Ô could be removed if we could output the pairs in reverse order.

üÎËñÔÎÔ

Try it

üÎËñÔÎÔ     :Implicit input of 2D array
ü           :Group & sort by
 Î          :  First element
  Ë         :Map
   ñ        :  Sort by
    Ô       :    Reverse (mutating the arrays)
     Î      :  First element
            :  Reverse

6 bytes

I think this works but it needs more testing.

üÎËñÍÎ

Try it

\$\endgroup\$
4
\$\begingroup\$

JavaScript (Node.js), 42 bytes

x=>x.sort().filter(v=>[v[0]!=x,x=v[0]][0])

Try it online!

Requiring an extra empty slot after each pair, which looks not that elegant

JavaScript (Node.js), 55 bytes

x=>x.filter((v,i)=>!x.some(w=>v[0]>w[0]^[v,i--]>[w,0]))

Try it online!

JavaScript (Node.js), 53 bytes

x=>x.map(v=>!v.l[1]&v.l>o[v.n]||[o[v.n]=v.l],o={})&&o

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ How many bytes needed to fix that? I mean get rid of the extra empty slot \$\endgroup\$
    – Simd
    Jan 7 at 7:16
  • \$\begingroup\$ @Simd Can't come up with a short solution to get rid of emptyslot \$\endgroup\$
    – l4m2
    Jan 7 at 7:36
4
\$\begingroup\$

Retina 0.8.2, 23 bytes

A\*
@$&
O`
D`.*,
G`,
@

Try it online! Takes input as a newline-separated list of subject, grade pairs. Explanation:

A\*
@$&

Prefix @ to any A* grades.

O`

Sort everything into lexical order.

D`.*,

Delete duplicate subjects.

G`,

Remove lines whose subjects were deleted.

@

Remove any @ prefixes.

\$\endgroup\$
4
\$\begingroup\$

awk, 53 bytes

51 bytes of AWK, in quotes, and 9 for shell invocation…

awk -F, '{if(t[$1]<$2)t[$1]=$2}END{for(s in t) print s,t[s]}'

As noticed by user Dominic van Essen, there're cases that should be fixed that way (from 51 to 53):

awk -F, '$2$2<=t[$1]$2$2{t[$1]=$2}END{for(s in t)print s,t[s]}'

If use of blank delimiters (space or tab) is allowed, we can shorten invocation with removal of -F, (4 bytes)

Usage and notes

…assuming that the table/list is represented by a pair subject,grade per line. Then, you can echo the list directly

... | invocation

or put the list in a file

invocation < grades.txt

To try, first create the grade.txt file this way

cat >grade.txt <<-EOF
  foo,A
  bar,A
  foo,A*
  baz,D
  fiz,E
  baz,B
EOF

or with nano grade.txt for example.

Ungolfed/explanation

For golfing purposes I've used one letter variables and removed some extra spaces usually good for readability.

BEGIN {           # this block is processed once before main
IFS=","           # change Input Field Separator to comma...
}                 # ...or done via command option `-F ','`
{                 # this is the main block, executed for each line
  if (T[$1]<$2)   # build a Table indexed by first field (subject)
                  # when the second field (current grade) is greater
                  # than the existing value (cell initially empty) 
      T[$1]=$2    # then replace the table value with current grade
}
END {             # this block is executed once after main processing
  for(S in T)     # loop over the grades Table indexed by Subject
     print S,T[S] # for each entry, output the key (the Subject then)
                  # and the associated value (the maximum grade then)
}

I'm sure an AWK guru can improve it a little.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ The 'highest grade' means that 'A' is higher than 'B': this code seems to incorrectly return 'B' when both 'A' and 'B' are present: try it \$\endgroup\$ Jan 16 at 22:30
  • 1
    \$\begingroup\$ ...but can be fixed for +4 bytes... \$\endgroup\$ Jan 16 at 22:43
  • \$\begingroup\$ (and by the way: no need to count the +9 for invocation. Your score is the bytes of the AWK program itself. To account for the -F, you should label the language as AWK -F, (see here)). \$\endgroup\$ Jan 16 at 22:54
  • 1
    \$\begingroup\$ 53 bytes... \$\endgroup\$ Jan 16 at 23:01
  • 1
    \$\begingroup\$ @gildux You're welcome :) \$\endgroup\$
    – DLosc
    Jan 17 at 20:06
3
\$\begingroup\$

05AB1E, 10 bytes

.¡н}εΣθR}н

Try it online.

Explanation:

.¡ }        # Group the (implicit) input-list of pairs by:
  н         #  The first string of the pair (the subject)
    ε       # Then map over each group:
     Σ  }   #  Sort the list of pairs in this group by:
      θ     #   The last string of the pair (the grade)
       R    #   Reversed
         н  #  Then just keep the first pair
            # (after which the result is output implicitly)
\$\endgroup\$
3
\$\begingroup\$

Perl 5 -a, 66 bytes

$f{$F[0]}.=$F[1]}{pairmap{say"$a ",(sort$b=~/./g)[0]=~s/\*/A*/r}%f

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ 66 is long for perl! \$\endgroup\$
    – Simd
    Jan 9 at 23:13
3
\$\begingroup\$

Bash, 72 bytes

tr 'ABC*' '3210' | sort -k 2,2 -n -r | sort -k 1,1 -u | tr '3210' 'ABC*'

Try it online!


Original Input

Latin A*
French A
Math B
French A*
Math C

tr 'ABC*' '3210'
Convert marks into numbers

Latin 30
French 3
Math 2
French 30
Math 1

sort -k 2,2 -n -r
Numeric sort using the 2nd field; reverse order

French 30
French 3
Latin 30
Math 2
Math 1

sort -k 1,1 -u
Sort using the 1st field only (already sorted); keep unique entries

French 30
Latin 30
Math 2

tr '3210' 'ABC*'
Reverse conversion

French A*
Latin A*
Math B
\$\endgroup\$
2
  • \$\begingroup\$ I though of sort but didn't had the idea to convert grades to numbers with tr That's brillant. For golfing, short sort -k 2,2 -n -r to sort -k2,2 -nr (3 bytes less) and maybe sort -nrk2,2 (5 bytes less). By the same sort -k 1,1 -u to sort -k1,1 -u (1 byte less) or even sort -uk1,1 (3 bytes saved) ;) \$\endgroup\$
    – gildux
    Jan 19 at 0:18
  • \$\begingroup\$ I really love this! \$\endgroup\$
    – Simd
    Jan 19 at 6:29
2
\$\begingroup\$

Excel (ms365), 88 bytes

enter image description here

Formula in C1:

=LET(x,SORTBY(A1:B4,"*"&B1:B4,-1),y,TAKE(x,,1),FILTER(x,MATCH(y,y,0)=SEQUENCE(ROWS(x))))
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Impressive! But I notice that B4 is hard-coded in the formula. Is there a way to write the formula so it still works if up to 10 subject/grade pairs are entered? \$\endgroup\$
    – DLosc
    Jan 10 at 16:46
  • 1
    \$\begingroup\$ =LET(x,FILTER(A:B,A:A<>""),y,SORTBY(x,"*"&DROP(x,,1),-1),z,TAKE(y,,1),FILTER(y,MATCH(z,z,0)=SEQUENCE(ROWS(x)))) would make the whole thing dynamic upto an insane amount of pairs if need be =). \$\endgroup\$
    – JvdV
    Jan 10 at 18:09
1
\$\begingroup\$

Arturo, 66 bytes

$[a][select a'x[every? select a'y->x\0=y\0'z[>=last z\1last x\1]]]

Try it

$[a][                                ; a function taking an argument a
  select a'x[                        ; select pairs in a; assign current pair to x
    every?      <block>       'z[    ; is every element z in <block> true?
           select a'y->x\0=y\0       ; select pairs in a where subject = x's subject
      >=last z\1last x\1             ; is the last letter of z's grade >= x's?
    ]                                ; end every?
  ]                                  ; end select
]                                    ; end function
\$\endgroup\$
1
\$\begingroup\$

C (gcc), 183 177 164 155 bytes

-7 bytes thanks to ceilingcat; -13 bytes thanks to Neil; -8 bytes thanks to l4m2

#define F for(j=0;j<l;j++){for(i=0;strcmp(s[i],s[j]);i++);
i,j;f(s,g,l)char**s,**g;{F*g[i]>*g[j]|g[j][1]?g[i]=g[j]:0;}F i-j?:printf("%s %s\n",s[i],g[i]);}}

Defines a function f that takes an array of subjects, an array of grades, and an integer representing the length of both arrays. It prints each subject and the best grade in that subject to stdout. Try it online!

Ungolfed/explanation

This is an awful mess still pretty hacky, but looking a bit nicer than it originally did.

#include <stdio.h>
#include <string.h>

int i, j;
int f(char *subjects[], char *grades[], int length) {
  // Loop through the arrays using index j
  for(j = 0; j < length; j++) {
    // For each j, loop through the indices i before it until reaching one with the
    // same subject (i.e. the first entry for that subject)
    for(i = 0; strcmp(subjects[i], subjects[j]); i++);
    // Compare the grades by comparing their first characters (smaller characters
    // are better); if the first characters are the same, check if the second
    // grade has a second character (star is better than no star)
    // If the grade at index j is better than the grade at index i...
    if(grades[i][0] > grades[j][0] || grades[j][1]) {
      // Change the grade at index i to point to the grade at index j instead
      grades[i] = grades[j];
    }
  }
  // Loop through the arrays again
  for(j = 0; j < length; j++) {
    // Find the first entry for this subject
    for(i = 0; strcmp(subjects[i], subjects[j]); i++);
    // If the current entry is the first entry for this subject...
    if(i == j) {
      // Output the subject and the associated grade
      printf("%s %s\n", subjects[i], grades[i]);
    }
  }
}
\$\endgroup\$
3
  • \$\begingroup\$ 164 bytes \$\endgroup\$
    – Neil
    Jan 14 at 20:59
  • \$\begingroup\$ @Neil Ahh, of course--no need to copy the actual strings, just modify the pointers. Thanks! \$\endgroup\$
    – DLosc
    Jan 15 at 1:17
  • \$\begingroup\$ *g[i]-g[i][1]>*g[j]-g[j][1] => *g[i]>*g[j]|g[j][1] \$\endgroup\$
    – l4m2
    Jan 15 at 14:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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