HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Derive integer lengths for given areas

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
deriveforlengthsareasgiveninteger

Problem

I want to build a function that, given an area size (target), returns a list of three lengths. The last item of the list (my_list[-1]) corresponds to the square plate.

The first two lengths in the list correspond to an orthogonal plate whose length is approximately 1.5 times larger than the one of the square plate and whose width is 1.5 times smaller. I want the lengths to be whole numbers.

The function I wrote for the task is given below, but I am sure there is a much better/faster solution. Keep in mind that when it comes to rounding floats, the bigger size for the length must be chosen since it is more conservative, but the main consideration is to not overshoot the target more than I have to.

import math

def get_plates(target):
    square = math.ceil(target ** 0.5)
    ortho1 = [math.ceil(target ** 0.5 * 1.5), math.floor(target ** 0.5 * 1.5)]
    ortho2 = [math.ceil(target ** 0.5 / 1.5), math.floor(target ** 0.5 / 1.5)]
    temp = list(zip([ortho1[0] for _ in ortho2], ortho2))
    temp.extend(list(zip([ortho1[1] for _ in ortho2], ortho2)))
    temp = [x for x in temp if x[0]*x[1] >= target]
    temp.sort(key=lambda x: abs(x[0] * x[1] - target))
    return [*temp[0], square]


For example, for a target = 700 the get_plates(700) correctly returns [39, 18, 27] and not [40, 18, 27] since 39x18 = 702 whereas 40x18 = 720 and 40x17 = 680

How can the above be made more efficient?

Solution

You don't need your temp list, you can just use itertools.product to build it:

>>> list(itertools.product(ortho1, ortho2))
[(41, 18), (41, 17), (40, 18), (40, 17)]


Also, you should use some more descriptive names.

You can use the fact that tuples are also iterable and can be written like so: t = 0, 1, saving you the unneeded list brackets.

You can also compute the square root of target once and save it.

You should add a docstring describing what your function does.

I would separate the two concerns of your function. One is to find the side length of a square most closely matching target, the other one is finding a rectangle with ratio about 1:2.

You can use min and re-use your key function for that, since you only need the pair with the smallest distance and don't care about all others. min is \$\mathcal{O}(n)\$, whereas sort is usually \$\mathcal{O}(n \log n)\$. The abs is also not needed anymore, since we made sure that only those pairs with a product greater or equal the target are still left.

With this, your code becomes:

import math
import itertools

def get_plates_square(target):
    """Returns the integer side length of a square with an area at least `target`."""
    return math.ceil(target ** 0.5)

def get_plates(target):
    """
    Return a rectangle with integer sides with a ratio of about 2:1,
    which is closest to the given `target` area.
    """
    target_sqrt = target ** 0.5
    sides_a = math.ceil(target_sqrt * 1.5), math.floor(target_sqrt * 1.5)
    sides_b = math.ceil(target_sqrt / 1.5), math.floor(target_sqrt / 1.5)
    rectangles = [(a, b) 
                  for a, b in itertools.product(sides_a, sides_b)
                  if a * b >= target]
    return min(rectangles, key=lambda x: x[0] * x[1] - target)

Code Snippets

>>> list(itertools.product(ortho1, ortho2))
[(41, 18), (41, 17), (40, 18), (40, 17)]
import math
import itertools


def get_plates_square(target):
    """Returns the integer side length of a square with an area at least `target`."""
    return math.ceil(target ** 0.5)


def get_plates(target):
    """
    Return a rectangle with integer sides with a ratio of about 2:1,
    which is closest to the given `target` area.
    """
    target_sqrt = target ** 0.5
    sides_a = math.ceil(target_sqrt * 1.5), math.floor(target_sqrt * 1.5)
    sides_b = math.ceil(target_sqrt / 1.5), math.floor(target_sqrt / 1.5)
    rectangles = [(a, b) 
                  for a, b in itertools.product(sides_a, sides_b)
                  if a * b >= target]
    return min(rectangles, key=lambda x: x[0] * x[1] - target)

Context

StackExchange Code Review Q#154560, answer score: 4

Revisions (0)

No revisions yet.