DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #174 - Soccer League Table

Implement the class LeagueTable.

This class is used to store results of soccer matches played, as well as return and display various stats about each team.

Match results will always be passed through the push method as a string in the format "Home Team 0 - 0 Away Team".

When a new match ends and the scores are tallied, points are awarded to teams as follows:

 3 points for a win 1 point for a draw 0 points for a loss

The class must have the following methods:

 push(match_string) # insert a new match record get_points(team_name) # Returns the no. of points a team has, 0 by default get_goals_for(team_name) # Returns the no. of goals a team has scored, 0 by default get_goals_against(team_name) # Returns the no. of goals a team has conceded (had scored against them), 0 by default get_goal_difference(team_name) # Return the no. of goals a team has scored minus the no. of goals a team has conceded, 0 by default get_wins(team_name) # Return the no. of wins a team has, 0 by default get_draws(team_name) # Return the no. of draws a team has, 0 by default get_losses(team_name) # Return the no. of losses a team has, 0 by default

Example

lt = LeagueTable.new lt.push("Man Utd 3 - 0 Liverpool") puts lt.get_goals_for("Man Utd") => 3 puts lt.get_points("Man Utd") => 3 puts lt.get_points("Liverpool") => 0 puts lt.get_goal_difference("Liverpool") => -3 lt.push("Liverpool 1 - 1 Man Utd") puts lt.get_goals_for("Man Utd") => 4 puts lt.get_points("Man Utd") => 4 puts lt.get_points("Liverpool") => 1 puts lt.get_goals_against("Man Utd") => 1 puts lt.get_points("Tottenham") => 0

Test

lt.push("Man Utd 2 - 1 Liverpool")
lt.push("Liverpool 3 - 0 Man Utd")

Good luck!


This challenge comes from Mackay on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Top comments (4)

Collapse
 
craigmc08 profile image
Craig McIlwrath

Well, I attempted this in Haskell. I took the liberty of making each function return a Maybe value, being Nothing when the team isn't in the table instead of defaulting to 0. It uses a state monad to make storing and "mutating" data feasible. I decided to implement my own state monad here, but there are (better) implementations on stackage.

module SoccerLeague ( Game, Name, Team, League , empty, push, points , goalsFor, goalsAgainst, goalDifference , wins, draws, losses , State, runState ) where import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Bifunctor (bimap) import Data.Tuple (swap) import Data.Maybe (fromMaybe) import Data.Bool (bool) import Text.ParserCombinators.Parsec ( GenParser, ParseError, parse, many1 , digit, letter, string, space, (<|>)) type Name = String data Team = Team { t_name :: Name , t_goalsFor :: Int , t_goalsAgainst :: Int , t_wins :: Int , t_draws :: Int , t_losses :: Int } deriving Show type League = Map Name Team type Game = ((Name, Int), (Name, Int)) updateTeam :: Name -> (Int, Int) -> State League () updateTeam name (for, against) = do map <- get let t = fromMaybe (Team name 0 0 0 0 0) $ map M.!? name let t' = Team { t_name = name , t_goalsFor = t_goalsFor t + for , t_goalsAgainst = t_goalsAgainst t + against , t_wins = t_wins t + bool 0 1 (for > against) , t_draws = t_draws t + bool 0 1 (for == against) , t_losses = t_losses t + bool 0 1 (for < against) } put $ M.insert name t' map empty :: League empty = M.empty push :: String -> State League () push str = case parseGame str of Left e -> error $ "bad push input: " ++ str Right ((n1, s1), (n2, s2)) -> do updateTeam n1 (s1, s2) updateTeam n2 (s2, s1) -- Lifts a simple function on a team in the league to work on the state liftToState :: (Team -> a) -> String -> State League (Maybe a) liftToState f name = get >>= return . fmap f . (flip (M.!?) name) points :: String -> State League (Maybe Int) points = liftToState $ sum . flip fmap [(*3) . t_wins, t_draws] . flip ($) goalsFor :: String -> State League (Maybe Int) goalsFor = liftToState t_goalsFor goalsAgainst :: String -> State League (Maybe Int) goalsAgainst = liftToState t_goalsAgainst goalDifference :: String -> State League (Maybe Int) goalDifference = liftToState (\team -> t_goalsFor team - t_goalsAgainst team) wins :: String -> State League (Maybe Int) wins = liftToState t_wins draws :: String -> State League (Maybe Int) draws = liftToState t_draws losses :: String -> State League (Maybe Int) losses = liftToState t_losses -- GAME STRING PARSER trimSpaces :: String -> String trimSpaces = f . f where f = dropWhile (==' ') . reverse number :: GenParser Char st Int number = many1 digit >>= return . read name :: GenParser Char st Name name = many1 (letter <|> space) >>= return . trimSpaces game :: GenParser Char st Game game = do fstName <- name fstScore <- number string " - " sndScore <- number sndName <- name return ((fstName, fstScore), (sndName, sndScore)) parseGame :: String -> Either ParseError Game parseGame = parse game "(unknown)" -- STATE MONAD IMPLEMENTATION newtype State s a = State { runState :: s -> (s, a) } instance Functor (State s) where fmap f (State x) = State $ bimap id f . x instance Applicative (State s) where pure x = State $ \s -> (s, x) (State sf) <*> (State sx) = State $ \s -> let (s', f) = sf s (s'', x) = sx s in (s'', f x) instance Monad (State s) where return = pure sx >>= f = State $ \s -> let (s', x) = runState sx s in runState (f x) s' get :: State s s get = State $ \x -> (x, x) put :: s -> State s () put s = State $ const (s, ()) 

