loading...

Daily Challenge #121 - Who has the most money?

thepracticaldev profile image dev.to staff ・1 min read

You're going on a trip with some students and it's up to you to keep track of how much money each Student has. A student is defined like this:

class Student:
def __init__(self, name, fives, tens, twenties):
self.name = name
self.fives = fives
self.tens = tens
self.twenties = twenties

As you can tell, each Student has some fives, tens, and twenties. Your job is to return the name of the student with the most money. If every student has the same amount, then return "all".

Notes:

  • Each student will have a unique name
  • There will always be a clear winner: either one person has the most, or everyone has the same amount
  • If there is only one student, then that student has the most money

This challenge comes from MrAppa 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!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

markdown guide
 

F#, also handles empty lists (returning "none") or lists where several but not all students have the same max amount (returning their names comma separated):

module DailyChallenge

type Student =
    { Name : string
      Fives : int
      Tens : int
      Twenties : int }

let totalAmount student =
    student.Fives * 5 + student.Tens * 10 + student.Twenties * 20

let richest (students : Student list) : string =
    match students.Length with
    | 0 -> "none"
    | 1 -> students.[0].Name
    | _ ->
        students
        |> List.groupBy totalAmount
        |> List.maxBy fst
        |> function
        | (_, l) when l.Length = students.Length -> "all"
        | (_, l) -> List.map (fun s -> s.Name) l |> String.concat ", "

Tests:

module DailyChallengeTests

open FsUnit.Xunit
open Xunit
open DailyChallenge

[<Fact>]
let ``no students``() =
    []
    |> richest
    |> should equal "none"

[<Fact>]
let ``one student``() =
    [ { Name = "A"
        Fives = 1
        Tens = 1
        Twenties = 1 } ]
    |> richest
    |> should equal "A"

[<Fact>]
let ``one student has max amount``() =
    let a =
        { Name = "A"
          Fives = 1
          Tens = 1
          Twenties = 1 }

    let b =
        { Name = "B"
          Fives = 2
          Tens = 1
          Twenties = 1 }

    let c =
        { Name = "C"
          Fives = 3
          Tens = 1
          Twenties = 1 }

    [ a; b; c ]
    |> richest
    |> should equal "C"

[<Fact>]
let ``two out of 3 students have max amount``() =
    let a =
        { Name = "A"
          Fives = 1
          Tens = 1
          Twenties = 1 }

    let b =
        { Name = "B"
          Fives = 2
          Tens = 1
          Twenties = 1 }

    let c =
        { Name = "C"
          Fives = 2
          Tens = 1
          Twenties = 1 }

    [ a; b; c ]
    |> richest
    |> should equal "B, C"

[<Fact>]
let ``all students have max amount``() =
    let a =
        { Name = "A"
          Fives = 1
          Tens = 1
          Twenties = 1 }

    let b =
        { Name = "B"
          Fives = 1
          Tens = 1
          Twenties = 1 }

    let c =
        { Name = "C"
          Fives = 1
          Tens = 1
          Twenties = 1 }

    [ a; b; c ]
    |> richest
    |> should equal "all"

 

Wow. Very good. I was nowhere near getting this. I need to spend some time working through your solution.

Thank you - it is very instructional. I really like how you show your tests as well. I haven't quite got my head around setting up tests in F# yet.

 

Let me try to help you a little bit:

  • I think the Student type and totalAmount function are clear.
  • The outermost match in richest directly matches on students.Length (a property of the List type) and handles the 0 and 1 special cases:
match students.Length with
    | 0 -> // return "none"
    | 1 -> // directly return the only student's name
    | _ -> // code for all lists with 2 or more students

Now the code that handles the general case looks like this, a typical functional pipeline:

students
|> List.groupBy totalAmount
|> List.maxBy fst
|> function
| (_, l) when l.Length = students.Length -> "all"
| (_, l) -> List.map (fun s -> s.Name) l |> String.concat ", "    

In the first step, we group our list by the result of applying totalAmount to each list member. This could also be written slightly longer as:

List.groupBy (fun s -> totalAmount s)

