text:
The best base is 10... Let's reach it!
Input:
A positive integer n consisting of digits in the range 0-9.
Challenge:
If d is the highest digit in the integer, assume the base of the number is d+1. E.g. if the integer is 1256 then you shall assume it's in base-7, if it's 10110 then you shall assume it's base-2 (binary), and if it's 159 then it's decimal.
Now, do the following until you either, 1: reach a base-10 integer, or 2: reach a single digit integer.

Convert the integer from base-(d+1) to base-10
Find the base of this new integer (again, base-(d+1) where d is the highest digit in the new number)
Go to step 1.

Examples:
Assume the input is n = 413574. The highest digit d=7, so this is base-8 (octal). Convert this to decimal and get 137084. The highest digit d=8, so this is base-9. Convert this to decimal and get 83911. The highest digit is 9, so this is a decimal number and we stop. The output shall be 83911.
Assume the input is n = 13552. The highest digit is d=5, so this is base-6. Convert this to decimal and get 2156. The highest digit d=6, so this is base-7. Convert this to decimal and get 776. The highest digit is d=7, so this is base-8. Convert this to decimal and get 510. The highest digit is d=5 so this is base-6. Convert this to decimal and get 186. The highest digit is 8, so this is base-9. Convert this to decimal and get 159. The highest digit is 9, so this is a decimal number and we stop. The output shall be 159.
Assume the input is n=17. This will give us 15, then 11, then 3, which we will output since it's a single digit.

Test cases:
5
5

17
3

999
999

87654321  (base-9 -> 42374116 in decimal -> base-7 -> 90419978 in decimal)
9041998

41253  (5505 -> 1265 -> 488 -> 404 -> 104 -> 29)
29

Notes:

Standard rules regarding I/O, loopholes etc. You may take the input as a string
Explanations are encouraged
You may use builtin base-conversion commands

Solutions that don't use the language's builtin base-conversion functions (if they exist) are welcome, even if they end up being much longer than the obvious approach using builtin functions.

Apparently, this is OEIS A091047.

Apparently I love matrices, so I thought I'd do a base-conversion challenge instead.

"The best base is 10"... "10" is written in which base?

@OlivierGrégoire That's the clever thing... Whatever base we end up with, it will still be a valid statement!

@StewieGriffin But how do you read it? "10" has a different name in each base.

Well, that depends on the base... or as Meghan would say it "it's all about that base,
'Bout that base, no trouble"  ;-)

Now tell me why there are infinitely many numbers which terminate in a single digit...

-1. Clearly 13 is the best base!! ;)

@StewieGriffin For a guy so into matrices, this must have been a change of base :)

"10" is written in decimal.

@user11153 I'm pretty sure 10 interpreted in base $n$ is $n$: the 0 represents $0n^0 = 0$, and the 1 represents $1n^1 = n$.

@SolomonUcko "10" is named "ten" only in one base.

@user11153 Exactly? I was saying that the name of 10 in each base is that base. For example, 10 in base two is "two".

Was expecting to see an "All your base are belong to us" comment here, so I'll be the one to say it.

Mathematica, 56 bytes
#//.x_/;(b=Max[d=IntegerDigits@x]+1)<11:>d~FromDigits~b&

Try it online! (Using Mathics.)
I thought I'd check out what the sequence looks like:

And here is a plot of the number of steps it takes to find the result:

(Click for larger versions. See the revision history for plots only up to n = 1000.)
Looks like a very interesting mixture of large scale structure and fine scale chaos. I wonder what's up with the wider gaps around 30,000 and 60,000.

@StewieGriffin should be on the left side. The small gaps are there, because the numbers leading up to a multiple of a power of 10 contain a 9, so they're already in base 10. But for 30k and 60k it seems that numbers with an 8 or even 7 (would have to check) in place of that 9 always become base 10 after at most one step.

@StewieGriffin To be precise, all numbers in the range [28051, 28888] are base 9 and translate to numbers of the form 19xxx, thus pretty much doubling that gap.

Ah, got it... :) For the record: I knew why there were gaps on the left side of powers of 10's... It was only the double sized gaps that were strange (why 30/60, and not 20/40/50). I see now though that numbers in the range 28xxx -> 19xxx, but 38xxx -> 26xxx, not 29xxx.

Java 8, 172 166 163 152 151 140 138 116 114 99 bytes
s->{for(Integer b=0;b<10&s.length()>1;s=""+b.valueOf(s,b=s.chars().max().getAsInt()-47));return s;}

Takes the input as a String.
-64 bytes thanks to @OlivierGrégoire. And here I thought my initial 172 wasn't too bad.. ;)
Try it here.
Explanation:
s->{                    // Method with String as parameter and return-type
  for(Integer b=0;b<10  //  Loop as long as the largest digit + 1 is not 10
      &s.length()>1;    //  and as long as `s` contains more than 1 digit
    s=""+               //   Replace `s` with the String representation of:
         b.valueOf(s,   //    `s` as a Base-`b` integer
          b=s.chars().max().getAsInt()-47)
                        //     where `b` is the largest digit in the String + 1
  );                    //  End of loop
  return s;             //  Return result-String
}                       // End of method

Promise to stay calm? :p Ok... let's go: s->{for(Integer b=0;b<10&s.length()>1;)s=""+b.valueOf(s,b=s.chars().max().getAsInt()-47);return s;}. Also I deleted most of my comments as they're totally irrelevant now (b is the base, your a; and s is the number we're working on).

I've tried to recursify this to shave off some bytes with: Integer b;return(b=s.chars().max().getAsInt()-47)>9|s.length()<2?s:c(""+b.valueOf(s,b)); (88), but I'm brand new to code golf. This is a snipppet, right? Is there a way to declare this as a method without needing to add public String c(String s) ?

@LordFarquaad You don't need the public, but I'm afraid you'll indeed have to use String c(String s){} for recursive-calls, even in Java 8. When you create a lambda using java.util.function.Function<String, String> c=s->{Integer b;return(b=s.chars().max().getAsInt()-47)>9|s.length()<2?s:c.apply(""+b.valueOf(s,b));} or interface using interface N{String c(String s);}N n = s->{Integer b;return(b=s.chars().max().getAsInt()-47)>9|s.length()<2?s:n.c(""+b.valueOf(s,b));}; it will give an "self-reference in initializer error" in both cases. But very nice approach nonetheless!

Ah, I guess I ungolfed it a few bytes then. But thanks for the help! I'll keep this in mind moving forward.

Pyth, 9 bytes
ui`GhseS`

Test suite
Explanation:
ui`GhseS`
ui`GhseS`GQ    Implicit variable introduction
u         Q    Repeatedly apply the following function until the value repeats,
               starting with Q, the input.
        `G     Convert the working value to a string.
      eS       Take its maximum.
     s         Convert to an integer.
    h          Add one.
 i`G           Convert the working value to that base

It's a bit weird how you used a two-variable lambda ending up using one variable...and it didn't throw error(s). Oh, the two variables are Q and Q, I get it.

@EriktheOutgolfer Not exactly. u without its third input is apply until repetition, whereas with a third input it is apply a fixed number of times. u lambda has G and H, but you don't need to use H.

Actually replacing G with H would've had the same result...btw implicit variable is G?

@EriktheOutgolfer The implicit variable is G, yes. H counts up from 0 with each iteration, so it's completely different. I'm not really sure what you're talking about. Here's an example program to show you what's going on: https://pyth.herokuapp.com/?code=u%2B%2a0%0AHi%60GhseS%60&test_suite=1&test_suite_input=5%0A17%0A999%0A87654321%0A4&debug=0

JavaScript (ES6), 63 57 54 53 bytes
f=a=>a>9&(b=Math.max(...a+""))<9?f(parseInt(a,b+1)):a

Saved 8 bytes thanks to Shaggy and Dom Hastings

f=a=>a>9&(b=Math.max(...a+""))<9?f(parseInt(a,b+1)):a;

console.log(f("413574"))

Beat me to it. I think you could save a few bytes with +a>9||b<9 and reversing the ternary.

@Shaggy edit: I'm dumb

Don't be so hard on yourself Tom! You're not dumb,,, but you're definitely not as smart as @Shaggy!

Alternative for 55 bytes f=n=>n>9&&(k=Math.max(...n+"")+1)<10?f(parseInt(n,k)):n

@DomHastings Thanks for the improvements! :)

Can you not save a byte by using & instead of &&?

@Neil You can; I was just in the middle of changing that! :)

Oops, sorrry, yes that should have been && instead of || :\

Python 3, 91 78 76 75 73 bytes
@Emigna shaved off 5 bytes. @FelipeNardiBatista saved 1 byte. @RomanGräf saved 2 bytes
i=input()
while'9'>max(i)and~-len(i):i=str(int(i,int(max(i))+1))
print(i)

Try it online!

Explanation
i=input()                                  - takes input and assigns it to a variable i
while '9'>max(i)and~-len(i):               - repeatedly checks if the number is still base-9 or lower and has a length greater than 1
    i=str(...)                             - assigns i to the string representation of ...
          int(i,int(max(i))+1)             - converts the current number to the real base 10 and loops back again
print(i)                                   - prints the mutated i

05AB1E, 10 5 bytes
5 bytes saved thanks to Magic Octopus Urn
F§Z>ö

