19
\$\begingroup\$

Very recently, I changed my SE username because I made this account some years ago and wanted to get back into asking questions. However, I’m still planning to post questions and potentially earn votes. We can imagine a site that has some issues dealing with name changes. The problem: given a list of names, reputation changes, and name changes, can you create a leaderboard of reputation? Assume everyone's reputation starts at 0.

Input comes in any reasonable format, and has two forms: a name change <String, String> and a rep change <String, Number>.

What does any reasonable format mean? When I write questions, it usually means the input can be rearranged or repackaged to work with the way your language takes in input (do you want a list of pairs? two lists? a string? that’s all fine) as long as it doesn’t add information or otherwise offload some of the computational work onto the human.

Name changes change a person’s username to any username not currently in use. For example, if A changes name to D, and B changes name to C, D (previously known as A) could still change their name to A or to B, even though these names have been used before. However, at any given moment the set of names are unique.

Rep changes represent something like getting up/down votes on a post, receiving a bounty, etc. All rep changes are guaranteed to be integers. They might come in the form <“Bob”, 5> or <“Alice”, -53000>. At the end, you must output all usernames that are currently in existence, and their rep counts, in descending order.

Test case:

Alice 5
Bob 7
Bob John
John 113
Alice Cheryl
Daniel 13
John Alice
Alice 12

Result:

Alice 132
Daniel 13
Cheryl 5

Test case, suggested by Unrelated String:

Jimmy Adam
Donald 12
Amelia -11

Result:

Donald 12
Adam 0
Amelia -11

Test case:

CodegoLfer65 12
Bluman 9
CodegoLfer65 n4me
n4me -3

Result:

Bluman 9
n4me 9 (note: these two can be in either order)

Usernames are composed of only [A-Za-z0-9], i.e. alphanumerics. However, the input will not be a number in your language. This means usernames like 144, 015, 0x0F, 1e7 will not appear in the input. Numbers such as 1/3, 2.8 do not appear due to the character specification in the first place.

This question seems heavy on data structures, so golfing languages may not perform as well. Maybe other languages will have a fighting chance? Scoring is by lowest number of bytes, i.e. tag:code-golf .

\$\endgroup\$
15
  • 1
    \$\begingroup\$ Can a user be instantiated by changing their name, without having gained rep? \$\endgroup\$ Commented Oct 29, 2019 at 4:30
  • 1
    \$\begingroup\$ Yes. I will add this test case. Note that the "old name" in that case really doesn't matter at all. \$\endgroup\$
    – rigged
    Commented Oct 29, 2019 at 4:43
  • 2
    \$\begingroup\$ May I assume rep change will never be zero? \$\endgroup\$
    – tsh
    Commented Oct 29, 2019 at 7:36
  • 3
    \$\begingroup\$ Suggested test case of Bob 5, Bob John, Bob 10. That should result in Bob 10, John 5, right? \$\endgroup\$
    – Veskah
    Commented Oct 29, 2019 at 12:14
  • 2
    \$\begingroup\$ Must names such as 1e4 and 0x123 be permitted? If so, there should probably be a test case such as Alice 1e4, Bob 0x123. A few of the answers treat 1e4 as a number. \$\endgroup\$
    – Tesseract
    Commented Oct 30, 2019 at 16:44

12 Answers 12

7
\$\begingroup\$

Python 3, 115 bytes

def f(l):
 r={}
 for n,m in l:q=m*0!=0;r[[n,m][q]]=r.pop(n,0)+[m,0][q]
 return sorted(r.items(),key=lambda i:-i[1])

Try it online!

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

Ruby, 102 101 95 94 93 90 bytes

->c{l={};c.map{|a,b|x=l[a]||0;b*0!=0?(l[b],l[a]=x,p):l[a]=x+b};l.compact.sort_by{|a,b|-b}}

This takes input in the form of a list of ["name", "new_name"/score] pairs. And it outputs in an ordered list of ["name", score]

