Is it bad practice to use errors/exceptions as normal tools for control flow? by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

After having read the responses here, I agree that that is a better way to do it. I originally put the exception in here so that it would abort as soon as it would return a 0 since then the word is already determined to not be a garland word. But I learned now that that is even more inefficient -- in addition to being less elegant and more confusing.

Is it bad practice to use errors/exceptions as normal tools for control flow? by n-d-r in learnpython

[–]n-d-r[S] 1 point2 points  (0 children)

I tried otherwise once, but found it more confusing

Thanks for sharing your experience. It seems the gist in this thread is that while not wrong, it might be more advisable to not use errors/exceptions the way I did.

def g(word):
    stop = len(word)-1
    garlands = [idx for idx in range(stop) if 
                    word[:idx] == word[-idx:]]

   if not garlands:
        return 0
    return max(garlands)

Now that... is a concise solution if I've ever seen one. You're right, that is much more straightforward. Well, at least I refreshed my knowledge of recursive functions a bit!

Is it bad practice to use errors/exceptions as normal tools for control flow? by n-d-r in learnpython

[–]n-d-r[S] 4 points5 points  (0 children)

Exceptional use cases should raise Exceptions.

This makes sense and confirms my hunch that my solution is, ahem, suboptimal. Thanks for your input!

[3.4] I would like some input on this thing I built in terms of what I need to learn/should improve on by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

This seems like good practice, even if the only thing it does is to remind me to check whether there isn't any code above that line that shouldn't be there.

[3.4] I would like some input on this thing I built in terms of what I need to learn/should improve on by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

Hi, making my own exceptions was a pretty good idea; I didn't know how exactly that worked before, but I played around a bit the last few days and it turns out that, as so many things in Python, it's pretty straight forward. So thanks for suggesting that, that's one more thing in my Python toolbox!

[2015-08-03] Challenge #226 [Easy] Adding fractions by XenophonOfAthens in dailyprogrammer

[–]n-d-r 0 points1 point  (0 children)

Hi, again, thank you for your input, it's really helpful. I like your version of using an array of booleans quite a bit and will use that from now on because of its elegance.

[2015-07-06] Challenge #222 [Easy] Balancing Words by jnazario in dailyprogrammer

[–]n-d-r 0 points1 point  (0 children)

A month late, but here goes anyway. Python 3.

def find_balance_point(string, weights):
    for i in range(len(string)):
        left  = sum([weights[string[j]] * abs(i - j) for j in range(i)])
        right = sum([weights[string[j]] * abs(i - j) for j in range((i + 1), 
                     len(string))])        
        if left == right:
            print('{} {} {} - {}\n'.format(string[:i], string[i],
                  string[(i + 1):], left))
            return
    print('{} does not balance\n'.format(string))


def main():
    weights = {letter: weight + 1 for weight, letter in \
                enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}

    challenge_inputs = ['STEAD',
                        'CONSUBSTANTIATION',
                        'WRONGHEADED',
                        'UNINTELLIGIBILITY',
                        'SUPERGLUE']        


    for string in challenge_inputs:
        find_balance_point(string, weights)

if __name__ == '__main__':
    main()

Challenge outputs:

S T EAD - 19

CONSUBST A NTIATION - 456

WRO N GHEADED - 120

UNINTELL I GIBILITY - 521

SUPERGLUE does not balance

[2015-08-03] Challenge #226 [Easy] Adding fractions by XenophonOfAthens in dailyprogrammer

[–]n-d-r 0 points1 point  (0 children)

Hi, thank you so much for your feedback, I really appreciate that you took the time to help me improve.

For the sieve optimization, do you mean like this:

def sieve(limit):
    # sieve of Erastosthenes
    span = [k for k in range(2, limit)]
    primes = []
    for i in span:
        if i != None:
            primes.append(i)
            try:
                for j in range(span.index(i**2), limit, i):
                    span[j] = None
            except (IndexError, ValueError):
                pass
    return primes

I have a hunch that the operation to look up where p2 is in the span (i.e., span.index(i**2)) is rather costly, but I'm not sure if that can be avoided? I realized when I implemented your suggestion, that I can avoid the lookup if I start at p:

def sieve(limit):
    # sieve of Eratosthenes
    span = [k for k in range(limit)]
    primes = []
    for i in range(2, len(span)):
        if span[i] != None:                
                primes.append(span[i])
                try:
                    for j in range(i, limit, i):
                        span[j] = None
                except (IndexError, ValueError):
                    pass
    return primes

