loading...

Daily Challenge #11 - Cubic Numbers

thepracticaldev profile image dev.to staff ・2 min read

Welcome to day 11 of our Daily Challenge series, hope you're having a wonderful Monday.

In this challenge, we’ll be looking at a modified version of g964’s “Hidden ‘Cubic’ Numbers” kata on CodeWars.

Primary Objective

The first part of this challenge is to create a method that can determine whether a number is a cubic number or not. If the number has been determined to not be cubic, the output should be either null or the string "Unlucky."

Cubic numbers are numbers with at most three digits, such that the sum of the cubes of their digits is the number itself.

For example: 153 is a cubic number, because 1^3 + 5^3 + 3^3 = 153.

Secondary Objective

If you are looking for more of a challenge, you can attempt to create a function that can find cubic numbers in a string. In the output, the numbers should be returned in the order in which they are encountered in the input string.

Example:
s = “aqdf& 0 1 xyz 153 777.777" should return “0 1 153 154”
s = “QK29 45[&erui” should return “Unlucky” or null.

Note: In a string where three digits or more follow each other, the function should examine the numbers in sets of three digits starting from the left. In string “001234”, the function should see 001, then 234, evaluate each, then return any cubic numbers it found (e.g. “24172410” becomes “241”, “724”, and “10”).

Good luck, happy coding!


Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge 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

pic
Editor guide
 

Perl:

#!/usr/bin/env perl

use strict;
use warnings;
use feature qw(signatures);
no warnings "experimental::signatures";
use List::Util qw(sum0);

# Primary objective
sub is_cubic ($num) {
    return length($num) <= 3 && sum0( map { $_**3 } split //, $num ) == $num;
}

# Secondary objective
sub find_cubes ($string) {
    my @out;
    while ( $string =~ s/(\d{1,3})// ) {
        push @out, $1 if is_cubic($1);
    }

    return join " ", @out if (@out);
    return "Unlucky";
}

use Test::More tests => 5;
is( is_cubic(153), 1, "153 is cubic" );
isnt( is_cubic(152), 1, "152 is not cubic" );
is( find_cubes("aqdf& 0 1 xyz 153 777.777"), "0 1 153", "Found '0 1 153'" );
is( find_cubes("QK29 45[&erui"),
    "Unlucky", "Found no cubic numbers in 'QK29 45[&erui'" );
is( find_cubes("24172410"), "Unlucky",
    "Found no cubic numbers in '24172410'" );
is( find_cubes("2417241530"), "153 0", "Found '153 0'" );

Primary objective

In the is_cubic function, I first check if the input has at most 3 digits. If that check passes, the rest of the logical statement is executed.

Next, the input is split up into digits by splitting on the empty string. The map function goes over that list of digits and cubes each one. I make use of the sum0 aggregate function from List::Util to calculate the sum of the cubes. The sum is finally compared to the original input value.

Secondary objective

In the find_cubes function, I simply use the substitution function in Perl to find and remove 1 to 3 successive digits in the input string. The while loop will be entered as long as s was able to make a substitution. Each match is captured and saved in the special variable $1, which I use as input to the is_cubic function from the primary objective. If the is_cubic function returns a true value, the number is saved to a list.

If any values at all have been saved to the list, the list items are joined together and a space-separated string is returned. Otherwise, the string "Unlucky" is returned.

I've provided some tests to show that it works at least with the test data provided in the challenge. Regex quantifiers are greedy by default, so the engine will try to match 3 digits as much as it can and to prove this, the last checks to see that I can find two known cubes, one of which is 3 digits, in a longer digit string.

 

Primary Objective Code golf-ished:

const isCubic = number => (
                            number < 1000 && 
                            ('' + number).split('').map(
                              n => parseInt(n)
                            ).reduce(
                              (a, c) => a + c ** 3, 
                              0
                            ) === number
                          )? 1 : null;

P.S. @thepracticaldev , I think the secondary objective example 1 output is incorrect:

s = “aqdf& 0 1 xyz 153 777.777" should return “0 1 153 154”

The 154 shouldn't be there, right?

 

Seems like it was supposed to be in the input, not expected output.

 
def cubic(x):
        total = 0
        tmp = x
        while ( tmp % 10 ) > 0 :
                total += (tmp % 10) **3
                tmp = ( tmp // 10)

        return ("lucky" if (total == x) else "unlucky")
 

Wow ... That's too good ...
But how about the secondary objective ? .. .

 

Rust Answer!

Clocked in at just under 100 lines so decided I can post that as a comment 😆

pub fn is_cubic(n: u32) -> bool {
    if n >= 1000 {
        return false;
    }

    let mut sum = 0;
    let mut cur = n;

    while cur > 0 {
        sum += (cur % 10).pow(3);
        cur /= 10;
    }

    sum == n
}

fn find_next_digit(vec: &Vec<char>, start: usize) -> Option<usize> {
    let mut cur = start;
    while cur < vec.len() && !vec.get(cur).unwrap().is_digit(10) {
        cur += 1;
    }

    if cur < vec.len() {
        Some(cur)
    } else {
        None
    }
}

fn find_last_digit(vec: &Vec<char>, start: usize) -> usize {
    let mut cur = start;
    while cur < vec.len() && vec.get(cur).unwrap().is_digit(10) {
        cur += 1;
    }

    cur
}

pub fn find_cubics(s: &str) -> Vec<u32> {
    let chars: Vec<_> = s.chars().collect();
    let mut cubics = vec![];

    let mut next_digit_index = find_next_digit(&chars, 0);
    while next_digit_index.is_some() {
        let start = next_digit_index.unwrap();
        let end = find_last_digit(&chars, start);

        let num: u32 = (&s[start..end]).parse().unwrap();
        if is_cubic(num) {
            cubics.push(num);
        }

        next_digit_index = find_next_digit(&chars, end + 1);
    }

    cubics
}

pub fn all_cubics() -> Vec<u32> {
    (0..1000).filter(|x| is_cubic(*x)).collect()
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn it_returns_false_for_most_numbers() {
        assert_eq!(is_cubic(2), false);
        assert_eq!(is_cubic(22), false);
        assert_eq!(is_cubic(500), false);
    }

    #[test]
    fn it_returns_false_for_numbers_over_999() {
        assert_eq!(is_cubic(1000), false);
        assert_eq!(is_cubic(5000), false);
    }

    #[test]
    fn it_works_for_the_examples() {
        assert_eq!(is_cubic(0), true);
        assert_eq!(is_cubic(1), true);
        assert_eq!(is_cubic(153), true);
    }

    #[test]
    fn find_cubics_works_too() {
        assert_eq!(find_cubics("aqdf& 0 1 xyz 153 777.777"), vec![0, 1, 153]);
        assert_eq!(find_cubics("370&371h xyz 15 407.777"), vec![370, 371, 407]);
    }

    #[test]
    fn can_find_all_cubics() {
        assert_eq!(all_cubics(), vec![0, 1, 153, 370, 371, 407]);
    }
}
 
package utils

import (
    "fmt"
    "strconv"
)

func IsCubicNumber(number int) bool {
    stringNumber := strconv.Itoa(number)
    var result int = 0

    for _, n := range stringNumber {
        parseInt, _ := strconv.ParseInt(string(n), 0, 32)

        i := int(parseInt)
        result += calcCube(i)
        fmt.Print(i, string(n))
    }

    if result == number {
        return true
    }

    return false
}

func calcCube(number int) int {
    return number * number * number
}
 

Haskell:

import Data.Bool (bool)
import Data.List (intercalate)
import Data.List.Split (chunksOf, splitOn)
import Data.Maybe (isJust, fromJust)
import Text.Read (readMaybe)

failString :: String
failString = "Unlucky"

-- e.g. 153 becomes [1,5,3]
intoDigits :: Int -> [Int]
intoDigits 0 = []
intoDigits n = intoDigits (div n 10) ++ [mod n 10]

-- e.g [1,5,3] becomes 153
fromDigits :: [Int] -> Int
fromDigits = foldl addDigit 0
   where addDigit num d = 10 * num + d

-- Primary
-- Ideally, this'd have returned a Maybe String, but spec and all
isCubic :: Int -> String
isCubic n =
    let
        cubes = map (^3) $ intoDigits n
    in
        bool (failString) (show n) (length cubes <= 3 && (sum cubes) == n)

-- Secondary
showCubes :: String -> String
showCubes s =
    let
        maybeDigits = filter (isJust) $ map (\s -> readMaybe s :: Maybe Int) $ words s
        splitLongerThanThrees = map fromDigits $ concat $ map (chunksOf 3) $ map intoDigits $ map fromJust $ maybeDigits
        justCubes = filter (/= failString) $ map isCubic splitLongerThanThrees
    in
        bool (failString) (intercalate " " justCubes) (length justCubes > 0)

Note - splitting the long numbers up drops the zero from the success - it never even makes it to the check. I got it working either with the zero and no splitting or vice versa, and the fix for me at this point would be a special case to catch zero specifically. Which I should do, just not this second.

Edit: Problem is fixed by adding a the check when we map intoDigits over the input:

showCubes :: String -> String
showCubes s =
    let
        maybeDigits = filter (isJust) $ map (\s -> readMaybe s :: Maybe Int) $ words s
        splitLongerThanThrees = map fromDigits $ concat $ map (chunksOf 3) $ map intoDigitsCatchingZero $ map fromJust $ maybeDigits
        justCubes = filter (/= failString) $ map isCubic splitLongerThanThrees
    in
        bool (failString) (intercalate " " justCubes) (length justCubes > 0)
    where
        -- Prevents it from becoming []
        intoDigitsCatchingZero n = if n == 0 then [0] else intoDigits n

This problem happened because my implementation of intoDigits is recursive and requires a base case (0) that returns an empty list. Just gotta sidestep it in that one special instance. Could have also inserted a check in the let binding of isCubic but either way I coudln't figure out how to not have to check for it specifically. Now produces correct output, at least.

 

Ideally, this'd have returned a Maybe String, but spec and all

Spec be damned, my version just returns a bool

 

Pascal

program CubicNumber;

uses Math;

function isCubicNumber(number: integer): boolean;
var
   hundreds, tens, units, original: integer;
   result: boolean;
begin

   original := number;
   result := false;

   if (number > -1) and (number < 1000) then
   begin
      units := number mod 10;
      number := trunc(number / 10);
      tens := number mod 10;
      hundreds := trunc(number / 10);

      result := power(units, 3) + power(tens, 3) + power(hundreds, 3) = original;
   end;

   isCubicNumber := result;

end;

var
   num: integer;
begin
   num := 153;
   writeln('Is ', num, ' a cubic number? ', isCubicNumber(num));
end.

Here is a live demo on RexTester.

 
const isArmstrongNumber = (input: number) =>
  [153, 370, 371, 407].includes(input);

const display = (isArmstrongNumber: boolean) =>
  isArmstrongNumber ? undefined : "unlucky";
 
 

I managed to complete the primary objective in C#!

You can find my code here: pastebin.com/kgKzprGN