The Lazy Squares
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L4
Problem
You're building a fixture of perfect squares whose decimal form contains a particular digit, and because those squares are scattered you can't tell in advance how far you'll have to look. Given a digit digit (0 through 9) and a positive integer count, return the first count such squares in increasing order, so squares_with_digit(2, 3) gives [25, 121, 225].
A sequence that never fully reveals itself.
Example
Input:
digit = 2, count = 3
Output:
[25,121,225]
Worked solution and explanation
What this is really about
Strip the digit-hunting costume and this is an unbounded search with a quota. The qualifying squares are sparse: for digit 2 the first one is 25 (the fifth square) and the third is 225 (the fifteenth square), so there is no range you can write down in advance. The reflex answer, a comprehension over range(1, count + 1), silently collects the wrong squares. For (2, 3) it only inspects 1, 4, 9 and hands back an empty list. The real skill is producing squares one at a time from an endless source and pulling until the quota is filled, then stopping. Materialize the source too early over a guessed range and you get too few results; run it open-ended with no stop and it never returns.
Break down the requirements
Step 1: Produce squares without a fixed ceiling
You do not know how many integers you must square before count of them qualify, so the producer has to be open-ended: square 1, 2, 3, and onward on demand rather than over a precomputed range. Any finite range is a guess, and sparse digits blow straight past it.
Step 2: Filter by digit membership
Test membership on the rendered number: str(digit) in str(square). This is a substring check on the decimal text, not arithmetic on the value, which is exactly why the qualifiers do not follow a tidy pattern you could bound ahead of time.
Step 3: Collect and stop at the quota
Append each qualifier and stop the instant you hold count of them. This is the boundary that makes the whole thing terminate: the source is infinite, the result is finite, and the quota is the only stop condition.
The trap in code
- Fixed range (wrong)
- [i * i for i in range(1, count + 1) if str(digit) in str(i * i)]. For (2, 3) this inspects only 1, 4, 9 and returns []. It assumes the count-th qualifier lives among the first count squares, which sparse digits violate.
- Open-ended search (correct)
- Generate 1, 4, 9, 16, 25, and onward with no ceiling, and stop once three of them contain the digit. Same predicate, but the search length is driven by the data instead of guessed.
The solution
def squares_with_digit(digit, count):
def squares():
i = 1
while True:
yield i * i
i += 1
target = str(digit)
found = []
for square in squares():
if target in str(square):
found.append(square)
if len(found) == count:
break
return foundCommon follow-up questions
- How would you guard against an input that makes the search run forever? (Tests defensive bounding: capping iterations or raising when a caller passes input that could search without end.)
- How would you hand back the qualifiers lazily so the caller can take only the first two? (Tests yielding from the outer function plus itertools.islice, so the caller can take just the first few without collecting all count.)
- How would you extend this to squares that contain a given multi-digit substring? (Tests generalizing the substring predicate from a single 0-9 digit to an arbitrary multi-digit run.)