As this grows slow very quickly for large input, I'm leaving the old much faster version here for testing. The algorithm is the same, only the number of iterations differ.
[©Z>öD®Q#§

Try it online!
Explanation
[             # start a loop
 ©            # store a copy of the current value in the register
  Z>          # get the maximum (digit) and increment
    ö         # convert the current value to base-10 from this base
     D        # duplicate
      ®Q#     # break loop if the new value is the same as the stored value
         §    # convert to string (to prevent Z from taking the maximum int on the stack)

Also, can't you just use тFZ>ö§? Seeing as the number of iterations (as seen here) seems to plateau? If you want to get technical, the rate at which the iterations rise is probably logarithmic... So you could just use something like: DFZ>ö§ and state it won't run for large n. OR maybe even: T.n>FZ>ö§ to directly calculate the number of iterations as log_10(n).

@MagicOctopusUrn: Yeah, now that you mention it, as the sequence definitely seems slower than linear, F§Z>ö should do the trick.

I think you can remove the §.

@EriktheOutgolfer: Unfortunately no. If we remove the §, Z will take the highest number on the stack instead of the highest digit in the number on top of the stack.

@Emigna Huh?

@EriktheOutgolfer: The problem starts with the second iteration as the input will be interpreted as string the first time. Look at this

APL (Dyalog), 20 16 bytes
Takes and returns a string
((⍕⊢⊥⍨1+⌈/)⍎¨)⍣≡

(…)⍣≡ apply the following function until two consecutive terms are identical:
 ⍎¨ execute each character (turns the string into a list of numbers)
 (…) apply the following tacit function to that:
  ⌈/ find the maximum of the argument
  1+ add one
  ⊢⊥⍨ evaluate the argument in that base
  ⍕ format (stringify, in preparation for another application of the outer function)
Try it online!

Ruby, 60 56 bytes
->n{n=(s=n.to_s).to_i(s.bytes.max-47)while/9/!~s&&n>9;n}

Try it online!

Perl 6, 49 bytes
{($_,{.Str.parse-base(1+.comb.max)}...*==*).tail}

Test it
Expanded:
{
  (

    $_,                 # seed the sequence with the input

    {
      .Str
      .parse-base(      # parse in base:
        1 + .comb.max   # largest digit + 1
      )
    }

    ...                 # keep generating values until

    * == *              # two of them match (should only be in base 10)

  ).tail                # get the last value from the sequence
}

Mathematica, 52 bytes
FromDigits[s=IntegerDigits@#,Max@s+1]&~FixedPoint~#&

Pure function taking a nonnegative integer as input and returning a nonnegative integer. Uses the same core mechanic FromDigits[s=IntegerDigits@#,Max@s+1] as Jenny_mathy's answer, but exploits FixedPoint to do the iteration.

Python 2, 60 59 56 53 bytes
Saved 4 byte thanks to Felipe Nardi Batista
Saved 3 bytes thanks to ovs
f=lambda x,y=0:x*(x==y)or f(`int(x,int(max(x))+1)`,x)

Try it online!
Using a recursive lambda, comparing the result of the base conversion to the previous iteration.

why not use x==y and x or ... as x will never be 0 (base 1). or even (x==y)*x or ...

@FelipeNardiBatista: Thanks! I tried x and x==y or ... which didn't work, but I'm not too proficient with these tricks so I didn't realize I could reverse it :)

@ovs: Right you are. Thanks!

@FelipeNardiBatista "Input:
A positive integer n consisting of digits in the range 0-9." 0 is still valid input, and now the code rejects it.

@Mast: Fortunately for us, 0 isn't a positive integer so it won't be given as input.

@Mast later i realized that it doesn't matter as the 0 would be inside a string

@Mast and still, 0 is an invalid input as there is no base 1 in most languages, the old version wouldn't work with "0" either way

@Emigna you can still golf it to 53 bytes with :x*(x==y)or

@FelipeNardiBatista: Of course, I should have realized that. Thank you :)

PHP, 71 bytes
for(;$b<10&9<$n=&$argn;)$n=intval("$n",$b=max(str_split($n))+1);echo$n;

Try it online!

C#, 257 244 243 244 233 222 bytes
using System.Linq;z=m=>{int b=int.Parse(m.OrderBy(s=>int.Parse(s+"")).Last()+""),n=0,p=0;if(b<9&m.Length>1){for(int i=m.Length-1;i>=0;i--)n+=int.Parse(m[i]+"")*(int)System.Math.Pow(b+1,p++);return z(n+"");}else return m;};

Well, C# always takes a lot of bytes but this is just ridiculous. None of the built-ins can handle an arbitrary base, so I had to calculate the conversion myself. Ungolfed:
using System.Linq;
z = m => {
int b = int.Parse(m.OrderBy(s => int.Parse(s + "")).Last()+""), n = 0, p = 0; //Get the max digit in the string
if (b < 9 & m.Length > 1) //if conditions not satisfied, process and recurse
{
    for (int i = m.Length - 1; i >= 0; i--)
        n += int.Parse(m[i] + "") * (int)System.Math.Pow(b+1, p++); //convert each base-b+1 representation to base-10
    return z(n + ""); //recurse
}
else return m; };

Pip, 17 bytes
Wa>9>YMXaaFB:1+ya

Takes input as a command-line argument. Try it online!
Explanation
This was fun--I got to pull out the chaining comparison operators.
We want to loop until the number is a single digit OR contains a 9. Equivalently, we want to loop while the number is multiple digits AND does not contain a 9. Equivalently, loop while the number is greater than 9 AND the maximum digit is less than 9: a>9>MXa.
Wa>9>YMXaaFB:1+ya
                   a is 1st cmdline arg (implicit)
     YMXa          Yank a's maximum digit into the y variable
Wa>9>              While a is greater than 9 and 9 is greater than a's max digit:
         aFB:1+y    Convert a from base 1+y to decimal and assign back to a
                a  At the end of the program, print a

Mathematica, 92 bytes
f[x_]:=FromDigits[s=IntegerDigits@x,d=Max@s+1];(z=f@#;While[d!=10&&Length@s>1,h=f@z;z=h];z)&

Javascript (ES6) with 0 arrow function, 74 bytes
function f(a){a>9&&b=Math.max(...a)<9&&f(parseInt(a,b+1));alert(a)}f('11')

Welcome to PPCG! :) This is probably a very nice answer, but I can't tell without an explanation. I (and probably others) never upvote answers that don't contain explanations.

Why are you calling f('11') after the function?  Unless I'm missing something that only looks to be usage not actually part of the submission.  If so, you should take it out of the code section and put it in your explanation (when you add one) and update your byte count to 67.

K4, 19 bytes
Solution:
{(1+|/x)/:x:10\:x}/

Examples:
q)\
  {(1+|/x)/:x:10\:x}/413574
83911
  {(1+|/x)/:x:10\:x}/13552
159
  {(1+|/x)/:x:10\:x}/17
3
  {(1+|/x)/:x:10\:x}/41253
29

Explanation:
Use /: built-in to convert base.
{(1+|/x)/:x:10\:x}/ / the solution
{                }/ / converge lambda, repeat until same result returned
            10\:x   / convert input x to base 10 (.:'$x would do the same)
          x:        / store in variable x
 (     )/:          / convert to base given by the result of the brackets
    |/x             / max (|) over (/) input, ie return largest
  1+                / add 1 to this

Kotlin, 97 bytes
fun String.f():String=if(length==1||contains("9"))this else "${toInt(map{it-'0'}.max()!!+1)}".f()

Beautified
fun String.f(): String = if (length == 1 || contains("9")) this else "${toInt(map { it - '0' }.max()!! + 1)}".f()

Test
fun String.f():String=if(length==1||contains("9"))this else "${toInt(map{it-'0'}.max()!!+1)}".f()
val tests = listOf(
        5 to 5,
        17 to 3,
        999 to 999,
        87654321 to 9041998,
        41253 to 29
)

fun main(args: Array<String>) {
    tests.forEach { (input, output) ->
        val answer = input.toString().f()
        if (answer != output.toString()) {
            throw AssertionError()
        }
    }
}

TIO
TryItOnline

Husk, 8 bytes
ωoṠBo→▲d

Try it online!
ωoṠBo→▲d
ω           # repeat until output no longer changes:
 oṠ         # apply function to value & function-of-value
       d    # value: digits of argument
   Bo→▲     # function of value:
   B        # interpret digits as base n, with n given by
    o→▲     # max digit +1

Japt, 25 bytes
>9©(A=Uì rw)<9?ßUs nAÄ):U

Try it online!

Jelly, 9 bytes
DḅṀ‘$$µÐL

Try it online!

C, 159 157 bytes
#include <stdlib.h>
char b[99];i=0;m=-1;c(n){do{m=-1;sprintf(b,"%d",n);i=0;while(b[i]){m=max(b[i]-48,m);i++;}n=strtol(b,0,++m);}while(b[1]&&m<10);return n;}

126 bytes

Scala, 119 bytes
if(x.max==57|x.length==1)x else{val l=x.length-1
f((0 to l).map(k=>(((x(k)-48)*math.pow(x.max-47,l-k))) toInt).sum+"")}

Try it online!
Scala, 119 bytes
if(x.max==57|x.length==1)x else f((0 to x.length-1).map(k=>(((x(k)-48)*math.pow(x.max-47,x.length-1-k))) toInt).sum+"")

Try it online!
Both methods work the same way, but in the first one I put x.length-1 in a variable, and in the second one I don't.

Japt, 12 bytes
_=ì)ìZrÔÄ}gN

Try it

J, 22 bytes
(#.~1+>./)@(,.&.":)^:_

Try it online!

Vyxal, 6 bytes
fG›β)Ẋ

Try it Online!
Will be 5 bytes once a bug fix is made.
Explained
fG›β)Ẋ
    )Ẋ # Until the result of applying the following does not change:
fG     #   Get the biggest digit of the current value - should just be G, but bugs
  ›β   #   and convert the current value from base (the biggest digit + 1) to base 10


text:
Find the highest unique digit
Surprisingly we haven't had a simple "find the highest digit" challenge yet, but I think that's a little too trivial.
Given input of a non-negative integer, return the highest unique (ie not repeated) digit found in the integer. If there are no unique digits, your program can do anything (undefined behaviour), other than the numbers that are non unique in the input of course.
The input can be taken as a single integer, a string, or a list of digits.
Test cases
12         -> 2
0          -> 0
485902     -> 9
495902     -> 5
999999     -> Anything
999099     -> 0
1948710498 -> 7

This is code-golf so fewest bytes in each language wins!

Can we take input as a string instead?