The result of this will be a list of tuples of the form (totalValue, list of students with this value):

  [(35, [{ Name = "A"
           Fives = 1
           Tens = 1
           Twenties = 1 }]); 
   (40, [{ Name = "B"
           Fives = 2
           Tens = 1
           Twenties = 1 }]);
   (45, [{ Name = "C"
           Fives = 3
           Tens = 1
           Twenties = 1 }])]

Next we pick the tuple with the highest totalValue, which is the first tuple element and can be accessed by fst (sligthly inefficient to do this in two steps, but the inputs are probably small enough to get away with and it's IMHO more readable). Once again this is the compact form of writing something like:

List.maxBy (fun tup -> fst tup)
// or using pattern matching
List.maxBy (fun (total, _) -> total)

We then pipe into function, which is essentially just a different way to do pattern matching. Roughly:

x 
|> function
| first case -> ...
| second case -> ...

// equivalent to
match x with
| first case -> ...
| second case -> ...

The match cases use patterns on the tuple, but always ignore the first element (via _) and only focus on the list of students, l. Alternatively we could have added another pipeline step to only pass through the second tuple element:

students
|> List.groupBy totalAmount
|> List.maxBy fst
|> snd
|> function
| l when l.Length = students.Length -> "all"
| _ -> List.map (fun s -> s.Name) l |> String.concat ", "

The match case (_, l) when l.Length = students.Length matches only when the length of l is the same as the one of students, which means everyone has the same amount of money so we return "all". Otherwise we map over the list to extract the names (s.Name) and join them with commas via String.concat. There's no checking of array length necessary here, in the case of s.Length = 1 there's simply nothing to concat and a single string will be returned.

 

My first attempt at F#

type Student(name,fives,tens,twenties) =
    member this.Name: string = name
    member this.Fives: int = fives
    member this.Tens: int = tens
    member this.Twenties: int = twenties
    member this.getStudentAmount  with get () = (this.Fives * 5) + (this.Tens * 10) + (this.Twenties * 20)


let a = new Student("a",1,1,1)
let b = new Student("b",1,1,1)
let c = new Student("c",1,1,1)

let students = [|a;b;c|] |> Array.sortByDescending(fun x -> x.getStudentAmount)

match students with
|_ when students.Length = 1 -> students.[0].Name
|_ when students.[0].getStudentAmount = students.[1].getStudentAmount -> "All"
|_ -> students.[0].Name

I couldn't get the syntax highlighting for F# so if anyone can point me in the right direction that would be much appreciated.

 

Generally we tend not to reach for classes in F# unless there's a good reason to do so, e.g. .NET interop or a problem that's really much better suited to OOP than FP. A record type and a simple function will do just fine here:

type Student =
    { Name : string
      Fives : int
      Tens : int
      Twenties : int }

let totalAmount student =
    student.Fives * 5 + student.Tens * 10 + student.Twenties * 20
 

Thank you Michael. I have been following your F# posts in this series and found them very helpful.

I thought about using a record type but thought it would be easier to use a class property for the match function.

If you get chance please could you show how you would approach the full solution.

Many thanks.

Thanks, glad to hear that you find my posts useful. 😊

I posted a solution to this problem on this thread already, let me know if you have any questions.

 
    ```fsharp
    match students with
    |_ when students.Length = 1 -> students.[0].Name
    |_ when students.[0].getStudentAmount = students.[1].getStudentAmount -> "All"
    |_ -> students.[0].Name
    ```

renders as

    match students with
    |_ when students.Length = 1 -> students.[0].Name
    |_ when students.[0].getStudentAmount = students.[1].getStudentAmount -> "All"
    |_ -> students.[0].Name

(HEY, dev.to maintainers, this is one of those "won't fix bugs!" 😠 Why can't I nest backticks as in the github flavored markdown spec?)

 
 

there is nothing more beautiful than Python

from typing import List, Union
from dataclasses import dataclass


@dataclass
class Student:

  name: str
  fives: int = 0
  tens: int = 0
  twenties: int = 0

  def __str__(self):
    return self.name

  def __eq__(self, value):
    return self._amount() == value

  def __gt__(self, value):
    return self._amount() > value

  def __lt__(self, value):
    return self._amount() < value

  def __hash__(self):
    return self._amount()

  def _amount(self):
    return sum((
      self.fives * 5,
      self.tens * 10,
      self.twenties * 20
    ))

def most_amount(students: List[Student]) -> str:
  uniques = set(students)
  return str(max(uniques)) if len(uniques) >= 2 else "all"

Usage:


student_1 = Student(name="richard", fives=25, tens=5, twenties=5)
student_2 = Student(name="helen", fives=55, tens=5, twenties=5)

print(most_amount([student_1, student_2]))  # helen
 

Hi, check this decorator :

docs.python.org/3/library/functool...

Will make you avoid writing some functions.

 

Thank you Héctor i didn't know about this.

I've tried to apply it but it looks is a little shady for me, i didn't quite understand what is does and how it "magically" does the comparison

 

I put myself in here as a test case as a joke because I didn't get an allowance as a kid 😂

education.go

package education

type Trip struct {
    Students []Student
}

func (t Trip) Richest() string {
    sCount := len(t.Students)
    if sCount == 0 {
        return "none"
    }
    if sCount == 1 {
        return t.Students[0].Name
    }
    poorest, richest := t.Students[0], t.Students[0]
    for i := 1; i < sCount; i++ {
        if t.Students[i].Funds() < poorest.Funds() {
            poorest = t.Students[i]
        } else if t.Students[i].Funds() > richest.Funds() {
            richest = t.Students[i]
        }
    }
    if poorest.Funds() == richest.Funds() {
        return "all"
    }
    return richest.Name
}

type Student struct {
    Name     string
    Fives    int
    Tens     int
    Twenties int
}

func (s Student) Funds() int {
    return (s.Fives * 5) + (s.Tens * 10) + (s.Twenties * 20)
}

education_test.go

package education

import "testing"

type baseTestCase struct {
    description string
}

var bob = Student{"Bob", 4, 0, 0}
var mary = Student{"Mary", 0, 2, 0}
var john = Student{"John", 0, 0, 1}
var goat = Student{"Goat", 0, 0, 0}
var richie = Student{"Richie", 10, 20, 100}

func TestTrip_Richest(t *testing.T) {
    testCases := []struct {
        baseTestCase
        input    Trip
        expected string
    }{
        {
            baseTestCase{"no students"},
            Trip{},
            "none",
        },
        {
            baseTestCase{"one student"},
            Trip{[]Student{mary}},
            mary.Name,
        },
        {
            baseTestCase{"many students with same amount of funds"},
            Trip{[]Student{mary, bob, john}},
            "all",
        },
        {
            baseTestCase{"many students with one having more"},
            Trip{[]Student{mary, bob, goat, richie}},
            richie.Name,
        },
    }
    for _, test := range testCases {
        if result := test.input.Richest(); result != test.expected {
            t.Fatalf("FAIL: %s - %+v.Funds(): '%s' - expected: '%s'", test.baseTestCase.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s", test.baseTestCase.description)
    }
}

func TestStudent_Funds(t *testing.T) {
    testCases := []struct {
        baseTestCase
        input    Student
        expected int
    }{
        {
            baseTestCase{"no moniez"},
            goat,
            0,
        },
        {
            baseTestCase{"only fives"},
            bob,
            20,
        },
        {
            baseTestCase{"only tens"},
            mary,
            20,
        },
        {
            baseTestCase{"only twenties"},
            john,
            20,
        },
        {
            baseTestCase{"all the moniez"},
            richie,
            2250,
        },
    }
    for _, test := range testCases {
        if result := test.input.Funds(); result != test.expected {
            t.Fatalf("FAIL: %s - %+v.Funds(): %d - expected: %d", test.baseTestCase.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s", test.baseTestCase.description)
    }
}
 

Javascript:

class Student {
  constructor(name, fives, tens, twenties) {
    this.name = name;
    this.fives = fives;
    this.tens = tens;
    this.twenties = twenties;
  }
}

const mostMoney = (students) => {
  const studentsMoney = (student) => student.fives * 5 + student.tens * 10 + student.twenties * 20;
  if (students.length === 1) {
    return students[0].name;
  }
  if (students.length > 1) {
    const firstStudentMoney = studentsMoney(students[0]);
    const secondStudentMoney = studentsMoney(students[1]);
    if (firstStudentMoney === secondStudentMoney) {
      return 'all';
    }
    let [maxName, maxMoney] = firstStudentMoney > secondStudentMoney ? [students[0].name, firstStudentMoney] : [students[1].name, secondStudentMoney];
    for (let i = 2; i < students.length; i++) {
      const money = studentsMoney(students[i]);
      if (money > maxMoney) {
        [maxName, maxMoney] = [students[i].name, money];
      }
    }
    return maxName;
  }
}

Usage:

const x = [
  new Student('John1', 2, 0, 1),
  new Student('John2', 5, 1, 0),
  new Student('John3', 40, 1, 0),
];
appDiv.innerText = mostMoney(x);
 

Ridiculously over-engineered Kotlin version:

data class Student(val name: String, val fives: Int, val tens: Int, val twenties: Int)

private val Student.total: Int
    get() = fives * 5 + tens * 10 + twenties * 20

sealed class StudentAccumulator {
    abstract val money: Int
}

object Empty : StudentAccumulator() {
    override val money = 0
}

data class ErrorFound(val message: String) : StudentAccumulator() {
    override val money = 0
}

data class MaxFound(val name: String, override val money: Int) : StudentAccumulator()
data class TieFound(override val money: Int) : StudentAccumulator()

fun generateStudentFolder(compareFn: (StudentAccumulator, Student) -> Int) = { acc: StudentAccumulator, student: Student ->
    when (acc) {
        is ErrorFound -> acc
        is Empty -> MaxFound(student.name, student.total)
        is MaxFound -> acc.consider(student, compareFn)
        is TieFound -> acc.consider(student, compareFn)
    }
}

fun MaxFound.consider(student: Student, compareFn: (StudentAccumulator, Student) -> Int) = when (compareFn(this, student)) {
    -1 -> MaxFound(student.name, student.total)
    0 -> TieFound(student.total)
    else -> this
}

fun TieFound.consider(student: Student, compareFn: (StudentAccumulator, Student) -> Int) = when (compareFn(this, student)) {
    -1 -> MaxFound(student.name, student.total)
    0 -> ErrorFound("More than one maximum")
    else -> this
}

fun Iterable<Student>.findMax() =
    fold(
        Empty as StudentAccumulator,
        generateStudentFolder { a, b -> a.money.compareTo(b.total) }
    )

fun Iterable<Student>.findMaxName(): String {
    return when (val result = findMax()) {
        is ErrorFound -> throw Error(result.message)
        is Empty -> throw Error("No max found")
        is MaxFound -> result.name
        is TieFound -> "all"
    }
}

fun main() {
    val studentsA = listOf(Student("a", 1, 1, 1))
    println(studentsA.findMaxName())
    val studentsB = listOf(
        Student("a", 1, 1, 1),
        Student("b", 1, 1, 1)
    )
    println(studentsB.findMaxName())
    val studentsC = listOf(
        Student("a", 1, 1, 1),
        Student("c", 2, 1, 1),
        Student("b", 1, 1, 1)
    )
    println(studentsC.findMaxName())
}

It's typesafe, and we could change the comparator out if we wanted to. A little kludgey in the fold section (I should probably involve Option to avoid people accidentally calling money on the Empty and Error types. 🤷‍♀️

While I love the sealed class idea in Kotlin, type erasure and lack of true pattern matching really hampers the readability. If I was able to rely on StudentAccumulator auto-casting to its internal types, then I could remove a when statement that just dispatches out to the same overloaded call with the proper type.

 

Ok, I fixed it. It's not too much uglier with proper Option protection...

import arrow.core.Option
import arrow.core.getOrElse

data class Student(val name: String, val fives: Int, val tens: Int, val twenties: Int)

private val Student.total: Int
    get() = fives * 5 + tens * 10 + twenties * 20

sealed class StudentAccumulator {
    abstract val money: Option<Int>
}

object Empty : StudentAccumulator() {
    override val money = Option.empty<Int>()
}

data class ErrorFound(val message: String) : StudentAccumulator() {
    override val money = Option.empty<Int>()
}

data class MaxFound(val name: String, override val money: Option<Int>) : StudentAccumulator() {
    constructor(student: Student) : this(student.name, Option.just(student.total))
}

data class TieFound(override val money: Option<Int>) : StudentAccumulator() {
    constructor(student: Student) : this(Option.just(student.total))
}

fun generateStudentFolder(compareFn: (StudentAccumulator, Student) -> Option<Int>) =
    { acc: StudentAccumulator, student: Student ->
        when (acc) {
            is ErrorFound -> acc
            is Empty -> MaxFound(student)
            is MaxFound -> acc.consider(student, compareFn)
            is TieFound -> acc.consider(student, compareFn)
        }
    }

fun MaxFound.consider(student: Student, compareFn: (StudentAccumulator, Student) -> Option<Int>) =
    compareFn(this, student).map { result ->
        when (result) {
            -1 -> MaxFound(student)
            0 -> TieFound(student)
            else -> this
        }
    }.getOrElse { ErrorFound("Bad comparison.") }

fun TieFound.consider(student: Student, compareFn: (StudentAccumulator, Student) -> Option<Int>) =
    compareFn(this, student).map { result ->
        when (result) {
            -1 -> MaxFound(student)
            0 -> ErrorFound("More than one maximum")
            else -> this
        }
    }.getOrElse { ErrorFound("Bad comparison.") }

fun Iterable<Student>.findMax() =
    fold(
        Empty as StudentAccumulator,
        generateStudentFolder { a, b ->
            a.money.map { it.compareTo(b.total) }

        }
    )

fun Iterable<Student>.findMaxName(): String {
    return when (val result = findMax()) {
        is ErrorFound -> throw Error(result.message)
        is Empty -> throw Error("No max found")
        is MaxFound -> result.name
        is TieFound -> "all"
    }
}

 

My try with python

import random as rand

class Student:
    def __init__(self, name, fives, tens, twenties):
        self.name = name
        self.fives = fives
        self.tens = tens
        self.twenties = twenties

    def getMoney(self):
        return self.fives * 5 + self.tens * 10 + self.twenties

    def __str__(self):
        return self.name + " " + str(self.getMoney())

#Creating a list of students       
students = sorted([Student('Student' + str(s), rand.randint(0, 10), rand.randint(0, 10), rand.randint(0, 10)) for s in range(20) ], key=lambda s: s.getMoney(), reverse=True)

for s in students:
    print(s)

print('Student with highest amount:', end=' ')

if len(students) == 1:
    print(students[1])
elif students[0] == students[::-1]:
    print('All')
else:
    print(students[0])

 

In Scala -

case class Student(name : String, fives: Int, tens: Int, twenties: Int) {
  override def toString = s"${name} -> ${worth}"
  def worth: Int = 5 * fives + 10 * tens + 20 * twenties
}

val students = List(
  Student("1", 5, 1, 4),
  Student("2", 5, 1, 4),
  Student("3", 5, 1, 4)
)

val winner = students.maxBy(f => f.worth) 
val winners = (winner :: students.filter(f => f.worth == winner.worth)).distinct

if (winners.size == students.size) 
  println("all")
else 
  println(winner)
 

Here is the Python code snippets:

def most_money(students):
    student_name = ''
    sum_student = 0
    check_all = 0
    for student in students:
        current_sum = student.fives * 5 + student.tens * 10 + student.twenties * 20
        if current_sum > sum_student:
            sum_student = current_sum
            student_name = student.name
        elif current_sum == sum_student:
            if check_all == 0:
                check_all += 2
            else:
                check_all += 1

    if check_all == len(students):
        return 'all'

    return student_name


 

My Python attempt :

Using total_ordering decorator from func_tools, which only requires two implementations of the comparison methods.

from functools import total_ordering

@total_ordering
class Student:

  def __init__(self, name, fives, tens, twenties):
    self.name = name
    self.fives = fives
    self.tens = tens
    self.twenties = twenties
    # Added new property
    self.money = self.fives*5 + self.tens*10 + self.twenties*20

  def __eq__(self, other):
    return self.money == other.money
  def __lt__(self, other):
    return self.money < other.money

st1 = Student('Héctor',1,7,3)
st2 = Student('Albert',5,4,3)
st3 = Student('Jordi',1,4,3)

students = [st1, st2, st3]

print(max(students).name)