DEV Community

Daily Challenge #121 - Who has the most money?

dev.to staff on November 20, 2019

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: cla...
Collapse
 
rafaacioly profile image
Rafael Acioly • Edited

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
Collapse
 
hectorpascual profile image
Héctor Pascual

Hi, check this decorator :

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

Will make you avoid writing some functions.

Collapse
 
rafaacioly profile image
Rafael Acioly

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

Collapse
 
peterbunting profile image
Peter Bunting • Edited

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.

Collapse
 
jbristow profile image
Jon Bristow • Edited
    ```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?)

Collapse
 
peterbunting profile image
Peter Bunting

Thank you

Collapse
 
dak425 profile image
Donald Feury • Edited

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)
    }
}
Collapse
 
erezwanderman profile image
erezwanderman

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);
Collapse
 
peterbunting profile image
Peter Bunting

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.

Collapse
 
jbristow profile image
Jon Bristow • Edited

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.

Collapse
 
jbristow profile image
Jon Bristow

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"
    }
}

Collapse
 
seniru profile image
Seniru Pasan Indira

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])

Collapse
 
logycon profile image
Igor

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)
Collapse
 
peter279k profile image
peter279k

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


Collapse
 
hectorpascual profile image
Héctor Pascual

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)
Collapse
 
peterbunting profile image
Peter Bunting

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.