Given the last test case, I think we are forced to take input as a string... (leading zeroes can't be represented in integers)

@Leo that was my bad actually, basically mashed the numbers on my keyboard, didn't notice the leading zero. But yes, input can be taken as a string

Then why can't we just take a list of digits (or numbers for that sake)?

Can we throw an error if there are no unique digits?

@Adám "undefined behaviour" generally means you can do anything, including summoning nameless horrors from the void if that saves bytes.

@MartinEnder in fact I'll happily knock off 50% of your bytes if your code successfully summons cthulhu upon there being no unique digits ;)

@Mayube Can we take a list of digits?

@Adám sure, most people are only splitting it into a list of some form anyway, I'll explicitly state accepted input formats on the post

Regarding the list of digits: can it be either a list of integers or a list of characters? ([1,2,3] vs ['1','2','3'])

@Arnauld no. List of integer digits only.

Python 3, 40 bytes
Saved 2 bytes thanks to movatica.
lambda i:max(x*(i.count(x)<2)for x in i)

Try it online!
42 bytes
Works for both String and list of digits parameter types. Throws an error for no unique digits, kind of abuses of that spec:
lambda i:max(x for x in i if i.count(x)<2)

Try it online!

Explanation

lambda i: - Declares a lambda function with a string or list of digits parameter i.
max(...) - Finds the maximum value of the generator.
x for x in i - Iterates through the characters / digits of i.
if i.count(x)<2 - Checks if the digit is unique.

40 bytes: lambda i:max(x*(i.count(x)<2)for x in i)

05AB1E, 4 3 bytes
Saved 1 byte thanks to Mr. Xcoder notifying that a digit list is valid input.
¢ÏM

Try it online!
Explanation
¢     # count occurrences of each digit in input
 Ï    # keep only the digits whose occurrences are true (1)
  M   # push the highest

Wait so in 05AB1E, 2 is not truthy; only 1? :o

@HyperNeutrino: Correct!

That seems both very useful and very bothersome... That is interesting :o :D

@HyperNeutrino: It often comes in handy, but it can be a disadvantage when the challenge says return a truthy value, when a lot of languages can return any positive integer or maybe even a non-empty string.

A strikethrough on the number for is not easy to see!

Alice, 15 bytes
/&.sDo
\i-.tN@/

Try it online!
Explanation
/...
\.../

This is a simple framework for linear code that operates entirely in Ordinal mode (meaning this program works completely through string processing). The unfolded linear code is then just:
i..DN&-sto@

What it does:
i    Read all input as a string.
..   Make two copies.
D    Deduplicate the characters in the top copy.
N    Get the multiset complement of this deduplicated string in the input.
     This gives us a string that only contains repeated digits (with one
     copy less than the original, but the number of them doesn't matter).
&-   Fold string subtraction over this string, which means that each of
     the repeated digits is removed from the input.
s    Sort the remaining digits.
t    Split off the last digit.
o    Print it.
@    Terminate the program.

-1, doesn't "summoning nameless horrors from the void" if there are no unique digits. ;) (Read: +1, great answer as always.)

@KevinCruijssen I tried, but it didn't save bytes. Maybe Dark might be a more appropriate language...

Retina, 16 bytes
O`.
(.)\1+

!`.$

Try it online!
Explanation
O`.

Sort the digits.
(.)\1+

Remove repeated digits.
!`.$

Fetch the last (maximal) digit.

Too bad Deduplicate doesn't help here :(

Charcoal, 18 12 bytes
Ｆχ¿⁼№θＩι¹ＰＩι

Try it online! (Link to verbose version)
Prints nothing if no solution is found. The trick is that the for loop prints every unique number in the input string, but without moving the cursor, thus the value keeps reprinting itself until the final solution is found.
The previous version printed the characters A to Z when no solution was found, hence the comments:
ＡααＦχＡ⎇⁼№θＩι¹Ｉιααα

Try it online! (Link to verbose version)

That's an interesting undefined behavior :)

This sounds Finnish to me :D

@fedorqui nice to see you here! Yeah, but Charcoal is easier to learn than Jelly or O5AB1E, and it's funnier to use in ASCII-art games. :-)

Husk, 7 bytes
→fo¬hgO

Try it online! (Test suite, crashes on the last test case since it has no unique digits)
This is a composition of functions in point-free style (the arguments are not mentioned explicitely anywhere). Takes input and returns output as a string, which in Husk is equivalent to a list of characters.
Explanation
Test case: "1948710498"

      O    Sort:                             "0114478899"
     g     Group consecutive equal elements: ["0","11","44","7","88","99"]
 fo¬h      Keep only those with length 1*:   ["0","7"]
→          Take the last element:            "7"

*The check for length 1 is done by taking the head of the list (all elements except the last one) and negating it (empty lists are falsy, non-empty lists are truthy).

JavaScript (ES6), 46 41 40 bytes
Takes input as a string. Returns RangeError if there are no unique digits.
s=>f=(i=9)=>s.split(i).length-2?f(--i):i

-7 bytes thanks to Rick Hitchcock
-1 byte thanks to Shaggy
Test cases

let f =

s=>g=(i=9)=>s.split(i).length-2?g(--i):i

console.log(f("12")())         // 2
console.log(f("0")())          // 0
console.log(f("485902")())     // 9
console.log(f("495902")())     // 5
//console.log(f("999999")())   // RangeError
console.log(f("999099")())     // 0
console.log(f("1948710498")()) // 7

Remove the alert for 39 bytes:  (s,i=9)=>s.split(i).length-2?f(s,--i):i.  You can avoid the stack overflow for 42 bytes:  (s,i=9)=>s.split(i).length-2?i&&f(s,--i):i.

Save a byte with currying: s=>g=(i=9)=>s.split(i).length-2?g(--i):i and then call it with f("12")()

Haskell, 37 bytes
f s=maximum[x|x<-s,[x]==filter(==x)s]

Try it online!
How it works:
  [  |x<-s   ]          -- loop x through the input string s
    x                   -- and keep the x where
     [x]==filter(==x)s  -- all x extracted from s equal a singleton list [x]
maximum                 -- take the maximum of all the x

R, 41 bytes
function(x,y=table(x))max(names(y[y==1]))

An anonymous function that takes a list of digits, either as integers or single character strings. It precomputes y as an optional argument to avoid using curly braces for the function body. Returns the digit as a string. This takes a slightly different approach than the other R answer and ends up being the tiniest bit shorter! looks like my comment there was wrong after all...
table computes the occurrences of each element in the list, with names(table(x)) being the unique values in x (as strings). Since digits are fortunately ordered the same lexicographically as numerically, we can still use max.
Try it online!

Nice! I didn't expect doing something with table would be shorter (plus I can never remember how to get names to work).

<2 for another byte.  There should never be a zero in the counts.

y=table(scan());max(names(y[y<2])) is a few bytes shorter.

Python 3, 40 bytes
lambda i:max(x+9-9*i.count(x)for x in i)

Only works for lists of digits. The edge case '990' works fine :)
Try it online!

Welcome to PPCG! Looks like you've got everything down :)

Husk, 3 bytes
►≠O

Try it online!
Explanation
►≠O
  O order the elements
►   max by
 ≠  inequality(selects least frequent elements)
    then returns the last of the least frequent elements

Brachylog, 8 bytes
ọtᵒtᵍhth

Try it online!
Explanation
Example input: 495902

ọ          Occurences:    [[4,1],[9,2],[5,1],[0,1],[2,1]]
 tᵒ        Order by tail: [[0,1],[2,1],[4,1],[5,1],[9,2]]
   tᵍ      Group by tail: [[[0,1],[2,1],[4,1],[5,1]],[[9,2]]]
     h     Head:          [[0,1],[2,1],[4,1],[5,1]]
      t    Tail:          [5,1]
       h   Head:          5

Husk, 9 8 bytes
Thanks to Leo for suggesting a slightly neater solution at the same byte count.
▲‡ȯf=1`#

Try it online!
Explanation
  ȯ       Compose the following thre functions into one binary function.
      `#  Count the occurrences of the right argument in the left.
    =1    Check equality with 1. This gives 1 (truthy) for values that
          appear uniquely in the right-hand argument.
   f      Select the elements from the right argument, where the function
          in the left argument is truthy.
          Due to the composition and partial function application this
          means that the first argument of the resulting function actually
          curries `# and the second argument is passed as the second
          argument to f. So what we end up with is a function which selects
          the elements from the right argument that appear uniquely in
          the left argument.
 ‡        We call this function by giving it the input for both arguments.
          So we end up selecting unique digits from the input.
▲         Find the maximum.

¬← could be more simply =1, same bytecount though :)

@Leo Ah yeah, I was too lazy to test whether the currying would work without parentheses. I need to trust more in the type inference. ;)

APL (Dyalog Unicode), 10 chars = 19 bytes
Method: multiply elements that occur multiple times by zero, and then fine the highest element.
⌈/×∘(1=≢)⌸

⌸ for each unique element and its indices in the argument:
 × multiply the unique element
 ∘(…) with:
  1= the Boolean for whether one is equal to
  ≢ the tally of indices (how many times the unique element occurs)
⌈/ the max of that
Try it online!
APL (Dyalog Classic), 15 bytes
⌈/×∘(1=≢)⎕U2338

Try it online!
Identical to the above, but uses ⎕U2338 instead of ⌸.

Mathematica, 41 bytes
(t=9;While[DigitCount[#][[t]]!=1,t--];t)&

thanks @Martin Ender
here is Martin's approach on my answer
Mathematica, 35 bytes
9//.d_/;DigitCount[#][[d]]!=1:>d-1&

Python 2, 39 bytes
lambda l:max(1/l.count(n)*n for n in l)

Try it online!

I enjoyed this, it’s great!

R, 45 43 bytes
function(x)max(setdiff(x,x[duplicated(x)]))
Try it online!
Takes input as a vector of integers. Finds the duplicated elements, removes them, and takes the maximum. (Returns -Inf with a warning if there is no unique maximum.)
Edited into an anonymous function per comment

max(x[!duplicated(x)]) is quite a bit shorter, but this is a great answer. I knew the way I was going to do it was not this good. Also you can remove the f= from the beginning, as anonymous functions are perfectly valid answers.
Also, you can use TIO to test your functions if you use this format: Try it online!

Thanks! I think the 'duplicated' function doesn't count the first occurrence of a duplicate element so your version wouldn't quite work

ah, good point. I almost never use duplicated but I actually thought up another, shorter answer!

Ruby, 42 bytes
->x{(?0..?9).select{|r|x.count(r)==1}[-1]}

Try it online!

Ruby ties Python :)

Because 42 is always the answer. :-)

@GB Or is it: ->x{x.chars.select{|r|x.count(r)<2}.max}

That would be 2 bytes shorter and ruin the whole thing. :-)

C# (.NET Core), 27 97 86 58 57 75 bytes
using System.Linq;

n=>n.GroupBy(i=>i).Where(i=>i.Count()<2).Max(i=>i.Key)-48

Try it online!
Thanks @CarlosAlejo

This does not work with "1948710498" as input (returns "9" instead of "7"), and you must add using System.Linq; to the byte count.

@CarlosAlejo Oops! Sorry! Just now read the specifications fully. Will edit the solution soon.

Edited. Are there any optimisations I can make?

Sure: try using OrderBy(...).Last() instead of .OrderByDescending(...).First(), for instance. Or even better, change your last part with .Max(i=>i.Key) after the Where clause.

@CarlosAlejo Thanks! Edited.

And even better: you can get rid of the Select clause and apply the transformation (changing '0'with its ASCII value 48) only to the final result, so you get: n=>n.GroupBy(i=>i).Where(i=>i.Count()<2).Max(i=>i.Key)-48.Try it online!

You need to include using System.Linq; into the byte count here.

Wrote this off the top of my head so haven't checked it but I believe this will be shorter if it works: using System.Linq;n=>n.Where(c=>n.Distinct(h=>h).Contains(c)).Max()-48 or maybe this: using System.Linq;n=>n.Where(c=>n.Distinct(h=>h).Any(a=>a==c)).Max()-48

You can use the C# Interactive compiler now without counting using System.Linq;, and you can also move the both the Where and the GroupBy things into the Max call for 38 bytes. Try it online!

Bash + coreutils, 30 28 bytes
-2 bytes thanks to Digital Trauma
fold -1|sort|uniq -u|tail -1

Try it online!

Bash + coreutils, 20 bytes
sort|uniq -u|tail -1

Try it online!
If input is given as a list of digits, one per line, we can skip the fold stage. That feels like cheating though.

Replace grep -o . with fold -1 to save 2 bytes.  I agree that an input integer given as a list of digits is stretching the rules too far.

+1 just because it is bash

<>< (Fish), 93 bytes
0l1-l2-$:?v~~o;
v&]{[-3lr <   |.!01~$r&\
>r$1-:?!v$rl3-[{]:&:&=?^
@&::&:$~/|.!01 $}&~$?(

Try it in my interpreter
Found a fun bug in my interpreter testing this
Expects input pre-pushed to the stack
Explanation

Top row: Setup. Push 0, the current maximum, l-1, the total number of iterations of the outer loop, and l-2, the total number of iterations in the inner loop. Initially both are the same.
Second row left: Shift left 1, then move the top item to the register. This is the candidate largest member.
Third row: Compare the register for each digit. If none match go down. Otherwise go up to second row right.
Second row right: Reset, then try another digit. This happens if the digit is equal to another.
Last row: We have found a unique digit! Now we check if it's better than the current max value, if so replace it. Then reset and jump to the start.

APL (Dyalog), 14 bytes
-2 thanks toTwiNight.
⌈/⊢×1=(+/∘.=⍨)

⌈/ the largest of
⊢ the arguments
× multiplied by
1=(…) the Boolean for each where one equals
 +/ the row sums of
 ∘.=⍨ their equality table
Try it online!

Since 0 is never the highest unique digit except for 0 itself, you can save 1 byte using × instead of /⍨, then save another byte converting that into a train

@TwiNight Nice! Thank you.

Japt -h, 12 11 10 4 bytes
I/O as a digit array.
ü l1

Try it

JavaScript (ES6), 52 50 bytes
Takes input as a list of digits. Returns 0 if there are no unique digits.
s=>s.reduce((m,c)=>m>c|s.filter(x=>x==c)[1]?m:c,0)

Test cases

let f =

s=>s.reduce((m,c)=>m>c|s.filter(x=>x==c)[1]?m:c,0)

console.log(f([1,2]))                 // 2
console.log(f([0]))                   // 0
console.log(f([4,8,5,9,0,2]))         // 9
console.log(f([4,9,5,9,0,2]))         // 5
console.log(f([9,9,9,9,9,9]))         // (0)
console.log(f([9,9,9,0,9,9]))         // 0
console.log(f([1,9,4,8,7,1,0,4,9,8])) // 7

PHP, 40 bytes
<?=array_flip(count_chars($argn))[1]-48;

Try it online!
PHP, 42 bytes
<?=chr(array_flip(count_chars($argn))[1]);

Try it online!

-2 bytes: <?=array_flip(count_chars($argn))[1]-48;

Java (OpenJDK 8), 89 85 79 bytes
a->{int i=10,x[]=new int[i];for(int d:a)x[d]++;for(;i-->0&&x[i]!=1;);return i;}

Try it online!
-6 bytes thanks to @KevinCruijssen's insight!

You can replace return i>0?i:0; with return i;. The output will be -1 for test case [9,9,9,9,9,9], but that is fine with the challenge: "If there are no unique digits, your program can do anything (undefined behavior).".

Indeed, I can since the current revision. Before I couldn't because of the test case 0. It's something I oversaw in the previous golf! :)

Java (JDK), 67 bytes
s->{int i=9;for(s=" "+s+" ";s.split(i+"").length!=2;i--);return i;}

Try it online!

J, 16 12 bytes
0{\:~-.}./.~

Try it online!

This is really nice, and since "The input can be taken as a single integer, a string, or a list of digits." you can actually do 0{\:~-.}./.~ for 12: Try it online!

APL (NARS2000) 16 chars, 23(?) bytes
{↑⌽⍸1=+⌿⍵∘.=⍳10}

(input as array of digits, e.g. {↑⌽⍸1=+⌿⍵∘.=⍳10}4 9 5 9 0 2)

Desmos, 113 bytes
U(l)=max([min([\left\{l[n]=l[i]:-1,l[n]\right\} for i=join([0...n-1],[n+1...l.length+1])]) for n=[1...l.length]])

Takes input as a list.
Try it out and see versions that make slightly more sense

Acc!!, 134 bytes
N
Count i while _%81/48 {
_+(1-_/3^(_%81-44)%3/2)*3^(_%81-44)-_%81+N
}
Count i while _ {
Count j while _/3^(13-i)%2 {
Write 57-i
0
}
}

Outputs a non-digit character if there are no unique digits. Try it online!
Explanation
We partition the accumulator into two sections: _%81 stores the most recently read character, while _/81 stores ten counters, one for each digit, denoting whether that digit has occurred 0, 1, or 2+ times. For example, an accumulator value of 3214945 (20001100002001 in base 3) represents the following:
Occurrences of       Next character
9 8 7 6 5 4 3 2 1 0
-------------------  --------------
2 0 0 0 1 1 0 0 0 0  7 (character code 55, or 2001 in base 3)

When a digit occurs more than 2 times, we cap its occurrence count at 2. Then once all digits have been processed, the answer is the highest digit that has 1 as its occurrence count.
### Part 1: read the input and count digit occurrences ###

# Read the first character into the accumulator
N
# Loop while the most-recently-read character was a digit (>= charcode 48):
Count i while _%81/48 {
  # The digit we just read is _%81-48
  # Its occurrence count is _/81/3^(_%81-48)%3
  # We add 1 to the occurrence count; but if the occurrence count was already 2,
  # we also subtract 1, leaving it unchanged
  _+(1-_/81/3^(_%81-48)%3/2)*81*3^(_%81-48)
  # Clear out the previously read character and read a new character
  _-_%81+N
}

### Part 2: output the highest unique digit ###

# Count up i, starting at 0, until the accumulator gets zeroed out
Count i while _ {
  # 9-i is the current digit
  # Its occurrence count is _/81/3^(9-i)%3
  # Loop if the occurrence count is odd (i.e. 1, not 0 or 2):
  Count j while _/81/3^(9-i)%3%2 {
    # Output that digit
    Write (9-i)+48
    # Zero out the accumulator, causing both loops to end
    0
  }
}

If there are no unique digits, the i loop keeps going past 9; some part of the newline character stored in _%81 triggers the j loop, and a non-digit character is written out. (The golfed and ungolfed versions output different characters, and I haven't taken the trouble to analyze why, but it's guaranteed not to be a digit because 9-i is less than 0 at this point.)

F#, 88 bytes
let f i=Seq.countBy(fun a->a)i|>Seq.maxBy(fun a->if snd a>1 then 0 else int(fst a))|>fst

Try it online!
An improved approach from my first effort, results in less bytes.
Points of interest: fst and snd return the first and second elements of a tuple respectively.

Mathematica, 42 bytes
Max@Position[RotateRight@DigitCount@#,1]-1&

Jelly, 9 bytes
ṢŒrṪỊ$ÐfṀ

Try it online!

well

@LeakyNun outgolfed, son

@Mayube but the core algorithm is the same

@LeakyNun no, it's quite different.

@LeakyNun I decided to post separately...basically a big difference is that in my case I just decide which to keep, while steenbergh takes some heads or something...

Pyth, 8 7 6 bytes
1 byte thanks to isaacg.
1 byte thanks to FryAmTheEggman.
e{I#.g

Test suite.

Pyth, 6 bytes
eS.m/Q

Test suite
Explanation:
eS.m/Q
eS.m/QbQ    Implicit variable introduction
  .m   Q    Find all minimal elements of the input by the following function:
    /Qb     Number of appearances in the input
eS          Take the maximum element remaining.

Perl 5, 59 bytes
for$x(split//,<>){$d[$x]++};for($x=11;--$d[--$x];){}print$x

Try it online!

Shortened to 40 bytes: Try it online!. (you can look up -F flag on perlrun if needed)

Pyth -- 8 bytes
eSf!t/QT

And also
eS-Q.-Q{

Try it here and here
Explanation:
eSf!t/QT  # Takes string
  f       # Filter characters T of implicit input
     /QT  # by counting occurrences of that character in the input
   !t     # keeping only characters that occur once (i.e., !(# occurrences - 1)
 S        # Sort (puts them in ascending order
e         # Take the last (highest)

And
eS-Q.-Q{  # Takes list of digits
       {  # Deduplicate the list
    .-Q   # Take the original list, and remove each element in the deduplicated list once (so only duplicated digits are left)
  -Q      # Remove these duplicated digits from the list
eS        # As before, sort and take the last element

Perl 6,  33  25 bytes
{max keys .comb∖.comb.repeated}

input is either an Int or Str.
Test it
{max keys $_∖.repeated}

input is a list of digits.
Test it
Expanded:
{ # bare block lambda with implicit parameter ｢$_｣

  max                  # the maximum from
    keys               # the keys (digits) out of the following Set

        $_             # the Input list of digits
      ∖                # Set minus (not ｢\｣)
        .repeated      # the repeated digits (implicit method call on ｢$_｣)
}

APL (Dyalog), 11 bytes
⌈/{⍺×2-≢⍵}⌸

Try it online!

⌈/×∘(2-≢)⌸

Java, 70 bytes
Input is taken as a List of digits, as per question parameters.
n->n.stream().reduce(0,(p,q)->p>q||n.indexOf(q)!=n.lastIndexOf(q)?p:q)

The power of Streams in the palm of your hand!
The secret here is the beautiful reduce function. Starting with a 0, we walk through our list of digits and perform a retaining algorithm, keeping the one we've got (?p:q) if it's bigger than the new one (p>q) or if the new one isn't unique (n.indexOf(q)!=n.lastIndexOf(q))

You can save a byte by changing || to |.

Octave, 31 bytes
@(a)find(hist(a,0:9)==1)(end)-1

Try it online!

Python 2, 41 bytes
lambda s:max(s,key=lambda o:s.count(o)<2)

Accepts input as string or list of strings

Ruby, 42 bytes
Same byte count as the other Ruby answer, but different approach.
->x{x.chars.group_by{|d|x.count d}[1].max}

Crashes when there are no unique digits.
Try it online!

x86 Machine Code (32-bit, requires built-in FPU), 35 bytes
C8 10 00 00 D9 EE DD 14 24 DD 5C 24 08 8B 01 FE 04 04
83 C1 04 4A 75 F5 6A 0A 58 48 FE 0C 04 75 FA C9 C3

The above code defines a function in 32-bit x86 machine code that finds the unique highest digit in an array of integer values. (The question says we can take the input as a "list of digits", and assembly language is like C in that it lacks a built-in list/array type, but rather represents them as a pointer and length. Therefore, the function is prototyped in C as follows:
int __fastcall FindHighestUniqueDigit(const int * ptrDigits, int count);

As the prototype suggests, this function is written to follow Microsoft's fastcall calling convention, which passes arguments in registers. The first parameter (ptrDigits) is passed in ECX, and the second argument (count) is passed in EDX. The return value is, as always, returned in EAX.
Try it online!
Here are the ungolfed assembly mnemonics:
FindHighestUniqueDigit:
   enter  16, 0                  ; reserve 16 bytes of space on the stack for a table
   fldz                          ; load 0 in the first x87 FPU register (st0)
   fst    QWORD PTR [esp+0]      ; store value in st0 on the stack, at offset 0
   fstp   QWORD PTR [esp+8]      ; store value in st0 on the stack, at offset 0, and pop st0
ReadDigits:
   mov    eax, DWORD PTR [ecx]   ; read next digit from array
   add    ecx, 4                 ; increment pointer to reference next digit (ints are 4-bytes long)
   inc    BYTE PTR [esp + eax]   ; increment value in table for this digit
   dec    edx                    ; decrement counter (length of array)
   jne    ReadDigits             ; keep looping as long as there are more digits to read
   push   10
   pop    eax                    ; space-efficient way to put '10' in EAX so we start with highest digit
ProcessDigits:
   dec    eax                    ; decrement EAX
   dec    BYTE PTR [esp + eax]   ; decrement value in table for this digit
   jnz    ProcessDigits          ; if that table value is now zero, we found a match; otherwise, keep looping
   leave                         ; undo effects of ENTER instruction
   ret                           ; return, with result in EAX (digit ID)

I suppose the code requires a bit of explanation…
Basically, what we do is create a table in memory, with enough space to store a unique count for each of the 9 possible digits (0 through 9).
| esp+0 | esp+1 | esp+2 | esp+3 | esp+4 | esp+5 | esp+6 | esp+7 | esp+8 | esp+9 | unused |
|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|--------|
|   9   |   8   |   7   |   6   |   5   |   4   |   3   |   2   |   1   |   0   |   ??   |

The first ENTER instruction is responsible for allocating this space on the stack. We allocate 16 bytes because it's good practice to keep this a power of 2 for alignment purposes, 8 bytes isn't enough, and it doesn't hurt or cost anymore code bytes to allocate more than we need. The next three instructions are responsible for zeroing out all entries in this array. It's basically equivalent to memset in C, but shorter. We could have used the x86's REP STOSD string instruction, but that requires some setup (getting the right values in the right registers), and it turned out that (ab)using the x87 FPU to do 8-byte stores was shorter (required fewer bytes of code). All we do is load 0 at the top of the x87 FPU stack, which is pseudo-register st0. Then, we store that value across the first 8 bytes of the table, and finally, we store that value across the next 8 bytes of the table, popping it off of the x87 FPU stack at the same time.
The next section of the code is ReadDigits. This just iterates through the array of digits that we were passed, one digit at a time, and increments the unique counter for that digit in our table. You should be able to figure out exactly how this works by reading the comments for each of the instructions.
Finally, we come to the final phase, ProcessDigits. Actually, the PUSH+POP instructions right above this label are conceptually part of this phase, too. They just initialize EAX with 10 so that we'll start processing the table values for the highest possible digit, 9. Inside of the ProcessDigits loop, we eagerly decrement EAX, and then decrement the value in the table for the corresponding digit. If the resulting table value/counter is 0, then we've found our match, and we exit the loop. Otherwise, we keep looping and trying smaller digits. (Note that we could have done a comparison here—e.g., cmp  BYTE PTR [esp + eax], 1+jnz—and in fact that's what we're logically doing, but the decrement was shorter to encode.)
Notice that if we never find a matching digit, then the code exhibits undefined behavior: it just keeps looping through memory, decrementing values and waiting until one is non-zero. When it finally finds one, it will terminate and return the ID, but the ID will be meaningless, and more importantly, it will have stomped all over the stack, decrementing values aimlessly!
The very last two instructions just clean up the stack (LEAVE) and return, with the return value (the highest unique digit that we found) left in EAX.

CJam, 13 bytes
9A,sqfe=W%X#-

Try it Online
9    e# Push 9 to stack
A,s  e# Push "0123456789" to stack
q    e# Push input to stack ("1948710498")
fe=  e# Create array containing number of occurences of each digit in input number ([1,2,0,0,2,0,0,1,2,2])
W%   e# Reverse that array ([2,2,1,0,0,2,0,0,2,1])
X#   e# Get index of 1 in that array (2)
-    e# Calculate 9 - returned index to get highest unique digit (7)

C# (Visual C# Interactive Compiler), 38 bytes
n=>n.Max(x=>n.Count(a=>a==x)<2?x:0)-48

Try it online!

Julia 1.0, 41 bytes
s->maximum(c for c=s if count(==(c),s)<2)

Try it online!

Wolfram Language (Mathematica), 43 29 28 bytes
Max@*Cases[{d_,1}->d]@*Tally

Try it online!

Removed 14 bytes by skipping IntegerDigits: according to the comments, the function can be given a list of digits instead of a number.

Removed 1 byte by using the operator form of Cases.

Husk, 8 bytes
▲fȯ¬←LgO

Try it online!
Input is list of digits.
▲fȯ¬←LgO
▲           # maximum value of
 f          # filter list to retain only truthy values
      gO    # list: sort digits and group equal values together
  ȯ         # filter condition: combine functions:
    ←L      # length minus one (so length 1 = FALSE)
   ¬        # NOT (so length 1 = TRUE)

5 bytes I think

3 bytes but this one's very different

@Razetime - That's much, much, better than mine!  Post it!

Vyxal, 7 bytes
'?$Oṅ;G

Try it Online!
Takes input as a string.
Explained
'?$Oṅ;G
'    ;  # From the input, keep only items where:
 ?$O    #   The count of the item in the input
    ṅ   #   Is <= 1
      G # Get the biggest digit from the remaining items.

Try it Online! for 8

@lyxal Erm if you want, you're welcome to edit my answer to add in the explanation! :3

Try it Online! for 7

Try it Online for 6 using G flag.

@Pacmanboss256 That can be 5 removing the trailing ;.

@Steffan good catch

Arturo, 43 32 bytes
$=>[tally&|select[k,v]->v=1|max]

Try it

Scala, 100 bytes
Golfed version. Try it online!
def f(x:String)=Try(x.groupBy(identity).filter(_._2.length<2).keys.map(_.asDigit).max).getOrElse(-1)

Ungolfed vesrion. Try it online!
import scala.util.Try

object UniqueDigitMax {
  def main(args: Array[String]): Unit = {
    val f = (input: String) => Try(input.groupBy(identity).filter(_._2.length < 2).keys.map(_.asDigit).max).getOrElse(-1)

    println(f("1948710498"))  // 7
    println(f("485902"))      // 9
    println(f("0"))           // 0
    println(f("999099"))      // 0
    println(f("999999"))      // -1 (Undefined)
  }
}

Kotlin, 106 74 bytes
{n:List<Int>->n.groupingBy{it}.eachCount().filterValues{it==1}.keys.max()}

It's a real shame we never got toDigits().
Takes input as a list of digits, errors if there are no unique digits.
Try it Online!

Scala, 49 bytes

The input can be taken as a single integer, a string, or a list of digits.

Golfed version. Try it online!
def f(s:String)=s.filter(c=>s.count(_==c)==1).max

Ungolfed version.
object Main {
  def f(s: String): Char = {
    s.filter(c => s.count(_ == c) == 1).max
  }

  def main(args: Array[String]): Unit = {
    println(f("1948710498"))
  }
}

Java (JDK), 77 bytes
Not the best Java Stream solution, but a different approach.
Input is a String (because for obscure reasons sum and max aren't easily accessible in Streams of Lists of non-primitive integers).
Outputs -48 in the "Anything" case.
s->s.chars().filter(e->s.chars().filter(c->c==e).sum()==e).max().orElse(0)-48

Try it online!

Thunno 2 G, 3 bytes
cḅị

Input as a digit list.
Explanation
cḅị  # Implicit input
c    # Counts of each
 ḅ   # Equals one?
  ị  # Filter input by above
     # G flag gets maximum
     # Implicit output

Screenshot

Clojure, 52 bytes
#(last(sort(for[c % :when(=((frequencies %)c)1)]c)))

Input is a string, returns a char, or nil if all digits are repeated.

Actually, 16 bytes
╗1╤rR⌠$╜c⌡M1@í9-

Try it online!

C (gcc), 76 bytes
d[58],i;main(){while((i=getchar())>0)++d[i];for(i=58;--d[--i];);putchar(i);}

Try it online!

Suggest d[58];main(i) instead of d[58],i;main(). Also, suggest (~(i=getchar())) instead of ((i=getchar())>0)

Pip, 11 bytes
MX:_Na=1FIa

Takes input as a command-line argument. Try it online!
Explanation
          a  1st cmdline arg
        FI   Filter digits by this function:
   _Na        Count of digit in a
      =1      equals 1 (i.e. keep only digits that appear exactly once)
MX:          Max of resulting list (using : to lower precedence)
             Autoprint (implicit)

CJam, 15 bytes
r$e`{0=1=},e~W=

Explanation:
r    e# Read token:          "2515"
$    e# Sort:                "1255"
e`   e# Run-length encode:   [[1 '1] [1 '2] [2 '5]]
{    e# Filter where:
 0=  e#   The first element: [1 1 2]
 1=  e#   Equals one:        [1 1 0]
},   e# End filter:          [[1 '1] [1 '2]]
e~   e# Run-length decode:   "12"
W=   e# Last digit:          '2

PHP, 79 76 bytes
Try it online!
foreach(array_count_values(str_split($n))as$x=>$i){$h=($i==1&&$x>$h)?$x:$h;}

Use with $n = 495902; it will print 5.

PowerShell v3+, 58 bytes
$i|% ToCharA*|sort|group|? count -lt 2|select -l 1 -exp N*

Assumes $i contains a string of digits.
Try it online!

F#, 67 bytes
let f s=Seq.countBy id s|>Seq.filter(snd>>(=)1)|>Seq.maxBy fst|>fst

Try it online!

Kotlin, 53 Bytes
val f={a:String->a.filter{a.count{b->it==b}<2}.max()}

Declares a lambda, f that takes in a string; same algorithm as the python answer. it is the implicit iterator of a.filter, and b is the explicitly declared iterator of a.count (because we can't use it again).
Try it online!

Braingolf, 15 bytes
k&gG{!L1-?$_|}X

Try it online!
Takes input as a list of digits
Explanation
k&gG{!L1-?$_|}X  Implicit input from commandline args
k                Sort in descending numerical order
 &g              Combine into single integer
   G             Split into digit runs
    {........}   Foreach loop..
     !L1-?       ..If length of item is greater than 1..
          $_     ....Remove item
            |    ..Endif
              X  Select highest value
                 Implicit output

To help visualize it a little, here's a run through showing the stack with input 122355567679
k&gG{!L1-?$_|}X  [1,2,2,3,5,5,5,6,7,6,7,9]
k                [9,7,7,6,6,5,5,5,3,2,2,1]
 &g              [977665553221]
   G             [9,77,66,555,3,22,1]
    {!L1-?$_|}   [9,3,1]
              X  [9]

k, 12 bytes
|/@&:1=#:'=:

Example:
k)F:|/@&:1=#:'=:
k)F "1948710498"
"7"
k)F "99999"
"\000"
k)F "mississippi"
"m"

The translation to q as explanation
max where 1 = count each group@

Interpreter available here

I think this can be simplified to |/&1=#:'=: (provided it's run as k4 code).

jq, 47 characters
(43 characters code + 4 characters command line options)
[./""|group_by(.)[]|select(length<2)[]]|max

Sample run:
bash-4.4$ jq -Rr '[./""|group_by(.)[]|select(length<2)[]]|max' <<< 1948710498
7

bash-4.4$ jq -Rr '[./""|group_by(.)[]|select(length<2)[]]|max' <<< 999999
null

Try on jq‣play

J, 19 bytes
[:>./(".*1=#)/.~@":

Explanation:
": converts the number to a string
@ propagates the result (composes the verbs)
/.~ classifies items of the list into partitions of identical items
". * 1=# - fork of 3 verbs:
   1=# - 1 if the key is unique, else 0
   ". converts the keys back to digits
   * multiplies the key values by 1 or 0 depending on their uniqueness
>./ finds the max digit
[: caps the fork, since we have 2 verbs only for the key operator

Example:
   a=.123456547
     ": a
    123456547  NB. string
    </.~@": a
    ┌─┬─┬─┬──┬──┬─┬─┐
    │1│2│3│44│55│6│7│          NB. keys
    └─┴─┴─┴──┴──┴─┴─┘

(1=#)/.~@": a
1 1 1 0 0 1 1                  NB. mask for unique digits

(".*1=#)/.~@": a
1 2 3 0 0 6 7                  NB. digits multiplied by the mask, rendering
                                   duplicates to 0

([:>./(".*1=#)/.~@":) a        NB. finds the max nonrepeating digit
7

Try it online!

Perl 5 -pF, 36 bytes
$_=join'',sort@F;s/(.)\1+//g;$_=chop

Try it online!

Stax, 4 bytes
|!|M

Run and debug it
This program takes an array of digits.  It works by calculating the maximum "anti-mode".  I'm not sure if anti-mode is a real thing outside of stax, but it refers to the rarest element in list.

dc, 48 bytes
[1+dd;a+r:az0<A]dsAx[d]sD1[dd;a=D1+dB>M]dsMxr1-p

Try it online!
Takes input as digits entered on the stack. I'm assuming this is a fair stand-in for a list of digits, since the justification for that was that folks were just dropping in boilerplate to turn an integer into a list of digits, and the task would be the same here (just... as a stack). Returns '10' on an entry w/ no unique digits.
Macro A builds an array a by taking each digit, loading the value in its own index (this will be 0 if thus far unassigned), adding itself, and storing this new value. We actually have to add 1 to every digit first so that 0 works correctly, and at the end we subtract 1 from our final result to make up for this. Ignoring that briefly for illustrative purposes, any index (let's call it n) of a will now have one of the following: 0 if n didn't exist as a digit in our original number, n itself if n was unique, or some multiple of n other than n*1 if n existed and was not unique.
In dc, conditionals either run a macro if true or do nothing if false, so you need a macro for them to do anything. [d]sD is a macro, D that simply duplicates a value.
A 1 goes on the stack to act as a counter of sorts, and then macro M runs. M really just takes this counter number, loads that index from a, compares it to itself, and duplicates itself (by running D) if they're equal. So, if they aren't equal (digit didn't exist or wasn't unique), we're simply left with one instance of our counter; if they are equal, we now have another copy of it on the stack. Increment, and keep on going 1-10. When this is done, we swap the top two values on the stack and subtract one as mentioned earlier.

Jelly, 7 bytes
ċⱮ`Ị×⁸Ṁ

Try it online!
Input as a list of digits
How it works
ċⱮ`Ị×⁸Ṁ - Main link. Takes the list D on the left
  `     - Using D as both arguments:
 Ɱ      -   For each element in D:
ċ       -     Count occurrences in D
   Ị    - Insignificant; Yield 1 if abs(x) ≤ 1 else 0
     ⁸  - Yield D
    ×   - Multiply the digits by the insignificants
      Ṁ - Take the maximum

Factor, 27 bytes
[ non-repeating ?supremum ]

Attempt This Online!

non-repeating Get only the elements of a sequence that don't repeat.
?supremum Get the largest element of a sequence, or return f for the empty sequence.

source: Stack Exchange

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/3.0/

sort: votes


text:
How many times should I press this?
We are all used to the old-school telephone keypad, right? For reference, here is what it looks like:

Given a String consisting only of lowercase ASCII letters and single spaces, your task is to return the number of taps one should make in order to type down the full String with a telephone keypad as the one above.
For those who are unfamiliar with this, here's how it works:

The key with the digit 2, for example, also has the string abc written down on it. In order to type a, you must press this key once, for b you must press twice and for c you must press thrice.
For consecutive letters that are on the same key, you must wait 1 second before pressing again. So, if you want to type cb, you must press 3 times for c, wait a second and then press twice for b, so still 5 taps.
The same applies for all other keys, except for a single space, which only requires 1 press. Also note that the keys 7 and 9 have four letters on them. The same algorithm is applied, the only difference being the number of letters. The strings corresponding to each key can be found in the image above (but lowercase), or in the following list, that contains all the characters you might receive:
"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", " "

Test Cases

Input  ->  Output  (Explanation)

"" -> 0                   (nothing should be tapped)
"water" -> 8              ("w,a,t" each require 1 tap (on keys 9, 2 and 8), "e" requires 2 taps (on key 3), "r" requires 3 taps (on key 7), 1+1+1+2+3 = 8)
"soap"  -> 9              (4+3+1+1)
"candela" -> 13           (3+1+2+1+2+3+1)
"code golf" -> 20         (3+3+1+2+1(for the space)+1+3+3+3)
"king of the hill" -> 33  (2+3+2+1+1+3+3+1+1+2+2+1+2+3+3+3)

Specs

Standard I/O rules and Default Loopholes apply.
You may only take input in your language's native String type. Output can either be an integer or a string representation of that integer.
This is code-golf, the shortest answer in every language wins.

Related, Inspired by this

Related. Related.

I think this would be a more interesting question if you did 1 tap per second, and had to wait 1 second, and counted seconds instead of taps.

@Yakk That would be far too complicated

@Mr.Xcoder Are you sure though? I have seen the code-wizards here do impossible stuff in less space than a tweet.

@J_F_B_M That wouldn't be very hard, but it is too complicated for the purpose of this challenge, at least.

Thrice, word is thrice. Once, twice, thrice. As a non-English speaker I saw our common struggle there :)

