DEV Community

Simon Green
Simon Green

Posted on

Weekly Challenge: Fractional Constant

Weekly Challenge 357

Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. It's a great way for us all to practice some coding.

Challenge, My solutions

Task 1: Kaprekar Constant

Task

Write a function that takes a 4-digit integer and returns how many iterations are required to reach Kaprekar's constant (6174).

My solution

This is where GitHub Copilot really scars me somewhat. Without mentioning "kaprekar" at all, and after typing while number != 6174: it auto-completed the rest of the code.

For this solution, I start by checking that the number is between 1 and 9999, and that it is not divisible by 1111. If any of these are false, I return -1.

I create a variable called count, starting at 0. If number is not 6174, I take the following steps

  1. Set digits to be the individual digits, left padded with zeros if needed.
  2. Set asc_digits to the above digits list, sorted numerically (lowest first). Set desc_digits to the same, but in reversed sorted order.
  3. Set small_num to the integer that concatenates asc_digits.
  4. Set large_num to the integer that concatenates desc_digits.
  5. Set number to be large_num minus small_num.
  6. If this is not 6174, repeat the loop again.
def kaprekar_constant(number: int) -> int:
    if not 0 < number < 10000:
        return -1

    if number % 1111 == 0:
        return -1

    count = 0
    while number != 6174:
        digits = [int(d) for d in str(number).zfill(4)]
        asc_digits = sorted(digits)
        desc_digits = sorted(digits, reverse=True)
        small_num = int(''.join(map(str, asc_digits)))
        large_num = int(''.join(map(str, desc_digits)))
        number = large_num - small_num
        count += 1

    return count
Enter fullscreen mode Exit fullscreen mode

The Perl solution is a little shorter, as I don't need to worry about finicky things like converting integers to strings :P

sub main ($int) {
    if ($int < 1 or $int > 9999 or $int % 1111 == 0) {
        say -1;
        return;
    }

    my $count = 0;
    while ($int != 6174) {
        my $small_number = join("", sort { $a <=> $b } (split //, sprintf("%04d", $int)));
        my $big_number = reverse($small_number);
        $int = $big_number - $small_number;
        $count++
    }

    say $count;
}
Enter fullscreen mode Exit fullscreen mode

Examples

$ ./ch-1.py 3524
3

$ ./ch-1.py 6174
0

$ ./ch-1.py 9998
5

$ ./ch-1.py 1001
4

$ ./ch-1.py 9000
4

$ ./ch-1.py 1111
-1
Enter fullscreen mode Exit fullscreen mode

Task 2: Unique Fraction Generator

Task

Given a positive integer n, generate all unique fractions you can create using integers from 1 to n and follow the rules below:

  • Use numbers 1 through n only (no zero)
  • Create fractions like numerator/denominator
  • List them in ascending order (from smallest to largest)
  • If two fractions have the same value (like 1/2 and 2/4), only show the one with the smallest numerator

My solution

Python has the fractions module as standard, while Perl has a CPAN module called Numbers::Fraction.

Both exhibit the same behaviors:

  • Takes the numerator (top part) and denominator (bottom part) as input variables
  • Will automatically convert to have the lowest unique values. For example, Fraction(2, 4) automatically is Fraction(1, 2).
  • Annoyingly for this task, will print a whole number if the denominator is 1.
  • Two fractions variables can be compared to be sorted correctly (by the number they represent). For example ¼ < ½.

With that in mind, this task involves having a double loop for n and d (both from 1 to number), removing the duplicates with the set function, and then sorting them numerically. Finally, I turn the Fraction object into strings, and return the list.

def unique_fraction_generator(number: int) -> list[str]:
    fractions = sorted(set(
        Fraction(n,d)
        for d in range(1, number + 1)
        for n in range(1, number+1)
    ))

    return [f"{frac.numerator}/{frac.denominator}" for frac in fractions]
Enter fullscreen mode Exit fullscreen mode

The Perl solution follows the same logic. It uses grep to check if a fraction is added previously, and pushes it to the fractions array if it is not.

use Number::Fraction;

sub main ($int) {
    my @fractions = ();
    foreach my $n (1 .. $int) {
        foreach my $d (1 .. $int) {
            my $fraction = Number::Fraction->new($n, $d);
            push @fractions, $fraction if not grep { $_ == $fraction} @fractions;
        }
    }

    my @sorted_fractions = sort { $a <=> $b } @fractions;
    say join(", ", map({ "$_->{num}/$_->{den}" } @sorted_fractions));
}
Enter fullscreen mode Exit fullscreen mode

Examples

$ ./ch-2.py 3
1/3, 1/2, 2/3, 1/1, 3/2, 2/1, 3/1

$ ./ch-2.py 4
1/4, 1/3, 1/2, 2/3, 3/4, 1/1, 4/3, 3/2, 2/1, 3/1, 4/1

$ ./ch-2.py 1
1/1

$ ./ch-2.py 6
1/6, 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 1/1, 6/5, 5/4, 4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1, 5/1, 6/1

$ ./ch-2.py 5
1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 1/1, 5/4, 4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1, 5/1
Enter fullscreen mode Exit fullscreen mode

Top comments (0)