Additionally, here's the example given in the post translated to my Haskell implementation:

import SoccerLeague import Data.Maybe (fromMaybe) example :: State League [Int] example = do push "Man Utd 3 - 0 Liverpool" l1 <- sequence [goalsFor "Man Utd", points "Man Utd", points "Liverpool", goalDifference "Liverpool"] push "Liverpool 1 - 1 Man Utd" l2 <- sequence [goalsFor "Man Utd", points "Man Utd", points "Liverpool", goalsAgainst "Man Utd", points "Tottenham"] return $ map (fromMaybe 0) $ l1 ++ l2 main :: IO () main = do sequence_ $ map print $ snd $ runState example empty 
Collapse
 
ruanengelbrecht profile image
Ruan Engelbrecht

Definitely some things missing and room for improvement but here we go. JavaScript.

 const calcPoints = Symbol('calcPoints'); class LeagueTable { constructor() { this.matches = []; this.teamMapping = null; } [calcPoints](a, b) { return a > b ? 3 : a === b ? 1 : 0; } push(matchString) { let rawScore = matchString.match(/\d+\s*\-\s*\d+/g)[0]; let teamGoals = rawScore.split('-').map(x => parseInt(x.trim())); let names = matchString.split(rawScore).map(x => x.trim()); if (!this.teamMapping) { this.teamMapping = names.map((x, i) => ({ key: i, value: x })); } let teamOne = { teamId: this.teamMapping.find(x => x.value === names[0]).key, goals: teamGoals[0], gd: teamGoals[0] - teamGoals[1], points: this[calcPoints](teamGoals[0], teamGoals[1]) }; let teamTwo = { teamId: this.teamMapping.find(x => x.value === names[1]).key, goals: teamGoals[1], gd: teamGoals[1] - teamGoals[0], points: this[calcPoints](teamGoals[1], teamGoals[0]) }; this.matches.push(teamOne); this.matches.push(teamTwo); } getPoints(teamName) { let teamId = this.teamMapping.find(x => x.value === teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => acc + v.points, 0); } getGoalsFor(teamName) { let teamId = this.teamMapping.find(x => x.value === teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => acc + v.goals, 0); } getGoalsAgainst(teamName) { let teamId = this.teamMapping.find(x => x.value !== teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => acc + v.goals, 0); } getGoalDifference(teamName) { let teamId = this.teamMapping.find(x => x.value !== teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => acc + -v.gd, 0); } getWins(teamName) { let teamId = this.teamMapping.find(x => x.value === teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => (v.points > 1 ? acc + 1 : acc), 0); } getDraws(teamName) { let teamId = this.teamMapping.find(x => x.value === teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => (v.points === 1 ? acc + 1 : acc), 0); } getLosses(teamName) { let teamId = this.teamMapping.find(x => x.value === teamName).key; return this.matches .filter(x => x.teamId === teamId) .reduce((acc, v) => (v.points === 0 ? acc + 1 : acc), 0); } } let lt = new LeagueTable(); lt.push('Man Utd 3 - 0 Liverpool'); console.log(`Goals : ${lt.getGoalsFor('Man Utd')}`); console.log(`Points : ${lt.getPoints('Man Utd')}`); console.log(`Points : ${lt.getPoints('Liverpool')}`); console.log(`Goal Difference : ${lt.getGoalDifference('Liverpool')}`); lt.push('Liverpool 1 - 1 Man Utd'); console.log(`Goals : ${lt.getGoalsFor('Man Utd')}`); console.log(`Points : ${lt.getPoints('Man Utd')}`); console.log(`Points : ${lt.getPoints('Liverpool')}`); console.log(`Goals Against : ${lt.getGoalsAgainst('Man Utd')}`); 