@EgeBayrak Happy now?

Can we take all uppercase if that makes the code shorter?

@Adám No, sorry. I don't want to create confusion with old answers that might become shorter too.

JavaScript (ES6) 77 66 64 60 bytes
(Saved some bytes thanks to @Johan Karlsson and @Arnauld).
s=>[...s].map(l=>s=~~s+2+'behknquxcfilorvysz'.search(l)/8)|s

f=

s=>[...s].map(l=>s=~~s+2+'behknquxcfilorvysz'.search(l)/8)|s

console.log(f(''));                 //0
console.log(f('water'));            //8
console.log(f('soap'));             //9
console.log(f('candela'));          //13
console.log(f('code golf'));        //20
console.log(f('king of the hill')); //33

(s,t=0)=>[...s].map(l=>t+=(1+'behknquxcfilorvysz'.indexOf(l)/8|0)+1)&&t for 71 bytes

Thanks, @JohanKarlsson, I figured the same thing while in the shower!  Found another optimization to shave off 5 more bytes.

I found a purely arithmetical solution for 71 bytes: f=s=>[...s].map(c=>t+=((c=parseInt(0+c,36))>23?c+3:c&&~-c%3)%7%4+1,t=0)|t.

@Neil, while that may not be shorter, it is certainly cleverer.