Golfy Tricks

  • I used p with no args to get a nil value.
  • I used String===b instead of b.is_a?String
  • I used b!=b.ord b.ord!=b to find out if it's a string or not. See String.ord and Integer.ord
  • I'm using b*0!=b to check if it's a string or not.
  • For some reason, b.ord!=b means that I don't need a space between the ? ternary operator and the following (, but if I swap it to b!=b.ord, then another whitespace is needed.
  • I sorted by -b, rather than by b and then reversing (d'oh!)

Try it online!

Edit: Thanks to Veskah, for pointing out some problems this solution was missing. Somehow adding a temporary variable to hold l[a]||0 has shortened the code by a byte.

Edit: Thanks to G B, for suggesting b*0!=0 instead of b.ord!=b, which also allowed me to get rid of the space before the ?, saving 3 characters.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ @Veskah: Yep, good find. Forgot about the implication of just setting the default value, as I set the values to nil when the name changes. \$\endgroup\$
    – IMP1
    Commented Oct 29, 2019 at 14:22
  • 1
    \$\begingroup\$ I also misunderstood that test case. Thanks for helping iron out the "edge cases" (read: cases that I just plain missed). \$\endgroup\$
    – IMP1
    Commented Oct 29, 2019 at 15:09
  • 1
    \$\begingroup\$ use b*0!=0 to check if b is a string (-3 bytes) \$\endgroup\$
    – G B
    Commented Nov 5, 2019 at 13:33
3
\$\begingroup\$

PHP, 111 bytes

for(;$n=$argv[++$i];+$r&&$a[$n]+=$r)if(!+($r=$argv[++$i])&&$a[$r]=+$a[$n])unset($a[$n]);arsort($a);print_r($a);

Try it online!

Inputs are command arguments ($argv) as a plain list like: Alice, 5, Bob, 7. Every item is a separate argument.

Commented

for(;
  $n=$argv[++$i];    // $n is set to the first parameter (name) for each event
  +$r&&$a[$n]+=$r)   // at the end of each iteration if $r is a number
                     //   add it to reputation of $n
  if(
    !+(
      $r=$argv[++$i] // $r is set to the second event parameter (name or reputation change)
    )&&
    $a[$r]=+$a[$n])  // if $r is a name, transfer reputation of $n to $r
      unset($a[$n]); //   and remove $n from $a
arsort($a);          // after the loop, sort $a by its values in descending order
print_r($a);         // print $a as string

PHP, 98 bytes

This one shows non-existing users too (at the end of the list), but shows nothing as their reputation.

for(;$n=$argv[++$i];arsort($a))+($r=$argv[++$i])?$a[$n]+=$r:[$a[$r],$a[$n]]=[+$a[$n]];print_r($a);

Try it online!

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

K (ngn/k), 65 bytes

{(>x)!x@>x}(0#`!0){(n;a):y;$[`i~@a;@[x;n;+;a];@[x;a;:;0^x@n]_n]}/

Try it online!

Input is taken as a list of pairs; user names are symbols (rather than strings).

  • (0#`!0){...}/ set up a reduce, seeded with an empty dictionary (mapping symbols to integers) that tracks the active usernames and their respective reputation scores
  • {(n;a):y;...} destructure the next pair into its name and action
  • $[`i~@a;...;...] check whether or not the action is an integer (representing a reputation change) or not
  • if the action was a reputation adjustment...
    • @[x;n;+;a] add the reputation change to that user (if this is the first time the username appears, use 0 as the fill element)
  • if the action was a username change...
    • @[x;a;:;0^x@n] carry over the reputation from the previous name to the new name (if the prior name didn't have any reputation or name changes yet, set its reputation to 0)
    • ..._n delete the old user name from our reputation tracker
  • once done handling the reputation/name changes...
    • {(>x)!x@>x} sort the users by their reputation, descending (outputting both the username and their "final" reputation)
\$\endgroup\$
2
\$\begingroup\$

Pip -rS, 71 53 bytes

{IaNI@ZllPB[a0](lY@Zl@?a1)+:bI+bNEb(ly0):b}MUg^sRVSNl

Try it online!

I'm gonna golf this some more before writing a thorough explanation. The basic idea:

  • Keep a list of [name score] pairs in l
  • Loop through the lines of input, split on spaces, using MU (for each line, the first item is a and the second item is b):
    • If a is not one of the names in l, append [a 0] to l
    • Add b to the score in l associated with the name a (if b is a name, this adds 0*)
    • If b is not a number, change the name a in l to b
  • Once the loop is done, sort l in descending numerical order (since all the names are 0 in numeric context*, this sorts by the scores)

* Assuming that no names start with a digit, which I argue is the best Pip-specific interpretation of "whatever the input is, it won't be able to be parsed as a number."

\$\endgroup\$
1
\$\begingroup\$

Haskell, 253 bytes

import Text.Read
import Data.List
m l(k,d)=maybe((d,o):n)(\a->(k,a+o):n)(readMaybe d)where o=maybe 0id(lookup k l);n=filter(\(x,_)->x/=k)l
f v=sortBy(\(_,x)(_,y)->compare y x)$filter(\(x,y)->maybe("",0)id(find(\(a,_)->a==x)r)==(x,y))r where r=foldl m[]v

The function takes a list of String pairs and returns a list of (String, Int) pairs

Full programme, 355 bytes

module Main where
import Text.Read
import Data.List
m l[k,d]=maybe((d,o):n)(\a->(k,a+o):n)(readMaybe d)where o=maybe 0id(lookup k l);n=filter(\(x,_)->x/=k)l
main=getContents>>= \c->let r=foldl m[]$map words$lines c in putStr$unlines$map(\(x,y)->unwords[x,show y])$sortBy(\(_,x)(_,y)->compare y x)$filter(\(x,y)->maybe("",0)id(find(\(a,_)->a==x)r)==(x,y))r

The programme takes input as specified in the testcases via STDIN

Try it online!

\$\endgroup\$
1
1
\$\begingroup\$

JavaScript (Node.js), 118 bytes

a=>a.flatMap(v=>([p,q]=v,u=a[p])?(q+-q?a[a[u[0]=q]=u,p]=0:u[1]+=q,[]):[q+-q?a[q]=[q,0]:a[p]=v]).sort((a,b)=>b[1]-a[1])

Try it online!

Expect input ([string, string]|[string, number])[] type.

q+-q is used to test whether q is a number or string. if q is number, q+-q will be falsy. If q is string, q+-q will be truthy.

\$\endgroup\$
2
  • \$\begingroup\$ @Arnauld That won't work for string 0x1, so I had changed to something just testing number type vs. string type. \$\endgroup\$
    – tsh
    Commented Oct 31, 2019 at 2:51
  • \$\begingroup\$ Oh, I see. My bad. You should however fix your code in the footer so that it really works, e.g. for 1e4 instead of Alice in the 1st test case. \$\endgroup\$
    – Arnauld
    Commented Oct 31, 2019 at 11:27
1
\$\begingroup\$

05AB1E, 48 bytes

¯svИy`Uk;©di®èX.ïi`X+‚ëy`:}®ǝëX.ïiyëX¾‚}ª]Σθï}R