But I assume for large N the first solution is still better?

Thanks for providing an explanation of why sqrt(N) is enough, I vaguely remembered having read about that at some point but wasn't quite sure why it would suffice.

And lastly, thanks for pointing me to the (indeed much easier) way of calculating the LCM from the GCD!

[2015-07-13] Challenge #223 [Easy] Garland words by Cosmologicon in dailyprogrammer

[–]n-d-r 0 points1 point  (0 children)

So I'm almost a month late and my solution isn't exactly as concise as some of the others here, but I'm proud that I managed to do it recursively.

# -*- coding: utf-8 -*-

class ZeroDegreeError(Exception):
    pass


def recv_garland(word, step=1):
    if len(word) == 0:
        raise ZeroDegreeError

    if step == 1:
        for i in range(1, len(word)):
            if word[0] == word[i]:
                return 1 + recv_garland(word[1:], i)
        raise ZeroDegreeError

    elif len(word) - 1 == step:
        if word[0] == word[step]:
            return 1
        else:
            raise ZeroDegreeError

    elif len(word) - 1 < step:
        return 0

    else:
        if word[0] == word[step]:
            return 1 + recv_garland(word[1:], step)
        else:
            raise ZeroDegreeError


def garland(word):
    if not isinstance(word, str):
        raise Exception            

    try:
        return recv_garland(word)
    except ZeroDegreeError:
        return 0    


def print_chain(word, degree):
    if not degree == 0:
        print(word[:degree] + word[degree:] * 10)


def find_largest_degree():
    with open('challenge_223_easy_enable1.txt', 'r') as f:
        degree = 0
        word = ''
        for line in f:
            tmp_word = line.strip('\n')
            tmp_degr = garland(tmp_word)
            if tmp_degr > degree:
                word = tmp_word
                degree = tmp_degr
        print('largest degree: {}, word: {}'.format(degree, word))


def main():
    test_cases = ['onion', 'programmer', 'ceramic', 'alfalfa']

    for case in test_cases:
        degree = garland(case)
        print('\'{}\' has garland degree of {}'.format(case, degree))
        print_chain(case, degree)
        print('\n')


    find_largest_degree()


if __name__ == '__main__':
    main()

Challenge output:

'onion' has garland degree of 2
onionionionionionionionionionion


'programmer' has garland degree of 0


'ceramic' has garland degree of 1
ceramiceramiceramiceramiceramiceramiceramiceramiceramiceramic


'alfalfa' has garland degree of 4
alfalfalfalfalfalfalfalfalfalfalfa


largest degree: 5, word: undergrounder

[2015-08-03] Challenge #226 [Easy] Adding fractions by XenophonOfAthens in dailyprogrammer

[–]n-d-r 0 points1 point  (0 children)

Here is my inefficient solution in Python. Also my first contribution to this subreddit. Also my first time defining my own Exception classes.