@Neil You should post it.

s=>[...s].map(l=>s=~~s+2+'behknquxcfilorvysz'.search(l)/8)|s should save 4 bytes, decimal parts being truncated by ~~s on each iteration and ultimately by |s.

Sweet, I'll study that and update later, thanks!

05AB1E, 29 26 25 bytes
ð¢svA•22ā₂•S£ð«øðδKy.åƶOO

Try it online!
Explanation
ð¢                         # count spaces in input
  sv                       # for each char y in input
    A                      # push the lowercase alphabet
     •22ā₂•S               # push the base-10 digit list [3,3,3,3,3,4,3,4]
            £              # split the alphabet into pieces of these sizes
             ð«            # append a space to each
               ø           # transpose
                ðδK        # deep remove spaces
                   y.å     # check y for membership of each
                      ƶ    # lift each by their index in the list
                       O   # sum the list
                        O  # sum the stack

Sorry, but for an empty input this gives 10. It is fine elsewhere

@Mr.Xcoder: The empty string gives no output, but it's still wrong. Thanks for notifying, I'll fix it.

It gives 10 on TIO.

@Mr.Xcoder: Yeah you have to explicitly give the empty string. No input is not the same as an empty string. It is a bit confusing I know. Fixed now though :)

@Mr.Xcoder: Empty string input is given like this