Input as a list of pairs.

Try it online or verify all test cases.

Explanation:

¯                # Start with an empty list: []
 sv              # Loop over each input-pair `y`
   Ð             #  Triplicate the current list
    ˜            #  Flatten it
     y`          #  Push the key and value of the current item `y` separated to the stack
       U         #  Pop the value, and store it in variable `X`
       k         #  Get the index of the key in the flattened list
        ;©       #  Halve it, and store it in variable `®` (without popping)
    di           #  If this index/2 is non-negative (>= 0):
      ®è         #   Get the `®`'th pair from the current list
        X.ïi     #   If `X` is an integer:
            `    #    Push the key and value of the indexed pair separated to the stack
             X+  #    Add `X` to the value
               ‚ #    And pair the key and this new value back together
           ë     #   Else (it's a rename):
            y`   #    Push the key and value of the current item `y` separated to the stack
              :  #    And replace key with value in the indexed pair
           }     #   After the if-else:
            ®ǝ   #   Insert this modified pair at index `®` back into the list
     ë           #  Else (the current key doesn't exist yet):
      X.ïi       #   If `X` is an integer:
          y      #    Simply use the current item `y`
         ë       #   Else:
          X¾‚    #    Pair `X` with 0
         }       #   After the if-else:
          ª      #   Append this new pair to the list
  ]              # After the loop:
   Σ  }          # Sort the resulting list in ascending order by:
    θ            #  The last item of each pair (the values)
     ï           #  Casted to an integer (because the `:` might have made it a string)
       R         # And then reverse this sorted list to make it descending
                 # (after which it is output implicitly as result)
\$\endgroup\$
1
\$\begingroup\$

AWK (with shell callout to "sort"), 105 bytes

b=$1,d=$2{g=f[b]=d+0==d;f[d]=!g;g?m[b]+=d:m[d]=0+m[b]}END{for(n in m)if(f[n])print n" "m[n]|"sort -k2rn"}

Try it online!

This one builds two arrays, one with the score for a give name, and one that's a boolean flag indicating if a name is active or not. At the end, the code prints the reputation scores for all the active accounts and pipes those lines to a sort call. Sorting things in AWK is a nightmare...

Here's a description of the components.

b=$1,d=$2{                                              }

This test/code-block runs for each input line. The b=$1 and d=$2 are just a shortcuts for positional variables used repeatedly in the code. The first one is always truthy, so the code block runs even if the reputation is 0.

g=f[b]=d+0==d;

The first expression sets the "is an active account" flag (the f[] array) to true or false depending on whether or not the second token on the input line is a number. The test d+0==d catches that since AWK converts a non-numeric string to 0 if you try an use it as an integer in a mathematical expression. And g is set just to shorten the code when f[b] is needed later.

f[d]=!g;

The "is an active account" flag for the second token is the opposite of the value computed for the first token. That will set some garbage entries for input lines that have a numeric reputation as the second token, but they will be ignored.

g?m[b]+=d:m[d]=0+m[b]

This ternary sets increments the reputation counter (the m[] array) for the first token if we got a numeric second token. Otherwise it sets the reputation token for the second token to the reputation token of the first token. In other words, it copies the reputation to the new name.

END{for(n in m)if(f[n])print n" "m[n]|"sort -k2rn"}

Once all the input lines have been handles, the code loops through all the names in the reputation array m[] and for any that are active users per the f[] array, it prints the name and final reputation. The sort -k2rn command sorts the whole set and prints the output.

\$\endgroup\$
0
\$\begingroup\$

AWK, 132 bytes

/.* -?[0-9]/{a[$1]+=$2}
!/.* -?[0-9]/{a[$2]=a[$1]+0;delete a[$1]}
END{PROCINFO["sorted_in"]="@val_num_desc";for(k in a)print k,a[k]}

Try it online!

We're careful to +0 to make sure the new name has reputation. The hardest part of this problem is printing the list out sorted by values. Thankfully, GNU Awk has an array called PROCINFO, and by writing the special value "@val_num_desc", the array will be numerically sorted in descending order by value.

\$\endgroup\$
0
\$\begingroup\$

Perl 5 -a, 114 bytes

$_=pop@F;/^-?\d+$/?$k{$F[0]}+=$_:($k{$_}=$k{$F[0]}+0,delete$k{$F[0]})}{say"$_ $k{$_}"for sort{$k{$b}-$k{$a}}keys%k

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Java 10, 166 251 bytes

import java.util.*;a->{var r=new HashMap<String,Long>();for(var l:a)if(l[1].matches("-?\\d+"))r.merge(l[0],new Long(l[1]),Long::sum);else{var t=r.remove(l[0]);r.put(l[1],t==null?0:t);}return r.entrySet().stream().sorted(Map.Entry.comparingByValue());}

+85 bytes because I hadn't noticed the output should be sorted by value.. >.>

Input as a 2D list of Strings, output as a sorted stream of key-value pairs.

Try it online.

Explanation:

import java.util.*;            // Required import for Map and HashMap
a->{                           // Method with 2D String-array as parameter
                               //  and a Stream of String-Long KeyValue-pairs as return-type
  var r=new HashMap<String,Long>();
                               //  Map, starting empty
  for(var l:a)                 //  Loop over the lines of the input:
    if(l[1].matches("-?\\d+")) //   If the second part is an integer:
      r.merge(l[0],            //    Use the first part as key
              new Long(l[1]),  //    Convert the second part to an integer
              Long::sum);      //    And sum it with a potential existing value for this key
    else{                      //   Else (it's a rename):
      var t=r.remove(l[0]);    //    Remove the first part from the map;
                               //    store the current value (of null if not present) in `t`
      r.put(l[1],              //    Use the second part as new key
            r.remove(l[0]));   //    And remove the first part from the map,
                               //    which will return the current value
                               //    (or null if not present)
  return r                     //  Return the Map as result
    .entrySet().stream()       //  after we've converted it to a steam of KeyValue pairs,
    .sorted(Map.Entry.comparingByValue());}
                               //  and sorted it by value
\$\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.