text:
Smallest unique number KoTH
Create a bot to choose the smallest unique number.
(Based on a psychology experiment I heard about many years ago but haven't been able to track down again.)
Rules

Each game will consist of 10 randomly selected bots playing 1000 rounds.
Each round, all bots select an integer from 1 to 10 (inclusive).  Any bots that choose the same value will be be excluded, and the remaining bot with the smallest value will receive a point.
In the event that no bot picks a unique value, no points will be awarded.
At the end of 1000 rounds, the bot with the most points (or all bots tied with the most points) wins the game.
The tournament will last 200 * (number of players) games.
The bot with the highest win percentage wins the tournament.

Specifications
Bots must be Python 3 classes and must implement two methods: select and update.
Bots will be constructed with an index.
select is passed no arguments and returns the bot's choice for the current round.
update is passed a list of the choices made by each bot in the previous round.
Example
class Lowball(object):
    def __init__(self, index):
        # Initial setup happens here.
        self.index = index
    def select(self):
        # Decision-making happens here.
        return 1
    def update(self, choices):
        # Learning about opponents happens here.
        # Note that choices[self.index] will be this bot's choice.
        pass

Controller
import numpy as np

from bots import allBotConstructors
allIndices = range(len(allBotConstructors))
games = {i: 0 for i in allIndices}
wins = {i: 0 for i in allIndices}

for _ in range(200 * len(allBotConstructors)):
    # Choose players.
    playerIndices = np.random.choice(allIndices, 10, replace=False)
    players = [allBotConstructors[j](i) for i, j in enumerate(playerIndices)]

    scores = [0] * 10
    for _ in range(1000):
        # Let everyone choose a value.
        choices = [bot.select() for bot in players]
        for bot in players:
            bot.update(choices[:])

        # Find who picked the best.
        unique = [x for x in choices if choices.count(x) == 1]
        if unique:
            scores[choices.index(min(unique))] += 1

    # Update stats.
    for i in playerIndices:
        games[i] += 1
    bestScore = max(scores)
    for i, s in enumerate(scores):
        if s == bestScore:
            wins[playerIndices[i]] += 1

winRates = {i: wins[i] / games[i] for i in allIndices}
for i in sorted(winRates, key=lambda i: winRates[i], reverse=True):
    print('{:>40}: {:.4f} ({}/{})'.format(allBotConstructors[i], winRates[i], wins[i], games[i]))

Additional information

No bot will play in a game against itself.
In the unlikely event that a bot is included in less than 100 games, the tournament will be rerun.
Bots may store state between rounds, but not between games.
Accessing the controller or other bots is not allowed.
The number of games and number of rounds per game are subject to increase if the results are too variable.
Any bots that raise errors or give invalid responses (non-ints, values outside [1, 10], etc.) will be disqualified, and the tournament will be rerun without them.
There is no time limit for rounds, but I may implement one if bots take too long to think.
There is no limit on the number of submissions per user.
The deadline for submissions is 23:59:59 UTC on Friday, September 28. The tournament is now closed for submissions.

Results
                BayesBot: 0.3998 (796/1991)
      WhoopDiScoopDiPoop: 0.3913 (752/1922)
           PoopDiScoopty: 0.3216 (649/2018)
                   Water: 0.3213 (660/2054)
                 Lowball: 0.2743 (564/2056)
                Saboteur: 0.2730 (553/2026)
                OneUpper: 0.2640 (532/2015)
         StupidGreedyOne: 0.2610 (516/1977)
          SecondSaboteur: 0.2492 (492/1974)
                    T42T: 0.2407 (488/2027)
                     T4T: 0.2368 (476/2010)
          OpportunityBot: 0.2322 (454/1955)
              TheGeneral: 0.1932 (374/1936)
             FindRepeats: 0.1433 (280/1954)
                  MinWin: 0.1398 (283/2025)
             LazyStalker: 0.1130 (226/2000)
               FollowBot: 0.1112 (229/2060)
                Assassin: 0.1096 (219/1999)
           MostlyAverage: 0.0958 (194/2024)
             UnchosenBot: 0.0890 (174/1955)
                 Raccoon: 0.0868 (175/2015)
               Equalizer: 0.0831 (166/1997)
       AvoidConstantBots: 0.0798 (158/1980)
WeightedPreviousUnchosen: 0.0599 (122/2038)
               BitterBot: 0.0581 (116/1996)
               Profiteur: 0.0564 (114/2023)
              HistoryBot: 0.0425 (84/1978)
            ThreeFourSix: 0.0328 (65/1984)
                 Stalker: 0.0306 (61/1994)
             Psychadelic: 0.0278 (54/1943)
              Unpopulist: 0.0186 (37/1994)
             PoissonsBot: 0.0177 (35/1978)
         RaccoonTriangle: 0.0168 (33/1964)
              LowHalfRNG: 0.0134 (27/2022)
              VictoryPM1: 0.0109 (22/2016)
            TimeWeighted: 0.0079 (16/2021)
             TotallyLost: 0.0077 (15/1945)
            OneTrackMind: 0.0065 (13/1985)
              LuckySeven: 0.0053 (11/2063)
          FinalCountdown: 0.0045 (9/2000)
                Triangle: 0.0039 (8/2052)
           LeastFrequent: 0.0019 (4/2067)
                Fountain: 0.0015 (3/1951)
             PlayerCycle: 0.0015 (3/1995)
                  Cycler: 0.0010 (2/1986)
               SecureRNG: 0.0010 (2/2032)
             SneakyNiner: 0.0005 (1/2030)
            I_Like_Nines: 0.0000 (0/1973)

Comments are not for extended discussion; this conversation has been moved to chat.

Will the indexes be from 0 to 9?

@ev3commander Yes.

What time zone is the end time?

@Arcanum I'll say the deadline is 23:59:59 UTC on Friday, but I'll probably also include anything submitted before I wake up on Saturday.

@Mnemonic Thanks; I will submit something.

@Mnemonic Any news?

@user1502040 Sorry, I've been way too busy for the past few days.  I'll run it tomorrow night.

@Mnemonic - when you do run it, you may want to consider running without PatternMatcher. When I ran a tournament locally, that one slowed everything down considerably. However, it does have a comment about a variable to adjust if it is too slow, so it may be worth looking into that.

@Mnemonic Anything?

@Herohtar I set it running before I left for work.  With any luck, it should be done when I get home.

@Mnemonic Has it finished yet?

@user1502040 I seem to have run into a lot of technical difficulties.  Maybe I should look into getting a less broken Python environment.  In the meantime, I'll try running it with Python 2 (and make the associated changes to the bots).

Would you like someone else to run it for you? I'm able to run it ok here. (My bot is nowhere close to winning, so not a major conflict of interest in that regard).

@Justin It's running right now, and doesn't seem to be crashing, but I definitely wouldn't mind the help if this run fails.

@Mnemonic When will the results be available?

@SpookClover I've had it running constantly for the past week and still isn't finished.  I'm going to rerun it with all the slow bots removed and hope it finishes in a reasonable time span.  I apologize for all the delays.

@Mnemonic Wow. Do you know what is the current iteration/game?

@SpookClover No.  I didn't think to add debug info until it had been running for a few days, and by then I didn't want to stop it.

After all that, I just removed PatternMatcher and it ran in 30 minutes.

@Mnemonic Would make sense, as @RobertFraser pointed in the comments, it can start an infinite loop

Was WaitWhatBot too slow to use too??

@JonathanAllan I had to switch to Python 2 and had too much trouble debugging it.

@Mnemonic haha hardly surprising

Henlo, I would like to run this, how do I put all the bots into from bots import allBotConstructors?

@MihailMalostanidis Create a file called bots.py in the same directory containing all the bots.  At the end, create a list of the constructors: allBotConstructors = [Lowball, BayesBot, ...]

@Mnemonic oh, okay. I thought there was a way to import all the modules in the directory as a hash or array.

I'm voting to close this question because it is already de-facto closed to new answers ("The tournament is now closed for submissions.")

@pppery As it doesn't appear like we have a consensus here just yet, I think it would be best to avoid closing any more KoTHs until we do

I don't know if this was the first, but try Nikoli derby

BayesBot
Tries to make the optimal choice using a simple statistical model.
import random

def dirichlet(counts):
    counts = [random.gammavariate(n, 1) for n in counts]
    k = 1. / sum(counts)
    return [n * k for n in counts]

class BayesBot(object):
    def __init__(self, index):
        self.index = index
        self.counts = [[0.2 * (10 - i) for i in range(10)] for _ in range(10)]
    def select(self):
        player_distributions = []
        for i, counts in enumerate(self.counts):
            if i == self.index:
                continue
            player_distributions.append(dirichlet(counts))
        cumulative_unique = 0.
        scores = [0.] * 10
        for i in range(10):
            p_unpicked = 1.
            for d in player_distributions:
                p_unpicked *= (1. - d[i])
            p_unique = p_unpicked * sum(d[i] / (1. - d[i]) for d in player_distributions)
            scores[i] = p_unpicked * (1. - cumulative_unique)
            cumulative_unique += p_unique * (1. - cumulative_unique)
        return scores.index(max(scores)) + 1
    def update(self, choices):
        for i, n in enumerate(choices):
            self.counts[i][n - 1] += 1

Avoid Constant Bots
Keep track of which bots have always returned the same value, and skip those values. Of the remaining values, select them randomly, but biased significantly towards lower values.
import numpy as np

class AvoidConstantBots(object):
    all_values = range(1, 11)
    def __init__(self, index):
        self.index = index
        self.constant_choices = None

    def select(self):
        available = set(self.all_values)
        if self.constant_choices is not None:
            available -= set(self.constant_choices)
        if len(available) == 0:
            available = set(self.all_values)
        values = np.array(sorted(available))
        weights = 1. / (np.arange(1, len(values) + 1)) ** 1.5
        weights /= sum(weights)
        return np.random.choice(sorted(available), p=weights)

    def update(self, choices):
        if self.constant_choices is None:
            self.constant_choices = choices[:]
            self.constant_choices[self.index] = None
        else:
            for i, choice in enumerate(choices):
                if self.constant_choices[i] != choice:
                    self.constant_choices[i] = None

WaitWhatBot
Not the most competitive bot and definitely not GTO, but will stifle the score of any "always 1" or "nearly always 1" opponent in the same game as in such a scenario WaitWhatBot becomes such a bot too.
Uses evolving probabilities with weighted weights both in time (more recent -> greater weight) and choice value (lower point -> greater weight).
Uses somewhat obfuscated code for a bit of a giggle.
from random import choices as weightWeight
class WaitWhatBot(object):
    def __init__(wait,what):
        weight,weightWhat=5,2
        wait.what,wait.weight=what,(weight**(weight/weight/weightWhat)+weightWhat/weightWhat)/weightWhat
        wait.whatWeight,wait.weightWeight=[wait.what==wait.weight]*int(wait.weight**weight),wait.weight
        wait.whatWhat=wait.whatWeight.pop()#wait, when we pop weight off whatWeight what weight will pop?
        wait.waitWait=tuple(zip(*enumerate(wait.whatWeight,wait.weightWeight!=wait.whatWeight)))[weightWeight==wait.weight]
    def select(what):return int(what.weight**what.whatWhat if all(not waitWait for waitWait in what.whatWeight)else weightWeight(what.waitWait,what.whatWeight)[what.weight==what.what])
    def update(waitWhat,whatWait):
        what,wait,weightWhat=set(wait for wait in whatWait[:waitWhat.what]+whatWait[waitWhat.what+1:]if wait in waitWhat.waitWait),-~waitWhat.whatWhat,waitWhat.weightWeight
        while wait not in what:
            waitWhat.whatWeight[wait+~waitWhat.whatWhat]+=weightWhat
            weightWhat/=waitWhat.weight
            wait-=~waitWhat.whatWhat
        if not wait!=(what!=weightWhat):waitWhat.whatWeight[waitWhat.whatWhat]+=weightWhat
        waitWhat.weightWeight*=waitWhat.weight

How much weight would WaitWhatBot have bought, if WaitWhatBot would but buy weight?

set([…for…in…]) ≡ {…for…in…}, by the way

@RomanOdaisky I actually advised someone of that just the other day for a golf!

Stalker
At the start of the game, this bot randomly chooses a specific index as its target. It then stalks that target the entire game, copying the number that it chose in the previous round.
import random

class Stalker(object):
  def __init__(self, index):
    # choose a random target to stalk that isn't ourself
    self.targetIndex = random.choice([x for x in range(10) if x != index])
    # get a random number to start with since we haven't seen our target's value yet
    self.targetValue = random.randint(1, 10)
  def select(self):
    return self.targetValue
  def update(self, choices):
    # look at what our target chose last time and do that
    self.targetValue = choices[self.targetIndex]

Whoop-di-scoop-di-poop
class WhoopDiScoopDiPoop(object):
    def __init__(self, index):
        self.index = index
        self.guess = 1
        self.tenure = 0
        self.perseverance = 4

    def select(self):
        return self.guess

    def update(self, choices):
        others = {c for i, c in enumerate(choices) if i != self.index}
        for i in range(1, self.guess):
            if i not in others:
                self.guess = i
                self.tenure = 0
                self.perseverance += 1
                return
        if self.guess not in others:
            self.tenure = 0
            return
        self.tenure += 1
        if self.tenure > self.perseverance:
            if self.guess == 10:
                return
            self.guess += 1
            self.tenure = 0

Poop-di-scoopty
class PoopDiScoopty(object):
    def __init__(self, index):
        self.index = index
        self.guess = 1
        self.tenure = 0
        self.perseverance = 4

    def select(self):
        return self.guess

    def update(self, choices):
        others = [c for i, c in enumerate(choices) if i != self.index]
        for i in range(1, self.guess):
            if i not in others:
                self.guess = i
                self.tenure = 0
                self.perseverance += 1
                return
        if self.guess not in others:
            self.tenure = 0
            return
        self.tenure += others.count(self.guess) # this is the change
        if self.tenure > self.perseverance:
            if self.guess == 10:
                return
            self.guess += 1
            self.tenure = 0

I've never seen or touched Python, is this unpythonic?

Add the line <!-- language: lang-python --> before the code block to enable syntax highlighting

@HermanL I hallucinated a python tag on the question and thought it would be automatic but I wrote something bad.

As for pythonicity, the code is quite good, except it might be considered pythonicer to say others = [c for i, c in enumerate(choices) if i != self.index], or, because subsequently you only use that variable for membership tests, { } rather than [ ] would construct a set rather than a list.

if (self.guess) is also very unpythonic.

I have no idea how those parens around self.guess got in there! Must have been one of the formatters.

Maybe I should make use of that list by doing self.tenure += others.count(self.guess)

+1 love your naming scheme

Opportunitybot
This bot keeps track of the lowest number not chosen by any other bots each round (the lowest available number, or opportunity), and plays the number that has been that number most frequently.
class OpportunityBot(object):
    def __init__(self, index):
        self.index = index
        self.winOccasions = [0,0,0,0,0,0,0,0,0,0]

    def select(self):
        return self.winOccasions.index(max(self.winOccasions))+1

    def update(self, choices):
        choices.pop(self.index)
        succeeded = [choices.count(i)==0 for i in range(1,11)]
        self.winOccasions[succeeded.index(True)] += 1

Flow Like Water
Avoids basic constant bot detection algorithms by doubling up on each number, slowly advancing toward lower values if they're unoccupied.
class Water(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.play = 4
        self.choices = [0]*10

    def select(self):
        if self.round > 0 and self.round%2 == 0:
            if not max([1, self.play - 1]) in self.choices:
                self.play -= 1
        return self.play

    def update(self, choices):
        self.round += 1
        self.choices = choices

I'm curious, is your bot somehow related to my Fountain? Both are "water-oriented", haha.

Honestly, my initial plan was to make a fixed guess bot that double guessed certain numbers, which was my motivation for the bot's decision making process. When I visualized it, I was thinking of a slow moving stream, which inspired the name. Shout out to the water theme though :)

So this is getting 3rd or 4th (usually 3rd) in every test I run. That's pretty amazing for such a simple strategy.

The Final Countdown
class FinalCountdown(object):
    def __init__(self, index):
        self.round = -1
    def select(self):
        self.round += 1
        return (10 - self.round // 100)
    def update(self, choices):
        pass

Try it online!
Returns 10 for the first 100 rounds, 9 for the next 100 and so on.

PatterMatcher
Looks for repeating sections in the submissions of the bots, tries to predict and avoid there numbers.
class PatternMatcher(object):
    def __init__(self, index):
        self.bots=[[]]*9
        self.index=index
    def select(self):
        minVisible=3    #increase these if this bot is to slow
        minOccurences=2
        predictions=set()
        for bot in self.bots:
            #match patters of the form A+(B+C)*minOccurences+B and use C[0] as a prediction
            for lenB in range(minVisible,len(bot)//(minVisible+1)+1):
                subBot=bot[:-lenB]
                patterns=[]
                for lenBC in range(lenB,len(subBot)//minOccurences+1):
                    BC=subBot[-lenBC:]
                    for i in range(1,minOccurences):
                        if BC!=subBot[-lenBC*i-lenBC:-lenBC*i]:
                            break
                    else:
                        patterns.append(BC)
                predictions|={pattern[lenB%len(pattern)] for pattern in patterns}
        other=set(range(1,11))-predictions
        if other: return min(other)
        else: return 1

    def update(self, choices):
        j = 0
        for i,choice in enumerate(choices):
            if i == self.index:
                continue
            self.bots[j].append(choice)
            j += 1

Triangle
The chance of picking n is (10-n)/45
import random
class Triangle(object):
    def __init__(self, index):pass
    def select(self):return random.choice([x for x in range(1, 11) for _ in range(10 - x)])
    def update(self, choices):pass

TimeWeighted
The probability a bot chooses a number is proportional to (10-n)*Δt. The first round this is identical to triangle.
import random
class TimeWeighted(object):
    def __init__(self, index):
        self.last=[0]*10
        self.round=1
    def select(self):
        weights=[(self.round-self.last[i])*(10-i) for i in range(10)]
        return 1+random.choice([x for x in range(10) for _ in range(weights[x])])

    def update(self, choices):
        for c in choices:
            self.last[c-1]=self.round
        self.round+=1

LeastFrequent
Submits the least frequently occurring number, if they are equal, take the lowest one.
class LeastFrequent(object):
    def __init__(self, index):self.frequenties=[0]*10
    def select(self):return 1+self.frequenties.index(min(self.frequenties))
    def update(self, choices):
        for c in choices:
            self.frequenties[c-1]+=1

LongestTime
Same as with frequenties but with the longest time between submissions.
class LongestTime(object):
    def __init__(self, index):
        self.frequencies=[0]*10
        self.round=1
    def select(self):return 1+self.frequencies.index(min(self.frequencies))
    def update(self, choices):
        for c in choices:
            self.frequencies[c-1]=self.round
        self.round+=1

Saboteur
Submits the lowest number which was submitted last time.
class Saboteur(object):
    def __init__(self, index):self.last=[1]
    def select(self):return min(self.last)
    def update(self, choices):self.last=choices

SecondSaboteur
Submits the second lowest number which was submitted last time
class SecondSaboteur(object):
    def __init__(self, index):self.last=[1,2]
    def select(self):return min({i for i in self.last if i!=min(self.last)})
    def update(self, choices):self.last=choices

Profiteur
Submits the lowest number not submitted last time
class Profiteur(object):
    def __init__(self, index):self.last=set()
    def select(self):return min(set(range(1, 11))-self.last, default=1)
    def update(self, choices):self.last=set(choices)

Sorry I got a bit carried away, getting idea for new bots while implementing the previous once. I wasn't sure which one would be the best and I'm curious about the performance of each of them. You can find them all here: https://repl.it/@Fejfo/Lowest-Unique-Number

Nice. You might consider modifying Saboteur to ignore its own last choice (unless that is intentional). Also, I think you may need to handle some special cases: what should SecondSaboteur do if every bot chooses the same value in some round, and what should Profiteur do if every bot chooses a different value? You may need an ending parenthesis in Profiteur after set(range(10).

PatternMatcher appears to have some sort of infinite loop or place where it gets stuck.

The top 50% RNG bot
import random

class LowHalfRNG(object):
    def __init__(self, index):
        pass
    def select(self):
        return random.randint(1, 5)
    def update(self, choices):
        pass

I was about to post a random bot, but hidefromkgb posted before me (by posting they're making themselves an easy target for the KGB, not a good way to hide). This is my first KOTH answer, just hoping to beat the rng bot.

Stupid Greedy One
class StupidGreedyOne(object):
    def __init__(self, index):
        pass
    def select(self):
        return 1
    def update(self, choices):
        pass

This bot assumes that other bots don't want to tie.
I realize this is the same as the provided example but I had the thought before I'd read that far. If this is incongruous with how KoTH challenges are run, let me know.

In general I prefer not to have duplicate bots, but I don't mind leaving it.

@Mnemonic well technically it`s not a dupe, as it doesn`t initialize self.index.

@Mnemonic No problem! Honestly, this is my first KoTH and my first anything in Python so I just followed the first two posters and didn't change it despite my suspicion that I should have. I also wasn't sure if you were going to include Lowball in your tests or it was really just an example for the post.

No worries.  Welcome to the wonderful world of KoTH!

You thew an "ace grenade":
https://puzzling.stackexchange.com/questions/45299/5-people-each-have-5-equal-cards

HistoryBot
import random

class HistoryBot(object):
    def __init__(self, index):
        self.pastWins = []
    def select(self):
        if not self.pastWins:
            return 1
        return random.choice(self.pastWins)
    def update(self, choices):
        unique = [x for x in choices if choices.count(x) == 1]
        if unique:
            self.pastWins.append(min(unique))

Implementation of user2390246's comment:

What about this then? Start with 1. After the first round, keep track
  of the winning values and pick randomly from them with probability
  equal to the number of occurrences. E.g. if the winning values in the
  first three rounds are [2, 3, 2] then in round four, pick [2] with p =
  2/3 and [3] with p = 1/3.

OneUpper
class OneUpper(object):
    def __init__(self, index):
        self.index = index
    def select(self):
        return 2
    def update(self, choices):
        pass

Everyone else's bots are either aiming for 1 or random, so why not just aim for 2?

OneTrackMind
This bot randomly picks a number and sticks with it for 50 rounds, then picks another and repeats.
import random

class OneTrackMind(object):
    def __init__(self, index):
        self.round = 0;
        self.target = random.randint(1,10)
    def select(self):
        return self.target
    def update(self, choices):
        self.round += 1;
        if self.round % 50 == 0:
            self.target = random.randint(1,10)

Lucky Seven
class LuckySeven(object):
    def __init__(self, index):
        pass
    def select(self):
        return 7
    def update(self, choices):
        pass

I'm feeling lucky today! I'm throwing out everything on 7!

My idea is that the strategy is more dependent on the number of bots than on actual evaluation of strategies.
With a significant number of bots, the options are:

"Greedy" robots aiming to the lower 1-3 numbers 10 bots being "clever" and aiming to get the lower 1-3 numbers, the best is to just let those bots interfere between them.
"Smart" robots who, once they realize 4 is always picked up, will go elsewhere.
"Random" and "constant" robots. Not much to do here.

So, I bet on #4.
class LazyStalker(object):
    def __init__(self, index):
        pass
    def select(self):
        return 4
    def update(self, choices):
        pass

Totally Lost
class TotallyLost(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.numbers = [4,8,1,5,1,6,2,3,4,2]
    def select(self):
        return self.numbers[self.round % len(self.numbers)]
    def update(self, choices):
        self.round = self.round + 1

The Cycler
This bot simply cycles through each of the numbers on its turns. Just for fun, it initializes the counter with its index.
class Cycler(object):
  def __init__(self, index):
    self.counter = index # Start the count at our index
  def select(self):
    return self.counter + 1 # Add 1 since we need a number between 1-10
  def update(self, choices):
    self.counter = (self.counter + 1) % 10

UnchosenBot
class UnchosenBot(object):
    def __init__(self, index):
        self.index = index
        self.answer = 0
    def select(self):
        if self.answer == 0:
            return 1
        return self.answer
    def update(self, choices):
        self.answer = 0
        del choices[self.index]
        for x in range(1, 11):
            if x not in choices:
                self.answer = x
                return

Takes the last round's choices, and chooses the lowest unchosen number (ignoring UnchosenBot's choice, of course).

MinWin
Keeps a running count of the winning values and the minimum unselected values (where the minimum unselected value is only considered if it is less than the winning value). It randomly selects among these winning and minimum values.
import random

class MinWin:

    def __init__(self, index):
        self.index = index
        self.mins = list(range(1, 11))
        self.wins = list(range(1, 11))

    def select(self):
        return min(random.choice(self.mins), random.choice(self.wins))

    def update(self, choices):
        counts = [0] * 10
        for x in choices:
            counts[x - 1] += 1

        if 0 in counts and (1 not in counts or counts.index(0) < counts.index(1)):
            self.mins.append(counts.index(0) + 1)
        if 1 in counts:
            self.wins.append(counts.index(1) + 1)

PlayerCycle
Cycles through the players. Current player (could be self)'s choice is now this bot's choice. Starts out printing 8, because why not. Sorry I can't python, this is probably bad code.
import itertools
class PlayerCycle(object):
    def __init__(self, index):
        self.a = itertools.cycle(range(10))
        self.b = 8
    def select(self):
        return self.b
    def update(self, choices):
        self.b = choices[next(self.a)]

Edit: Thanks to Triggernometry for improving my code with itertools

Your code works just fine, but you can add an intertools.cycle() for a so that it automatically cycles through 0-9 and you don't have to do incrementation or checks - Try it online!

The essential RNG bot
import secrets

class SecureRNG(object):
    def __init__(self, index):
        pass
    def select(self):
        return secrets.randbelow(10) + 1
    def update(self, choices):
        pass

Fountain
A simple bot, picks the lowest number first and if any other bot chooses it too, it will increment the counter - the floor gets filled and the water flows down. When it reaches 11, it restarts to 1 - the water gets pumped back to the top.
class Fountain:

    def __init__(self, index, target=10):

        # Set data
        self.index = index
        self.pick  = 1
        self.target = target+1

    def select(self):

        # Select the number
        return self.pick

    def update(self, choices: list):

        # Remove self from the list
        choices.pop(self.index)  # I hope `choices[:]` is passed, not `choices`.

        # While the selected number is occupied
        while self.pick in choices:

            # Pick next number
            self.pick += 1

            # If target was reached
            if self.pick == self.target:

                # Reset to 1
                self.pick = 1

In it's current form your bot will get stuck in the while loop if the other bots have chosen all numbers from 1 to 8. Did you mean to set target to 10?

@Emil True, it was originally like this, changed

Assassin
Stays in the shadows, then aims for the current lowest guess. Run.
class Assassin(object):
    def __init__(self, index):
        self.index = index
        self.round = 0
        self.choices = [0]*10

    def select(self):
        if self.round == 0:
            return 10
        else:
            return min(self.choices)

    def update(self, choices):
        self.round += 1
        self.choices = choices
        self.choices[self.index] = 10

FollowBot
Copy the winner from the last round, or at least the best minimally-tied selection if there was no winner.
import collections

class FollowBot(object):
    def __init__(self, index):
        self.lastround = []

    def select(self):
        counter = collections.Counter(self.lastround)
        counts = [(count,value) for (value,count) in counter.items()]
        counts.sort()
        if len(counts) >= 1:
            return counts[0][1]
        else:
            return 1

    def update(self, choices):
        self.lastround = choices

PoissonsBot
Select numbers from a Poisson distribution which is biased to lower values. Adjust the mean parameter of the distribution up if we are in a tie and down if there are guesses below us. Step size gets progessively smaller as the game proceeds.
from numpy.random import poisson
import math

class PoissonsBot(object):
    def __init__(self, index):
        self.index = index
        self.mean = 2
        self.roundsleft = 1000

    def select(self):
        self.roundsleft = max(self.roundsleft-1, 2)
        return max(min(poisson(self.mean),10),1)

    def update(self, choices):
        myval = choices[self.index]
        nequal = len([c for c in choices if c==myval])
        nless = len([c for c in choices if c<myval])
        step = math.log10(self.roundsleft)
        if nequal > 1:
            self.mean += nequal/step
        self.mean -= nless/step
        self.mean = max(self.mean, 0.3)

Psychadelic
The only way to win a nuclear war is to make yourself insane. So I'm going to make every predictive bot in the tournament insane.
class Psychadelic(object):
    def __init__(self, index):
        self.index = index
    def select(self):
        return random.randint(1, self.index + 1)
    def update(self, choices):
        pass

Raccoon
Choose the lowest number not chosen in the previous round, except our own previous choice, which could be chosen again this time. In the first round, choose 1. (Given 9 opponents and 10 choices, there is guaranteed to be one available value.)
I came up with this independently, but now see at least 2 previous bots that are essentially the same.
class Raccoon(object):
    def __init__(self, index):
        self.index = index
        self.last_round = None
        self.domain = None
    def select(self):
        # Return the lowest number not chosen last time.
        if self.domain is None:
            return 1
        else:
            # This finds the smallest element of domain, not present in last_round
            return min(self.domain-self.last_round)
    def update(self, choices):
        last_round = choices[:]
        last_round[self.index] = 0 # don't include our own choice
        self.last_round = set(last_round)
        if self.domain is None:
            self.domain = set(range(1,len(choices)+1))

Raccoon Triangle
Combines Raccoon and Triangle: from the unchosen values, choose one based on reverse triangle probability.
import random
class RaccoonTriangle(object):
    def __init__(self, index):
        self.index = index
        self.unchosen = set([1,])
        self.domain = None
    def select(self):
        # Return the lowest number not chosen last time.
        if self.domain is None:
            return random.randint(1,self.index+1)
        else:
            # Reverse triangle weights for unchosen values
            weighted_choices = [u for i,u in enumerate(sorted(self.unchosen),0) for _ in range(len(self.unchosen)-i)]
            return random.choice(weighted_choices)
    def update(self, choices):
        last_round = choices[:] # make a copy
        last_round[self.index] = 0 # don't include our own choice
        if self.domain is None:
            self.domain = set(range(1,len(choices)+1))
        self.unchosen = self.domain - set(last_round)

Error: AttributeError: 'RaccoonTriangle' object has no attribute 'boundaries'

Yes, sorry. I think I fixed it. I was in the middle of writing tests, when I left off.

Tit for two Tats
T4T, but uses the value from two rounds ago.
class T42T(object):
    def __init__(self, _):
        self.next = [10, 9]
    def select(self):
        return self.next.pop(0)
    def update(self, choices):
        choice = 10
        for c in choices:
            choice = min(c, choice)
        choice += 1
        if(choice > 10): choice = 10
        self.next.append(choice)

MostlyAverage
import random
import math

class MostlyAverage(object):
    def __init__(self, index):
        self.total = 0
        self.count = 0
    def select(self):
        returner = []
        if self.count > 0:
            avg = math.floor(self.total / self.count)
            if avg > 1:
                returner.append(avg-1)
            if avg < 10:
                returner.append(avg+1)
            returner.append(avg)
            returner.append(avg)
        else:
            returner.append(1)
        return returner[random.randint(0, len(returner)-1)]
    def update(self, choices):
        for x in range(11):
            if choices.count(x) == 1:
                self.total += x
                self.count += 1
                break

Keeps track of the average winning number and returns a number close to it.

I Like Nines
This bot is bored, and doesn't like mind games. So they choose a random number every turn. However, they only have a d16 and don't want to disrupt the game to go find a d10 - so they choose 9 anytime they roll greater than 10!
EDIT:
Just to be contrary, if own index is 9, the bot will instead prefer 6 when they roll greater than 10.
import random

class I_Like_Nines(object):
    def __init__(self, index):
        self.index = index

    def select(self):
        r = 0
        # 4 bits are required to code 1-10 ([0-9] + 1, [0b0000 - 0b1001] + 1)
        for i in range(0, 4):
            # flip a coin. Puts a 1 in this bit place 50% of the time
            if random.random() >= .50:
                r += 2 ** i
        # if your random bit assigning has produced a number outside the range 1-10, prefer 9
        if not (0 < r < 11):
            # when you are Bot #9, prefer 6
            if self.index == 9:
                r = 6
            else:
                r = 9
        return r

    def update(self, choices):
        pass

The General
The General always fights the last war(s).
import numpy
import random

class TheGeneral:
    def __init__(self, index):
        self.round = 0
        self.index = index
        self.would_have_won = [0] * 10

    def select(self):
        if self.round <= 100:
            return random.choice((list(numpy.nonzero(self.would_have_won)[0]) + [0, 1])[:2]) + 1

        return random.choice(numpy.argsort(self.would_have_won)[-2:]) + 1

    def update(self, choices):
        for i, s in enumerate(numpy.bincount([c - 1 for i, c in enumerate(choices)
            if i != self.index], minlength=10)):

            if s == 0:
                self.would_have_won[i] += 1
            elif s == 1:
                break

        self.round += 1

No-Repeat Random
import secrets

class NoRepeats(object):
    def __init__(self, index):
        self.lastround = secrets.randbelow(10) + 1

    def select(self):
        i = secrets.randbelow(10) + 1
        while i == self.lastround:
             i = secrets.randbelow(10) + 1
        self.lastround = i
        return self.lastround

    def update(self, choices):
        pass

Bot picks randomly, but avoids picking the same number it did the previous round.

Equalizer
class Equalizer(object):
    def __init__(self,index):
        self.chosen = []
        self.wins = {}
        for x in range(10):
            self.chosen.append([])
            self.wins[x] = 0
        self.choice = 1
    def select(self):
        return self.choice
    def update(self,choices):
        winningnum = 0
        for x in range(11):
            if choices.count(x) == 1:
                winningnum = x
                break
        for x in range(10):
            self.chosen[x].append(choices[x])
            if choices[x] == winningnum:
                self.wins[x] += 1
        bests = [key for key in self.wins.keys() if self.wins[key] == max(self.wins.values())]
        if len(bests) > 0:
            target = bests[random.randint(0, len(bests)-1)]
            if len(self.chosen[target]) > 2:
                shortlist = self.chosen[target][-20:]
                match = self.chosen[target][-1]
                match2 = self.chosen[target][-2]
                lastmatch = 0
                foundit = 0
                for k,v in enumerate(shortlist):
                    if k < 19:
                        if lastmatch == 1:
                            if shortlist[k] == match:
                                lastmatch = 2
                            else:
                                lastmatch = 0
                        elif lastmatch == 2:
                            self.choice = v
                            foundit = 1
                            break
                        elif shortlist[k] == match2:
                            lastmatch = 1
                if foundit == 0:
                    lastmatch = 0
                    for k,v in enumerate(shortlist):
                        if k < 19:
                            if lastmatch == 1:
                                self.choice = v
                                foundit = 1
                                break
                            elif shortlist[k] == match:
                                lastmatch = 1

Tries to keep the scores in the lead as even as possible by attempting to match what the first place bot will play, looking for patterns in their recent selections. It works on some bot combinations better than others, but I have seen many games within one/two points or even tied at the end.

BitterBot
Chooses randomly for 100 rounds, then targets the bot with the most wins (besides itself) to that point and mimics it for the rest of the game.
import random

class BitterBot(object):
    def update_score(self, choices):
        for element in sorted(choices):
            if choices.count(element) == 1:
                self.scores[choices.index(element)] += 1
                return
        return

    def choose_target(self):
        other_bot_scores = list(self.scores)
        del(other_bot_scores[self.index])
        return self.scores.index(max(other_bot_scores))

    def __init__(self, index):
        self.scores = [0] * 10
        self.index = index
        self.round_num = 0
        self.target_index = 0
        self.target_choice = 0

    def select(self):
        if self.round_num <= 100:
            return random.choice(range(1,5))
        else:
            return self.target_choice

    def update(self, choices):
        self.round_num += 1
        if self.round_num < 100:
            self.update_score(choices)
        elif self.round_num == 100:
            self.target_index = self.choose_target()
        else:
            self.target_choice = choices[self.target_index]

ThreeFourSix
Start with 6, then randomly pick one of 3, 4, 6, excluding the one picked in the previous round.
Seeing other (random) bots picking 1 or 2 each round and picking values < 5 or lower values more often, hopefully using 3, 4 and 6 is a good combination.
class ThreeFourSix(object):
    def __init__(self, index):
        self.index = index
        self.guess = 6
    def select(self):
        return self.guess
    def update(self, choices):
        threeFourSix = [3, 4, 6]
        threeFourSix.remove(choices[self.index])
        self.guess = threeFourSix[random.randint(0, 1)]

Unpopulist
import random

class Unpopulist(object):
    def __init__(self, index):
        self.index = index
        self.choices = [0, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    def select(self):
        pos = random.randrange(sum(self.choices))
        for i in range(1, 11):
            if pos < self.choices[i]:
                return i
            pos -= self.choices[i]
    def update(self, choices):
        choices[self.index] = 0;
        for i in range(1, 11):
            if choices.count(i) < 2:
                self.choices[i] += 11 - i

Prefers lower numbers as long as they don't get too many collisions.

You should change choices[i] to self.choices[i] (2x) in select.

Tit for Tat
Plays 10, then the smallest value plus one last round.
class T4T(object):
    def __init__(self, _):
        self.next = 10
    def select(self):
        return self.next
    def update(self, choices):
        choice = 10
        for c in choices:
            choice = min(c, choice)
        choice += 1
        if(choice > 10): choice = 10
        self.next = choice

choose should be select

I would never do something that stupid <3

Victory Plus Minus One
Randomly picks one above or below the last victory number.
import random

class VictoryPM1(object):
    def __init__(self, index):
        self.index = index
        self.victory = 0

    def select(self):
        if self.victory < 1 or self.victory > 10:
            return 1
        elif random.randint(0, 1):
            if self.victory < 2:
                return 2
            else:
                return self.victory - 1
        else:
            if self.victory > 9:
                return 9
            else:
                return self.victory + 1

    def update(self, choices):
        a = [0] * 11
        for b in choices:
            a[b] += 1
        for i in range(1, 10):
            if a[i] == 1:
                self.victory = i
                return

Nobody likes high numbers but somebody might decide to go for 10 as the least obvious pick. Avoid those tricksters and sneak in with a 9.
class SneakyNiner(object):
    def __init__(self, index):
        self.index = index
    def select(self):
        return 9
    def update(self, choices):
        pass

FindRepeats
class FindRepeats(object):
    def __init__(self,index):
        self.wins = []
    def select(self):
        returner = 1
        length = len(self.wins)
        if length > 10 and self.wins[-1] == self.wins[-6] and self.wins[-2] == self.wins[-7] and self.wins[-3] == self.wins[-8] and self.wins[-4] == self.wins[-9] and self.wins[-5] == self.wins[-10]:
            returner = self.wins[-5]
        if length > 8 and self.wins[-1] == self.wins[-5] and self.wins[-2] == self.wins[-6] and self.wins[-3] == self.wins[-7] and self.wins[-4] == self.wins[-8]:
            returner = self.wins[-4]
        if length > 6 and self.wins[-1] == self.wins[-4] and self.wins[-2] == self.wins[-5] and self.wins[-3] == self.wins[-6]:
            returner = self.wins[-3]
        if length > 4 and self.wins[-1] == self.wins[-3] and self.wins[-2] == self.wins[-4]:
            returner = self.wins[-2]
        if length > 2 and self.wins[-1] == self.wins[-2]:
            returner = self.wins[-1]
        return returner
    def update(self,choices):
        for x in range(11):
            if choices.count(x) == 1:
                self.wins.append(x)
                break

Tries to find patterns in the most recent winning numbers and continue the pattern if it finds one.

WeightedPreviousUnchosen
Selects from the previous round's unchosen numbers. In order, each has the chance to be chosen equal to 1.25 * (the frequency at which they remained unchosen in the next round).

class WeightedPreviousUnchosen(object):

    def get_unchosen(self, list):
        unique_choices = []
        duplicate_choices = []
        for choice in list:
            if choice not in duplicate_choices:
                if choice in unique_choices:
                    duplicate_choices.append(choice)
                    unique_choices.remove(choice)
                else:
                    unique_choices.append(choice)
        return unique_choices

    def __init__(self, index):
        self.previous_unchosen = []
        self.current_choices = []
        self.unchosen_history = []
        for n in range (0, 10):
            self.unchosen_history.append([0, 0])

    def select(self):
        current_unchosen = sorted(self.get_unchosen(self.current_choices))
        if len(self.previous_unchosen) > 0:
            rand = random.random()
            for n in range(0, len(current_unchosen) - 1):
                option = (self.unchosen_history)[n]
                if option[0] != 0:
                    if ( (option[1] / option[0])*1.25 > rand):
                        self.previous_unchosen = current_unchosen
                        return sorted(self.get_unchosen(self.current_choices))[n]

        self.previous_unchosen = current_unchosen
        return random.choice(range(2,5))

    def update(self, choices):
        self.current_choices = choices
        for n in range(1, 10):
            if n in self.previous_unchosen:
                self.unchosen_history[n-1][0] += 1
                if n not in choices:
                    self.unchosen_history[n-1][1] += 1

Based on the premise that bots could be missing would-be-winning numbers. Looks like this generally isn't the case, so it isn't performing too well!


text:
Rest of the path
Consider this file path:
C:/Users/Martin/Desktop/BackupFiles/PC1/images/cars/new.png

Your goal is to write a program that will return the file path starting from the folder after the last folder that contains a number till the filename. So, for the above file path, the program should return images/cars/new.png.
One or more of the folder names may also contain spaces:
C:/Users/User1/Documents/Books/eBooks/PM2.4/ref/Project Management.pdf

should return ref/Project Management.pdf.
Also this filepath:
C:/Users/John/Videos/YouTube/3D/Animations/52.Tricky.Maneuvers/video234.png

should return video234.png.
Your answer should work with the above given three examples and all other examples that match the criteria described here.
This is code-golf, so the answer with the lowest number of bytes yields victory.
I am new to the site, so feel free to modify my post and/or add relevant tags.

@Stephen "challenge proposer".

Suggested test case: C:/Users/User1a/Documents/foo.pdf. (Can you confirm that the expected output is Documents/foo.pdf in that case?) And welcome to PPCG!

Sorry @Stephen, I forgot to mention that. As per the example given, you should start from the last folder that contains a number in its name. I will also modify the question so that there are three examples.

Do we have to support some weird Unicode characters like ߈ ("NKO DIGIT EIGHT") and would it be considered a number?

What if the "part" after the last folder with a digit is the file name not a folder? (example C:\a1\a.pdf)

No, numbers are 0,1,2,3,4,5,6,7,8,9 and 0.

C:\a1\a.pdf should return a.pdf.

Yes @arnuld, this is the required output from your path.

@WaisKamal are backslashes considered legitimate path separators?

I'm not clear on why the third test case is supposed to return Animations/52.Tricky.Maneuvers/video234.png as opposed to video234.png. Why does the folder 52.Tricky.Maneuvers not count as " the last folder that contains a number"?

I agree with @mypetlion. 52.Tricky.Maneuvers contains a digit, so shouldn't it output video234.png instead? And if you mean by "starting from the folder after the last folder that contains a number till the filename" that there should always be at least one folder (regardless if it contains digits) before the file-path, doesn't that mean that C:\a1\a.pdf should output C:\a1\a.pdf?..

As per the example given, you should start from the last folder that contains a number in its name.. This is not what your third example suggests. However, this is what your second example does. So, what's the correct one?

@mypetlion (challenge fixed)

Um, does our code need to work with something else than the provided three examples? "Your answer should work with the above given three examples." If it should only work for the three examples, that's highly discouraged.

These are just examples, so the answer must work as specified in the question.

Retina, 9 bytes
.*\d.*?/

Regex is ported from @Arnauld's JavaScript answer, so make sure to upvote him!
Try it online.
Explanation:
.*\d.*?/    # Main regex:
.*          #  Zero or more characters (as much as possible)
  \d        #  Followed by a digit
    .*?     #  Followed by zero or more optional characters (as few as possible)
       /    #  Followed by a slash

            # Replace the match with:
            #  Nothing (so basically remove that leading part)

Gema, 8 bytes
*<D>*\/=

Sample run:
bash-4.4$ gema '*<D>*\/=' <<< 'C:/Users/Martin/Desktop/BackupFiles/PC1/images/cars/new.png'
images/cars/new.png

bash-4.4$ gema '*<D>*\/=' <<< 'C:/Users/User1/Documents/Books/eBooks/PM2.4/ref/Project Management.pdf'
ref/Project Management.pdf

bash-4.4$ gema '*<D>*\/=' <<< 'C:/Users/John/Videos/YouTube/3D/Animations/52.Tricky.Maneuvers/video234.png'
video234.png

Try it online!

Can you please add a Try It Online link? I am not familiar with Gema.

Sorry, unfortunately there is no Gema or GeL interpreter on TIO. ☹

Requested.

Might have better luck if you ask @pxeger to add it to https://ato.pxeger.com

JavaScript (ES6), 29 28 bytes
Saved 1 byte thanks to @tsh
s=>s.replace(/.*\d.*?\//,'')

Try it online!
How?
                                             /.*\d.*?\//
                                              \/\/\_/\/
                                              | |  | |
match the longest possible string (greedy) ---+ |  | |
match a digit ----------------------------------+  | |
match the shortest possible string (lazy) ---------+ |
match a slash ---------------------------------------+

So, why not s=>s.replace(/.*\d.*?\//,'')?

@tsh Because I didn't realize it would be 1 byte shorter. :p Thanks!

05AB1E, 23 bytes
'/©¡RćUηí®ýʒþd_}®«õš¤X«

Try it online or verify all test cases.
05AB1E has no regexes, so this is not the kind of challenge it will do very good, in comparison to non-verbose languages that have a strength in regexes like Retina or Pyth.
Can probably still be golfed by at least a few bytes, though.
Explanation:
'/              # Push a "/"
  ©             # Store it in the register (without popping)
   ¡            # Split the (implicit) input on slashes
                #  i.e. "C:/Desktop3/Stuff/F0/images/new1.png"
                #   → ["C:","Desktop3","Stuff","F0","images","new1.png"]
    R           # Reverse this list
                #  i.e. ["C:","Desktop3","Stuff","F0","images","new1.png"]
                #   → ["new1.png","images","F0","Stuff","Desktop3","C:"]
     ć          # Head extracted
                #  i.e. ["new1.png","images","F0","Stuff","Desktop3","C:"]
                #   → ["images","F0","Stuff","Desktop3","C:"] and "new1.png"
      U         # Pop the extracted head, and store it in variable `X`
       η        # Take the prefixes of the list
                #  i.e. ["images","F0","Stuff","Desktop3","C:"]
                #   → [["images"],["images","F0"],["images","F0","Stuff"],["images","F0","Stuff","Desktop3"],["images","F0","Stuff","Desktop3","C:"]]
        í       # Reverse each inner list again
                #  i.e. [["images"],["images","F0"],["images","F0","Stuff"],["images","F0","Stuff","Desktop3"],["images","F0","Stuff","Desktop3","C:"]]
                #   → [["images"],["F0","images"],["Stuff","F0","images"],["Desktop3","Stuff","F0","images"],["C:","Desktop3","Stuff","F0","images"]]
         ®ý     # And join them by slashes
                #  i.e. [["images"],["F0","images"],["Stuff","F0","images"],["Desktop3","Stuff","F0","images"],["C:","Desktop3","Stuff","F0","images"]]
                #   → ["images","F0/images","Stuff/F0/images","Desktop3/Stuff/F0/images","C:/Desktop3/Stuff/F0/images"]
ʒ   }           # Now filter this list by:
 þ              #  Leave only the digits of this suffix
                #   i.e. "images" → ""
                #   i.e. "Stuff/F0/images" → 0
  d             #  Check if its >= 0
                #   i.e. "" → 0 (falsey)
                #   i.e. 0 → 1 (truthy)
   _            #  Inverse the boolean (0→1; 1→0)
     ®«         # Append a slash to each remaining suffix
                #  i.e. ["images"] → ["images/"]
       õš       # Prepend an empty string as list
                # (work-around when there are no valid suffices left)
                #  i.e. [] → [""]
                #  i.e. ["images/"] → ["","images/"]
         ¤      # Take the last suffix
                #  i.e. ["","images/"] → "images/"
          X«    # Append variable `X` (and implicitly output the result)
                #  i.e. "images/" and "new1.png" → "images/new1.png"

sed, 17 bytes
s:.*[0-9][^/]*/::

Try it online.

Java (JDK 10), 26 bytes
s->s.split(".*\\d.*?/")[1]

Try it online!

Ruby, 22 bytes
->s{s=~/.*\d.*?\//;$'}

Try it online!
Same trick as Arnauld and everybody else after him.

+1 for TIL about $'

Powershell 42 39 45 30 26 bytes
Find the last decimal before a /, fetch all after the slash as the new path
($args-split'.*\d.*?/')[1]

Try it out!

- many bytes by tips from @mazzy
- 1 byte by removing non-required regex escape
- 4 bytes as the regex with -split didn't require a group
- added greedy decimal search

Great! I think $args is better then param($f)$f

Completely right, although using $args within an expression always seems to return the original value, powershell might think of it as an array with ToString() or something. Works fine with indexer though.

"$args". Welcome to Tips for golfing in PowerShell

The script return 2 values: $true/$false from -match expression and match string. I think should return the file path means the file path only. I beleave it can be golfed more.

Included both solutions. Up to @Wais Kamal which output matches the criteria.

$v= is shorter then >$null :) and try a -split instead -match.

split had a lot of additional perks :)

Cool! and... fatality... Finish it! ($args-split'.*\d.*?/')[1] :)

port of Javascript by @tsh $args-replace'.*\d.*?/'. Welcome to PPCG!

Jelly, 16 bytes
”/ɓṣṖf€ØDTṀ‘ṫ@ṣj

A monadic Link accepting a list of characters which returns a list of characters.
Try it online!
How?
”/ɓṣṖf€ØDTṀ‘ṫ@ṣj - Link: list of characters X
”/               - literal '/' character
  ɓ              - start a new dyadic chain with swapped arguments - i.e. f(X,'/')
   ṣ             - split (X) at ('/')
    Ṗ            - pop (removes the rightmost)
       ØD        - yield digit characters
      €          - for each: (of the parts of X after the split & pop)
     f           -   filter keep (digit characters)
         T       - truthy indices (1-indexed indices of parts containing any digit(s))
          Ṁ      - maximum (rightmost, zero if not found)
           ‘     - increment
              ṣ  - split (X) at ('/') (...again)
             @   - with swapped arguments:
            ṫ    -   tail (the split X) from index (the incremented maximum...)
               j - join with ('/')

Python 2, 74 bytes
r='';k=1
for c in input():
 r+=c;k-=c.isdigit()
 if'/'==c:r*=k;k=1
print r

Try it online!

Python 2, 42 bytes
lambda n:re.sub(".*\d.*?/","",n)
import re

Try it online!

Red, 79 bytes
func[s][d: charset"0123456789"parse s[any[to d thru"/"]copy t to end(print t)]]

Try it online!

Pyth, 14 bytes
The same regex solution that everybody else is using:
:z"^.*\d.*?/""

:                     regex replace
 z                    in the input() string
  "^.*\d.*?/"         the file path up until the last path with a number
             "        with an empty string (Pyth terminates this string at end of program)

Pip, 12 bytes
a@`\d.*?/`$'

Takes input as a command-line argument. Verify all test cases: Try it online!
Explanation
Does not use the same regex as everyone else! (Well, okay, it's pretty similar.)
a             1st command-line argument
  `\d.*?/`    Regex matching a digit, as few characters as possible, and then a slash
 @            Find all matches
          $'  Special variable: the portion of the string after the last match
              Autoprint

The shortest solutions using the standard regex were all 13 bytes:
aRM`.*\d.*?/`   Remove
a|>`.*\d.*?/`   Left-strip
aR`.*\d.*?/`x   Replace with empty string

Matlab, 68 bytes
function[r]=f(s),[~,b]=regexp(s,'/[^/;]+\d[^/;]+/');r=s(b+1:end);end

Try it Online!

disp(f('C:/Users/John/Videos/YouTube/3D/Animations/52/video234.png'));  output an empty string due to + in regex.

PHP 4 (42 chars)
Given $argv[1], it will silence the end's “Only variables should be passed by reference” notice.
echo@end(preg_split(';\d.*?/;',$argv[1]));

C, 103 100 88 bytes
*a(b,l)char*b;{for(b+=l;!isdigit(*b);b--);for(;*b-47&&*b;b++);for(;*b-47;b--);return b;}

Try online!
-22 bytes thanks to @ceilingcat
Note: the function may have undefined behavior if the string doesn't contain a number and a "/" character!
Ungolfed version
*a(b,l)char*b; /* b:the string, l:the length of the string */
{
    for(b += l; !isdigit(*b); b--); /* start from the end of the string and decrement pointer until a number is found */
    for(; *b-47 && *b; b++); /*increment pointer until '/' (ascii value 47) is found*/
    for(; *b-47; b--); /* decrement pointer until '/' is found */
    return b;
}

88 bytes

Perl 5 -p, 13 bytes
12 bytes for code; +1 for -p switch
s!.*\d.*?/!!

Try it online!
I solemnly swear I came up with my own solution before reading other answers.

source: Stack Exchange

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

sort: votes