Don't Give me five challenge -- Swift

Viewed 1788

I am really struggling with this kata on codewars.com. I am a complete beginner so i don't want a solution but if some1 could just push me in the right direction.

"In this kata you get the start number and the end number of a region and should return the count of all numbers except numbers with a 5 in it. The start and the end number are both inclusive! Examples: 1,9 -> 1,2,3,4,6,7,8,9 -> Result 8
4,17 -> 4,6,7,8,9,10,11,12,13,14,16,17 -> Result 12"

So, i tried this so far, but i don't know if there is a way to search for all numbers that have 5 in them. Is there a way to add a universal character search like we have "*" online ?

func dontGiveMeFive(_ start: Int, _ end: Int) -> Int {
    var rez = 0
    var ints = [String]()

    for i in start...end {
        if ints.contains("5") {
            rez += 1
        }
    }

    return rez
}

NOTE: Solving examples is not that hard, but problem occures when you take into account some numbers that are not divisible by 5. like 51,52,53....or 425980 :)

4 Answers

There are two different approaches to making the check - using a string conversion, and using math.

An approach that uses math uses less CPU power, but the approach that uses strings is often less typing.

Using the math approach you perform these two steps until the number being checked is reduced to zero:

  • Check if the last digit is five; if yes, return true
  • Drop the last digit

To check if the last decimal digit of an Int n is five use n % 10 == 5 expression. Operator % returns a remainder after dividing n by ten, which is the numeric value of the last digit of n.

To drop the last digit use n /= 10. because integer division truncates any fractional part.

The other answers should help you figure it out yourself. Here's a one-line functional approach if that interests you:

With Strings:

func dontGiveMeFive(_ start: Int, _ end: Int) -> Int {
    return [Int](start...end).map(String.init).filter{!$0.contains("5")}.count
}

The easiest way to do this is to convert each number to a string and check whether the string contains "5" as a substring:

extension CountableClosedRange {
    var countOfFivelessElements: Int {
        var count = 0
        for i in self {
            if (!"\(i)".contains("5")) {
                count += 1
            }
        }
        return count
    }
}

(40...60).countOfFivelessElements

A math based solution could be as follows:

// First create a function that will tell you if a number contains 
// a 5.  (easier to define and implement recursively):
// A number will contain a 5 if its last digit is 5
// or if its ten's (number divided by 10) contains a 5
//
func has5(_ value:Int) -> Bool
{ return value > 0 && (value % 10 == 5 || has5(value / 10)) }

// generate the list of numbers (using a range) and 
// filter out numbers that contain a 5
func exclude5s(from:Int, to:Int) -> [Int]
{ return (from...to).filter{!has5($0)} }

// use the number generation function and count
// elements

exclude5s(from:4, to: 17).count  // --> 12

[EDIT] Another way to look at it mathematically is that numbers that don't have any 5s only use 9 digits in their representation and are actually base 9 numbers with different symbols for digits 5,6,7 and 8 (which would display as 6,7,8 and 9 respectively.

So, if we had a pair of functions to convert between the true base 9 representation and this "alternate" display form with no 5s, we could process the ranges using standard functions provided that we go through the conversion functions to do so.

For example:

// convert a base9 number to its "no 5s" representation in base 10)
func base9toNo5s(_ value:Int) -> Int
{  return value == 0 ? 0 : base9toNo5s(value/9)*10 + value % 9 + (value % 9 > 4 ? 1 : 0) }

// convert a number in "no 5s" representation to its base 9 equivalent
func no5sToBase9(_ value:Int) -> Int
{ return value == 0 ? 0 : no5sToBase9(value/10)*9 + value % 10 - ( value % 9 > 4 ? 1 : 0) }

// first 100 "no 5s" number
let first100 = (1...100).map{ base9toNo5s($0) }

// "no 5s" numbers in a range
let range = (no5sToBase9(1237)...no5sToBase9(2119)).map{ base9toNo5s($0) }
Related