Collapse
 
cipharius profile image
Valts Liepiņš

Ruby, concise as usual:

class LeagueTable def initialize @results = Hash.new(Hash.new(0)) end def push str str.match /^(.+) (\d+) - (\d+) (.+)$/ h_name, a_name = $1, $4 h_goal, a_goal = $2.to_i, $3.to_i result = h_goal <=> a_goal @results[h_name] = Hash.new(0) unless @results.has_key? h_name @results[a_name] = Hash.new(0) unless @results.has_key? a_name @results[h_name].tap do |stats| stats[:goals] += h_goal stats[:conceded] += a_goal stats[:wins] += (result + 1) / 2 stats[:losses] += (-result + 1) / 2 stats[:draws] += -(result.abs) + 1 end @results[a_name].tap do |stats| stats[:goals] += a_goal stats[:conceded] += h_goal stats[:wins] += (-result + 1) / 2 stats[:losses] += (result + 1) / 2 stats[:draws] += -(result.abs) + 1 end end def goals_for team @results[team][:goals] end def goals_against team @results[team][:conceded] end def goal_difference team @results[team][:goals] - @results[team][:conceded] end def get_wins team @results[team][:wins] end def get_draws team @results[team][:draws] end def get_losses team @results[team][:losses] end def get_points team 3*@results[team][:wins] + @results[team][:draws] end end 
Collapse
 
jehielmartinez profile image
Jehiel Martinez

Javascript:

function LeagueTable() { this.matches = [] }; LeagueTable.prototype.push = function (matchStr){ const teams = matchStr.split(' - '); const homeTeam = teams[0].slice(0,-2); const awayTeam = teams[1].slice(2); const homeGoals = parseInt(teams[0].slice(-2)); const awayGoals = parseInt(teams[1].slice(0, 2)); let homeScore = 0; let awayScore = 0; if(homeGoals == awayGoals) { homeScore = 1; awayScore = 1; } else if(homeGoals > awayGoals){ homeScore = 3; } else { awayScore = 0; } const match = { homeTeam, awayTeam, homeGoals, awayGoals, homeScore, awayScore, }; this.matches.push(match); return JSON.stringify(match); }; LeagueTable.prototype.get_points = function (teamName){ points = 0; this.matches.forEach(match => { if(match.homeTeam === teamName){ points += match.homeScore; } else if(match.awayTeam === teamName){ points += match.awayScore; } }) return points; }; LeagueTable.prototype.get_goals_for = function (teamName){ goals = 0; this.matches.forEach(match => { if(match.homeTeam === teamName){ goals += match.homeGoals; } else if(match.awayTeam === teamName){ goals += match.awayGoals; } }); return goals; }; LeagueTable.prototype.get_goals_against = function (teamName){ goals = 0; this.matches.forEach(match => { if(match.homeTeam === teamName){ goals += match.awayGoals; } else if(match.awayTeam === teamName){ goals += match.homeGoals; } }); return goals; }; LeagueTable.prototype.get_goals_difference = function (teamName){ diff = 0; this.matches.forEach(match => { if(match.homeTeam === teamName){ diff += (match.homeGoals - match.awayGoals); } else if(match.awayTeam === teamName){ diff += (match.awayGoals - match.homeGoals);; } }); return diff; }; LeagueTable.prototype.get_wins = function (teamName){ wins = 0; this.matches.forEach(match => { if(match.homeTeam === teamName && match.homeScore === 3){ wins++ } else if(match.awayTeam === teamName && match.awayScore === 3){ wins++ } }); return wins; }; LeagueTable.prototype.get_draws = function (teamName){ draws = 0; this.matches.forEach(match => { if(match.homeTeam === teamName && match.homeScore === 1){ draws++ } else if(match.awayTeam === teamName && match.awayScore === 1){ draws++ } }); return draws; }; LeagueTable.prototype.get_losses = function (teamName){ losses = 0; this.matches.forEach(match => { if(match.homeTeam === teamName && match.homeScore === 0){ losses++ } else if(match.awayTeam === teamName && match.awayScore === 0){ losses++ } }); return losses; };