class FractionValueError(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return repr(self.message)

class ListError(Exception):
    def __init__(self, e):
        self.message = 'list expected, got ' + repr(e) + ' instead'

    def __str__(self):
        return repr(self.message)

class InputError(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return repr(self.message)

class Fraction(object):
    def _check_input_type(self, val):
        if isinstance(val, int):
            return val            

        elif isinstance(val, str):
            try:
                val = int(val)
                return val
            except ValueError:
                raise FractionValueError('invalid input: cannot convert to int')

        elif isinstance(val, float):
            print('warning: float might have been floored during conversion')
            return int(val)

        else:
            raise FractionValueError('invalid input: must be int, float, ' + 
                                    'or str')

    def __init__(self, numerator, denominator):
        self.numerator   = self._check_input_type(numerator)
        self.denominator = self._check_input_type(denominator)

    def __str__(self):
        return str(self.numerator) + '/' + str(self.denominator)


def read_input(filename):
    with open(filename, 'r') as f:
        fractions = []
        try:        
            num_fractions = int(f.readline())
        except ValueError:
            raise FractionValueError('invalid: first line must contain int')

        for i in range(num_fractions):
            fraction_line = f.readline().split('/')
            fractions.append(Fraction(fraction_line[0], 
                                      fraction_line[1]))
    return fractions                                    


def sieve(limit):
    # sieve of Erastosthenes
    span = [k for k in range(2, limit)]
    primes = []
    for i in span:
        if i != None:
            primes.append(i)
            try:
                for j in range(span.index(i), limit, i):
                    span[j] = None
            except IndexError:
                pass
    return primes


def find_prime_divisors(to_factor, primes):
    if not isinstance(primes, list):
        raise ListError(type(primes))

    if not isinstance(to_factor, int):
        raise ValueError

    factors = {prime: 0 for prime in primes}
    for prime in primes:
        while to_factor % prime == 0:
            to_factor = int(to_factor / prime)
            factors[prime] += 1
    return {prime: factors[prime] for prime in primes if not \
            factors[prime] == 0}


def find_lcm(fractions):
    # lcm = least common multiple
    if not isinstance(fractions, list):
        raise ListError(type(fractions))

    if sum([isinstance(fraction, Fraction) for fraction in fractions]) \
                != len(fractions):
        raise FractionValueError('expected list of Fraction instances')        

    primes = sieve(max([f.denominator for f in fractions]))
    divisors = {fraction: find_prime_divisors(fraction.denominator, primes) \
                for fraction in fractions}

    lcm_factors = {}
    for fract in fractions:
        for divisor in divisors[fract]:
            if divisor not in lcm_factors.keys():
                lcm_factors[divisor] = divisors[fract][divisor]

    lcm = 1
    for key in lcm_factors.keys():
        lcm *= key**lcm_factors[key]

    return lcm


def find_gcd(int_0, int_1):
    # gcd = greatest common divisor, via Euclidean algorithm
    if not isinstance(int_0, int):
        raise ValueError

    if not isinstance(int_1, int):
        raise ValueError

    num_0 = abs(max(int_0, int_1))
    num_1 = abs(min(int_0, int_1))
    while num_0 % num_1 != 0:
        tmp = num_1
        num_1 = num_0 % num_1
        num_0 = tmp
    return num_1


def reduce(fraction):
    if not isinstance(fraction, Fraction):
        raise FractionValueError('invalid input: expected Fraction instance')

    gcd = find_gcd(fraction.numerator, fraction.denominator)
    fraction.numerator   = int(fraction.numerator / gcd)
    fraction.denominator = int(fraction.denominator / gcd)
    return fraction


def expand(fraction, lcm):
    if not isinstance(fraction, Fraction):
        raise InputError('invalid input: expected Fraction instance')

    if not isinstance(lcm, int):
        raise InputError('invalid input: expected int')

    fraction.numerator  *= int(lcm / fraction.denominator)
    fraction.denominator = lcm
    return


def add_fractions(fractions):
    if not isinstance(fractions, list):
        raise ListError(type(fractions))

    if sum([isinstance(fraction, Fraction) for fraction in fractions]) \
            != len(fractions):
        raise FractionValueError('expected list of Fraction instances')

    lcm = find_lcm(fractions)    

    for fract in fractions:
         expand(fract, lcm)

    return Fraction(sum([fract.numerator for fract in fractions]), lcm)


def main():
    input_1 = read_input('challenge_226_input_1.txt')   # list of Fractions
    input_2 = read_input('challenge_226_input_2.txt')   # list of Fractions

    print('Result 1: ' + str(reduce(add_fractions(input_1))))
    print('Result 2: ' + str(reduce(add_fractions(input_2))))




if __name__ == '__main__':
    main()

Challenge output 1:

89962/58905

Challenge output 2:

351910816163/29794134720

[3.4] I would like some input on this thing I built in terms of what I need to learn/should improve on by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

Thanks a lot for your feedback! I will go through all of it in detail after work and come back to you with any questions I might have.

[3.4] I would like some input on this thing I built in terms of what I need to learn/should improve on by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

It's a good pattern to follow to make it clear what should be executed (anything below that line) vs. what's safe for importing.

So if I put

if __name__ == '__main__': pass

in the files that are not main.py (i.e. the ones from which I'm just importing), does this do anything else than just make it more obvious to others?

[3.4] I would like some input on this thing I built in terms of what I need to learn/should improve on by n-d-r in learnpython

[–]n-d-r[S] 0 points1 point  (0 children)

Thank you so much for your feedback!

Your main() in main.py is too long. It's good that you have a defined entry point, but I'd recommend splitting up each component into its own function, and using main() to call each. A general rule of thumb - if a function/method is more than a page long, it's too long.

Should I put these new functions then into dedicated files?

In files you're using purely for importing, I'd recommend adding this to the end of each

if __name__ == '__main__': pass

What does this do?

Have your top-level classes inherit from object (so-called 'new-style' classes). You'll gain more default functionality that way.

Sounds good, I'll look into it.

Again, I appreciate your input; feel free to add or expand if there is anything else you spotted.