@Emigna Won't just \n suffice?

@EriktheOutgolfer: A literal line break would work as well yes :)

Ooh, didn't know £ would behave like that...it's supposed to do a[:b] right?

@EriktheOutgolfer: Yep. But if b is a list instead of an int it repeatedly does a[:b] on the remainder of a for each element in b basically.

ƵÜbS3+ pushes [3,3,3,3,3,4,3,4] but reversed.

@MagicOctopusUrn: I also had ƵxbRS3+ as well as •M†–•5в, but unfortunately they are all the same size :(

•Cç•3вÌ also works.

-1 by using ðм instead of ðδK. Probably wasn't available yet at time of posting (and currently it also doesn't work in the TIO since the items are wrapped in inner lists, so a third O would be necessary..)

Python 2, 56 bytes
Uses the same algorithm as @RickHitchcock's Javascript solution
lambda x:sum('behknquxcfilorvysz'.find(c)/8+2for c in x)

Try it online!

Interesting solution. How does it work for spaces, I don't get it >.<?

@Mr.Xcoder for anything not in the string '...'.find(c) returns -1. By adding 2 we get one keypress.

I knew it returned -1, but didn't realize you have a +2 after the boilerplate... Anyway, the shortest Python solution by far.

Oml, I happened to make the exact same solution after slowly golfing my program down, until I realized you posted it :( Nice job for finding this solution as well :)

Dyalog APL, 37 bytes
+/⌈9÷⍨'adgjmptw behknqux~cfilorvy~'⍳⍞

Try it online!
How?
Get the ⍳ndex of every char of the input in the string 'adgjmptw behknqux~cfilorvy~' (s and z will default to 28), divide by 9, round up and sum.

You can use 'adgjmptw ' 'behknqux' 'cfilorvy' 'sz' to save some bytes

49 bytes

@LeakyNun onice

You can drop the space in the string

@Uriel wait, you don't need to count f← so it is 47 bytes

@LeakyNun yup. and 46

Save a byte by converting to tradfn.

@Adám but trains looks so much better! ;)

Python 3, 69 67 65 64 bytes
1 byte thanks to Mr. Xcoder.
1 byte thanks to Felipe Nardi Batista.
lambda x:sum((ord(i)+~(i>"s"))%3+3*(i in"sz")+(i>" ")for i in x)

Try it online!

Save one byte by replacing i==" " with i<"a", because you only receive letters and spaces

61 minutes...too late!

JavaScript (ES6), 71 bytes

f=
s=>[...s].map(c=>t+=((c=parseInt(0+c,36))>23?c+3:c&&~-c%3)%7%4+1,t=0)|t
<input oninput=o.textContent=f(this.value)><pre id=o>

Look no letter tables! I didn't quite understand @LeakyNun's formula so I came up with my own.

Pure arithmetic :)

What does s=>[...s] do why not just s=>s.map()...

@EvanCarroll s is a string, so you can't map it directly. ...s iterates over s, while [...s] converts the iteration into an array, effectively splitting s into an array of characters.

C, 211 196 bytes
First submission here...looks quite lengthy and I see that this is not an efficient approach, but at least it works :)
f(char*n){char*k=" abcdefghijklmnopqrstuvwxyz";int t[]={0,3,3,3,3,3,4,3,4};int l=0,s,j,i;while(*n){i=0;while(k[i]){if(k[i]==*n){s=0;for(j=0;s<i-t[j];s+=t[j++]);*n++;l+=(!i?1:i-s);}i++;}}return l;}

Ungolfed version:
int f(char *n){
  char *k=" abcdefghijklmnopqrstuvwxyz";
  int t[]={0,3,3,3,3,3,4,3,4};
  int l=0,s,j,i;
  while(*n){                          // loop through input characters
    i=0;
    while(k[i]){
      if(k[i]==*n){                   // find matching char in k
        s=0;
        for(j=0;s<i-t[j];s+=t[j++]);  // sum up the "key sizes" up to the key found
        *n++;
        l+=(!i?1:i-s);                // key presses are i-s except for space (1)
      }
      i++;
    }
  }
  return l;
}

*(k+i) can be k[i].

You can cut the space after an * (e.g. char*n), and add your declarations to your empty for statement (instead of int s=0,j=0;(for(; you'd have for(int s=0,k=0;) and instead of i==0 use !i

Thank you for those hints. I couldn't put s into the for loop because I use it later on, but I put the int declarations together and used assignemts where I needed them.

Yay a fellow C golfer! Anyways, some pointers: for loops are strictly better than while loops in almost all situation - take advantage of the free semicolons, particularly in the iteration expression. Use commas instead of semicolons in most places, this allows you to get away with not having curly braces in most places. There are other optimizations but they are more reliant on which version of C you compile to.

Haskell - 74 71 62 bytes
Edit: removed 3 bytes by using a list comprehension instead of filter
Edit: Save 9 bytes thanks to Siracusa, Laikoni and Zgarb!
f=sum.(>>= \x->1:[1|y<-"bcceffhiikllnooqrrsssuvvxyyzzz",y==x])

Usage
λ> f "candela"
13
λ>

Try it online!

What is the purpose of the duplicate letters?

@Mr.Xcoder It's used to count the taps, I will add an explanation.

You can save one byte by rewriting f to f=length.(=<<)(\x->x:[y|y<-l,y==x]), where (=<<) is concatMap here.

And another one with going back to filter: f=length.(=<<)(\x->x:filter(==x)l)

As you use l only once, it can be inlined.

Use a single sum instead of sum of lengths: sum.(>>= \x->1:[1|y<-l,y==x]). Also, as Laikoni said, inline l.

Haven't gotten to the chapters on Monoids/Functors/Applicatives, so I am afraid I have never used =<<. I tried to do as you suggested, but was unable to get it working. @siracusa

Oh, what was the problem?

@siracusa Oh! I thought you were giving me partial code, so I tried to add stuff to what I thought would work. Now that I tried what you posted, I was able to get it to work fine. Thanks!

Jelly, 25 bytes
Øaḟ⁾sz©;⁶s3Z®ṭċ@€€⁸×"J$ẎS

Try it online!

Clojure, 82 76 bytes
#(apply +(for[c %](+(count(filter #{c}"bcceffhiikllnooqrrsssuvvxyyzzz"))1)))

Oh it is simpler to just filter and count than use frequencies. Original:
#(apply +(count %)(for[c %](get(frequencies"bcceffhiikllnooqrrsssuvvxyyzzz")c 0)))

The string encodes how many times more than just once you need to press the key for a given character :)

Python 3, 91 bytes
lambda x:sum(j.find(i)+1for j in' !abc!def!ghi!jkl!mno!pqrs!tuv!wxyz'.split('!')for i in x)

Try it online!

Python 3, 60 bytes
Probably sub-optimal, as this is my first golf ever in Python.
lambda x:sum((ord(i)-8)%3.15//1+3*(i>'y')+(i>' ')for i in x)

Try it online!

Retina, 46 36 bytes
Thanks to CalculatorFeline for saving 6 bytes.
s|z
,c
[cfilorvy]
,b
[behknqux]
,,
.

Try it online!

Java, 95 73 bytes
a->a.chars().map(b->1+(b<64?0:b+(Math.abs(b-115)<4?4:5))%(3+b/112)).sum()

Thanks to Kevin Cruijssen for making the function a lambda expression (where a is of type String). 95 bytes became 73 bytes!
A lambda expression sums up the press count of each character using map(). map() converts each character (ASCII in lower case range is 97-122) in the stream to the appropriate value (looks like simple saw wave, but taking into account both 4 cycles is annoying) using this math: 1+(b<64?0:b+(Math.abs(b-115)<4?4:5))%(3+b/112). Here's a desmos graph of that model.

The loophole list says not to post code snippets even though it looks like everyone so far has done that. Either way, my full program is 130 bytes. Here it is: interface Z{static void main(String a){System.out.print(a.chars().map(b->1+(b<64?0:b+(Math.abs(b-115)<4?4:5))%(3+b/112)).sum());}}

Welcome to PPCG! You're indeed right that snippets aren't allowed, but the default is program or function. And with Java 8 you can use lambdas. So in this case a->{return a.chars().map(b->1+(b<64?0:b+(Math.abs(b-115)<4?4:5))%(3+b/112)).sum();} is allowed. And since it's a single return statement, a->a.chars().map(b->1+(b<64?0:b+(Math.abs(b-115)<4?4:5))%(3+b/112)).sum() (73 bytes) would be your answer. Also, here is a TryItOnline-link of your answer you might want to add to your answer. Again: welcome, and nice answer. +1 from me.

Some things to note about lambdas. You don't have to count f= nor the leading semi-colon ;. And you also don't have to add the type of the parameter as long as you mention what the type is (so instead of (String a)-> you can use a-> and mention that input a is a String in your answer). Oh, and Tips for golfing in Java and Tips for golfing in  might be interesting to read, in case you haven't yet.

Pip, 100 90 bytes
a:qb:["abc""def""ghi""jkl""mno""pqrs""tuv""wxyz"s]Fc,#a{Fd,#b{e:((bd)@?(ac))e<4?i+:e+1x}}i

Check each character of the input for a match in each element of b. The index of that match plus 1 gets added to the total.
Try it online!

C, 92 88 bytes
c,n;f(char*s){n=0;while(c=*s++)n+=(c=='s')+3*(c>'y')+1+(c+1+(c<'s'))%3-(c<33);return n;}

you may use s=n to replace return n, and combine s++; with c=*s. It could be 9 bytes shorter.

@KeyuGan s=n wouldn't work, since s is a local.  And *s=n wouldn't work since there are only CHAR_BIT bits in *s, which wouldn't be enough for some messages.  But you're right about the s++.  Thanks.

C (gcc), 75 77 bytes
n,b;f(char*a){for(n=0;b=*a++;)n+=b<'s'?--b%3+(b>31):""[b-'s'];a=n;}

The unprintable string "" is a table of 04 01 02 03 01 02 03 04.
Try it online!

Mathematica, 83 bytes
c=Characters;Tr[Tr@Mod[c@"bc1def1ghi1jkl1mno1pqrstuv1wxyz "~Position~#,4]+1&/@c@#]&

It seems to be generally accepted that Mathematica answers are allowed to use lists of characters for string variables, such as the input to this function. (Also is there an a missing at the start of "bc1..."?)

this is code golf.this gives the right result without a."Tr " does the job

QBIC, 94 bytes
[_l;||_SA,a,1|p=p-(instr(@sz`,B)>0)-(instr(@cfilorvy`+C,B)>0)-(instr(@behknqux`+C+D,B)>0)+1}?p

Explanation
[    |      FOR a = 1 TO
 _l |         the length of
   ;            the input string (A$)
_SA,a,1|    Take the a'th char of A$ and assign it to B$
p=p         p is our tap-counter, and in each iteration it gets increased by the code below
            which consist of this pattern:
                instr(@xyz`,B)>0    where
                - instr tests if arg 2 is in arg 1 (it either returns 0 or X where X is the index of a2 in a1)
                - @...` defines the letters we want to test as arg1
                - B is the current letter to count the taps for
            Each of these blocks adds 1 tap to the counter, and each block has the letters of its level
            (4-taps, 3-taps or 2-taps) and the level 'above' it.
    -(instr(@sz`,B)>0)              <-- letters that require 4 taps
    -(instr(@cfilorvy`+C,B)>0)      <-- 3 or 4 taps
    -(instr(@behknqux`+C+D,B)>0)    <-- 2, 3,or 4 taps
    +1                              <-- and always a 1-tap
}           NEXT
?p          PRINT the number of taps

Bash, 69  68 bytes
bc<<<`fold -1|tr "\n "adgjmptwbehknquxcfilorvysz +[1*9][2*8][3*8]44`

Try it online!
Folds one char per line, transliterates each newline with +, each space with     1 and each letter with the corresponding number of pushes. bc does the sum.

on your machine you may need bc <(fold -1|tr "\n "adgjmptwbehknquxcfilorvysz +[1*9][2*8][3*8]44;echo 0)

APL (Dyalog), 36 bytes
{+/(3×⍵∊'sz'),1+3|¯1+⍵⍳⍨819⌶⎕A~'SZ'}

Try it online!
Finds the mod-3 indices in the alphabet without S and Z. Since space, S, and Z are not found, they "have" index 25 (one more than the max index), which is good for space. Then we just need to add 3 for each S or Z.
{ anonymous function where the argument is represented by ⍵:
 ⎕A~'SZ' the uppercase Alphabet, except for S and Z
 819⌶ lowercase
 ⍵⍳⍨ the ɩndices of the argument in that
 ¯1+ add negative one
 3| mod-3
 1+ add one (this converts all 0-mods to 3)
 (…), prepend:
  ⍵∊'sz' Boolean where the argument is either s or z
  3× multiply by 3
 +/ sum

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/3.0/

all_licenses: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/


text:
Short Date into English Long Date
Convert short date format into English long date in as few bytes as possible.
Input
Input will be in the form of a string with format, yyyy-mm-dd, with zero padding optional for all values. You can assume that this is syntactically correct, but not necessarily a valid date. Negative year values do not need to be supported.
Output
You must convert the date into the English long date format (e.g. 14th February 2017). Zero padding here is not allowed.
If the date is invalid (e.g. 2011-02-29), then this must be recognised in some way. Throwing an exception is allowed.
More examples can be seen below.
Test Cases
"1980-05-12" -> 12th May 1980
"2005-12-3"  -> 3rd December 2005
"150-4-21"   -> 21st April 150
"2011-2-29"  -> (error/invalid)
"1999-10-35" -> (error/invalid)

Is zero-padding allowed? aka 03rd instead of 3rd

@ValueInk If you read my previous comment, ignore it; I misunderstood the question. Zero padding in the output is not allowed.

Should we consider a year on more than 4 characters (eg 10987-01-01)?

@mdahmoune You don't need to support this unless it's easier to do so.

What about 2016-2-29?

@OlivierGrégoire That date would be valid.

You say we don't have to support negative year values, does that imply we have to support starting at year 1? (There was no year 0). Or just back to year 150, per your earliest example? Any max year? Up to 9999 ok?

And we shouldn't 0-pad the year either? So output "21 April 3" for 0003-04-03?

@BradC I would say don't zero pad the year and have it support the range, 0 - 9999.

Actually there is no year zero, 31st Dec 1 BC is followed immediately by 1st Jan 1 AD. Also, there are several existing answers that pad the year already, so might be better to leave that unspecified.

@BradC TIL! In that case, it's optional. But I think it's better to enforce a lack of padding as I did include it in the examples and state it in the output description.

Does it have to work independently of the language setting of the OS? (My german Windows would use german names unless I force powershell to do otherwise)

@whatever No. As long as the code produces the desired result on English machines, you're fine.

PostgreSQL, 61 characters
prepare f(date)as select to_char($1,'fmDDth fmMonth fmYYYY');

Prepared statement, takes input as parameter.
Sample run:
Tuples only is on.
Output format is unaligned.
psql (9.6.3, server 9.4.8)
Type "help" for help.

psql=# prepare f(date)as select to_char($1,'fmDDth fmMonth fmYYYY');
PREPARE

psql=# execute f('1980-05-12');
12th May 1980

psql=# execute f('2005-12-3');
3rd December 2005

psql=# execute f('150-4-21');
21st April 150

psql=# execute f('2011-2-29');
ERROR:  date/time field value out of range: "2011-2-29"
LINE 1: execute f('2011-2-29');
                  ^
psql=# execute f('1999-10-35');
ERROR:  date/time field value out of range: "1999-10-35"
LINE 1: execute f('1999-10-35');
                  ^
HINT:  Perhaps you need a different "datestyle" setting.

Nice, wish MS-SQL recognized the "th" formatting style.

Considering you managed to complete the task properly with the fewest bytes, I suppose your solution is the winner!

Wow. Thanks. That's completely unexpected. Didn't noticed until now the absence of dedicated golfing languages. However accepting a solution after just one day is a bit soon.

@manatwork I did wonder if it might be a little early. But I can change it if need be anyway.

Python 3.6, 137 129 bytes
from datetime import*
def f(k):g=[*map(int,k.split('-'))];n=g[2];return f"{date(*g):%-d{'tsnrhtdd'[n%5*(n^15>4>n%10)::4]} %B %Y}"

Try it online!

%-d is the no-padding version of %d that you can use in your string formatting instead of {g[2]}. Also, 12 should become 12th, not 12nd (numbers from 10-19 don't follow the same rules as 1-9 and 20+)

+1. didn't know about the f strings

@ValueInk thanks! also, fixed the ordinals issue

Python 3.6, 154 bytes
from datetime import*
s=[*map(int,input().split('-'))]
b=s[2]
print(date(*s).strftime(f"%-d{'th'if(3<b<21)+(23<b<31)else('st','nd','rd')[b%10-1]} %B %Y"))

Try it online! (Set input stream and then run.)
Thanks to good suggestions from commenters below.

You can save a byte by removing the space between int(x) and for in your list comp.

@ChristianDean Thanks, done!

(('st','nd','rd')[b%10-1]if b<4 or 20<b<24 else'th') instead of your current conditional for -3 bytes.

@ValueInk Sadly, that will produce 31th. Another way I thought of breaking it down was 'th' if not 0<b%10<4 or 10<b<14 but it didn't save any bytes.

In that case, abuse type coersion. (3<b<21)+(23<b<31) for -1 byte. Try it online!

@ValueInk Thanks; 1 less byte. :) Also "upgraded" to Python 3.6 for the new string format.

140 Bytes

@officialaimm Thanks, and hence even a little less with f"". I consider that structure different enough from mine that you can feel free to make it an answer if you want.

JavaScript (ES6), 142 140 bytes
Outputs NaNth Invalid Date for invalid dates.
The code for ordinal numbers was adapted from this answer.
d=>`${s=(D=new Date(d)).getDate()+''}${[,'st','nd','rd'][s.match`1?.$`]||'th'} `+D.toLocaleDateString('en-GB',{month:'long',year:'numeric'})

f=
d=>`${s=(D=new Date(d)).getDate()+''}${[,'st','nd','rd'][s.match`1?.$`]||'th'} `+D.toLocaleDateString('en-GB',{month:'long',year:'numeric'})

console.log(
  f('2005-12-3'),
  f('1980-05-12'),
  f('2005-12-3'),
  f('150-4-21'),
  f('2011-2-29'),
  f('1999-10-35')
)

Gives "1st March 2011" for 2011-2-29 in Chrome.  That may be a tough fix.

Bash + coreutils, 115 78

2 bytes saved thanks to @manatwork.

d="date -d$1 +%-e"
t=`$d`
f=thstndrd
$d"${f:t/10-1?t%10<4?t%10*2:0:0:2} %B %Y"

Try it online.

Seems that using string instead of array would help a bit: f=thstndrd; $d"${f:t/10-1?t%10<4?t%10*2:0:0:2} %B %Y".

BTW, your revision 1 inspired a Bash tip. ;)

@manatwork yes - its funny - I considered trying that but didn't think it would help.  Thanks for the nudge.

PHP, 87 bytes
<?=checkdate(($a=explode("-",$argn))[1],$a[2],$a[0])?date("jS F Y",strtotime($argn)):E;

Run as pipe with -F or test it online. Always prints a 4 digit year; fails for years > 9999.
no validity check, 35 bytes:
<?=date("jS F Y",strtotime($argn));

C#, 147 143 bytes
s=>{var t=System.DateTime.Parse(s);int d=t.Day,o=d%10;return d+((d/10)%10==1?"th":o==1?"st":o==2?"nd":o==3?"rd":"th")+t.ToString(" MMMM yyy");}

Saved 4 bytes thanks to @The_Lone_Devil.

Could you not replace the second t.Day with d for a 4 byte saving?

@The_Lone_Devil Of course I could thanks, don't know how I missed that.

mIRC version 7.49 (197 bytes)
//tokenize 45 2-2-2 | say $iif($3 isnum 1- $iif($2 = 2,$iif(4 // $1 && 25 \\ $1||16//$1,29,28),$iif($or($2,6) isin 615,30,31))&&$2 isnum1-12&&1//$1,$asctime($ctime($+($1,-,$2,-,$3)date), doo mmmm yyyy))

Ruby, 104 103 102+8 = 112 111 110 bytes
Uses -rdate -p program flags.
-1 byte from manatwork.
sub(/.*-(\d*)/){Date.parse($&).strftime"%-d#{d=eval$1;(d<4||d>20)&&"..stndrd"[d%10*2,2]||:th} %B %-Y"}

Try it online!

Am I missing a reason why you not used ternary operator? d<4||d>20?"..stndrd"[d%10*2,2]:"th"

@manatwork A number like 26 will try to access indices 12..13 in the lookup string, which is out of bounds, and thus returns nil. Thus using the ternary makes it d<4||d>20?"..stndrd"[d%10*2,2]||"th":"th", which is longer by 2 bytes.

Ah, I see. Well, then cool trick @ValueInk.

Almost forgot, a tiny change: "th" → :th.

C# (.NET Core), 167 197 bytes
s=>s.Equals(DateTime.MinValue)?"":s.Day+((s.Day%10==1&s.Day!=11)?"st":(s.Day%10==2&s.Day!=12)?"nd":(s.Day%10==3&s.Day!=13)?"rd":"th")+" "+s.ToString("MMMM")+" "+s.Year

Try it online!
+30 bytes for
using System;
DateTime.Parse()

You can reverse the ternary check to get rid of the ! for -1 byte. And you can change the && to & for -3 bytes. Also, since you use s.Day 7 times it saves some bytes to create a temp value for it: s=>{var t=s.Day;return s.Equals(DateTime.MinValue)?"":t+((t%10==1&t!=11)?"st":(t%10==2&t!=12)?"nd":(t%10==3&t!=13)?"rd":"th")+" "+s.ToString("MMMM")+" "+s.Year;}

@KevinCruijssen Thanks!

You also need to include using System; or fully qualify the DateTime object.

Also DateTime.MinValue is 1-1-1 so I don't think you need that check. Which would also make my previous point irrelevant.

Are you taking input as a string or DateTime because I belive you should be taking it as a string.

Taking input as a DateTime and parsing outside of the method isn't acceptable you should do all the work inside the method. Or add an extra method to split work off.

Excel, 212 bytes
=ABS(RIGHT(A1,2))&IF(ABS(ABS(RIGHT(A1,2))-12)<2,"th",SWITCH(RIGHT(A1,1),"1","st","2","nd","3","rd","th"))&TEXT(MID(A1,FIND("-",A1)+1,FIND("-",REPLACE(A1,1,FIND("-",A1),""))-1)*30," mmmm ")&LEFT(A1,FIND("-",A1)-1)

If you break it into chunks at every ampersand, you get these pieces:

ABS() pulls the day number from the last two characters in the string. Since that may include a hyphen, ABS converts it to positive.
IF((ABS-12)<2,"th",SWITCH()) adds the ordinal. The -12 bit is because 11, 12, and 13 don't follow the normal rule and they all get th instead of st, nd, and rd. This corrects for that.

Note: The SWITCH function is only available in Excel 2016 and later. (Source) It's shorter than CHOOSE in this case because it can return a value if no match is found whereas CHOOSE requires numeric input and must have a corresponding return for each possible value.

TEXT(MID()*30," mmmm ") extracts the month name. MID() pulls out the month number as a string and multiplying by 30 returns a number. Excel sees that number as a date (1900-01-30, 1900-02-29, 1900-03-30, etc.) and TEXT() formats it as a month name with a space on both ends. 28 and 29 would have also works but 30 looks "nicer".
LEFT() extracts the year number.

Now, given all that, it would have been way easier if the test cases were all in a date range that Excel can handle as an actual date: 1900-01-01 to 9999-12-31. The big advantage is that the entire date is formatted at once. That solution is 133 bytes:
=TEXT(DATEVALUE(A1),"d""" & IF(ABS(ABS(RIGHT(A1,2))-12)<2,"th",SWITCH(RIGHT(A1,1),"1","st","2","nd","3","rd","th")) & """ mmmm yyyy")

The other big hurdle was having to include the ordinal. Without that, the solution is just 34 bytes:
=TEXT(DATEVALUE(A1),"d mmmm yyyy")

Swift 3 : 298 bytes
let d=DateFormatter()
d.dateFormat="yyyy-MM-dd"
if let m=d.date(from:"1999-10-3"){let n=NumberFormatter()
n.numberStyle = .ordinal
let s=n.string(from:NSNumber(value:Calendar.current.component(.day, from:m)))
d.dateFormat="MMMM YYY"
print("\(s!) \(d.string(from:m))")}else{print("(error/invalid)")}

Try it online!

Welcome to the site! Here the goal is to make the code as short as possible, I can see you have long variable names and lots of whitespace, you can shorten and remove these to save a lot of bytes. We also usually include a header at the top of the answer in the form of # Language, N bytes. It would be good if you could add one on too.

T-SQL, 194 bytes
DECLARE @ DATE;SELECT @=PARSE('00'+i AS DATE)FROM t;PRINT DATENAME(d,@)+CASE WHEN DAY(@)IN(1,21,31)THEN'st'WHEN DAY(@)IN(2,22)THEN'nd'WHEN DAY(@)IN(3,23)THEN'rd'ELSE'th'END+FORMAT(@,' MMMM yyy')

Input is via text column i in pre-existing table t, per our IO standards.
Works for dates from Jan 1, 0001 to Dec 31, 9999. The year is output with at least 3 digits (per 150AD example).
Invalid dates will result in the following ugly error:
Error converting string value 'foo' into data type date using culture ''.

Different default language/culture settings might change this behavior. If you want a slightly more graceful error output (NULL), add 4 bytes by changing PARSE() to TRY_PARSE().
Format and explanation:
DECLARE @ DATE;
SELECT @=PARSE('00'+i AS DATE)FROM t;
PRINT DATENAME(d,@) +
    CASE WHEN DAY(@) IN (1,21,31) THEN 'st'
         WHEN DAY(@) IN (2,22)    THEN 'nd'
         WHEN DAY(@) IN (3,23)    THEN 'rd'
         ELSE 'th' END
    + FORMAT(@, ' MMMM yyy')

The DATE data type introduced in SQL 2008 allows much wider range than DATETIME, from Jan 1, 0001 to Dec 31, 9999.
Some very early dates can be parsed wrong with my US locality settings ("01-02-03" becomes "Jan 2 2003"), so I pre-pended a couple extra zeros so it knows that first value is the year.
After that, its just a messy CASE statement to add the ordinal suffix to the day. Annoyingly, the SQL FORMAT command has no way to do that automatically.

q/kdb+ 210 bytes, non-competing
Solution:
f:{a:"I"$"-"vs x;if[(12<a 1)|31<d:a 2;:0];" "sv(raze($)d,$[d in 1 21 31;`st;d in 2 22;`nd;d in 3 23;`rd;`th];$:[``January`February`March`April`May`June`July`August`September`October`November`December]a 1;($)a 0)};

Examples:
q)f "2017-08-03"
"3rd August 2017"
q)f "1980-05-12"
"12th May 1980"
q)f "2005-12-3"
"3rd December 2005"
q)f "150-4-21"
"21st April 150"
q)f "2011-2-29"       / yes it's wrong :(
"29th February 2011"
q)f "1999-10-35"
0

Explanation:
This is a horrible challenge as there is no date formatting, so I have to create months from scratch (95 bytes) as well as generating the suffix.
Ungolfed solution is below, basically split the input string and then join back together after we've added the suffix and switched out the month.
f:{
   // split input on "-", cast to integers, save as variable a
   a:"I"$ "-" vs x;
   // if a[1] (month) > 12 or a[2] (day) > 31 return 0; note: save day in variable d for later
   if[(12<a 1) | 31<d:a 2;
     :0];
   // joins the list on " " (like " ".join(...) in python)
   " " sv (
           // the day with suffix
           raze string d,$[d in 1 21 31;`st;d in 2 22;`nd;d in 3 23;`rd;`th];
           // index into a of months, start with 0 as null, to mimic 1-indexing
           string[``January`February`March`April`May`June`July`August`September`October`November`December]a 1;
           // the year cast back to a string (removes any leading zeroes)
           string a 0)
  };

Notes:
Dates in q only go back to ~1709 so I don't have a trivial way of validating the date, hence this is a non-competing entry... The best I can do is check whether the day is > 31 or month is > 12 and return 0.