M6) Glasses for face shape

An example glasses app recommends a frame style using a fixed rule. In this app, Round maps to Aviator, Oval to Wayfarer, and Square to Round.

Figure 1 illustrates these three rules with display mannequins. The face outline identifies the input category, and the frames show the style the app should return.

Round mannequin face wearing thin double-bridge aviator frames with teardrop lenses.
(a) Round face with Aviator frames.
Oval mannequin face wearing thick angular Wayfarer frames.
(b) Oval face with Wayfarer frames.
Square mannequin face with an angular jaw wearing circular round frames.
(c) Square face with Round frames.
Figure 1: The example app’s face-shape and frame-style matches.

The app supports English, Spanish, and Dutch. For example, the English input Round should yield Aviator, the Spanish input Redonda should yield Aviador, and the Dutch input Rond should yield Aviator.

The dictionary in Table 1 stores the greeting and recommendation message for each language.

Table 1: Messages for different languages stored in LANGUAGE_MESSAGES.
Message type Language Message
greeting EN Hello! What is your face shape? Options: Round, Oval, Square.
greeting ES ¡Hola! ¿Cuál es la forma de tu cara? Opciones: Redonda, Ovalada, Cuadrada.
greeting NL Hallo! Wat is de vorm van je gezicht? Opties: Rond, Ovaal, Vierkant.
recommendation EN Based on your face shape, we recommend the style of glasses:
recommendation ES Según la forma de tu cara, recomendamos las gafas de estilo:
recommendation NL Op basis van de vorm van je gezicht, raden we de brilstijl aan:

The list in Table 2 stores face shapes and frame styles in one three-record block per language. offset is the zero-based index of the first record in the selected block.

Table 2: Mapping stored in FRAME_ROWS. The offset is the index of the first item in the selected language block.
List index Face shape Language Glasses style
0 Round EN Aviator
1 Oval EN Wayfarer
2 Square EN Round
3 Redonda ES Aviador
4 Ovalada ES Wayfarer
5 Cuadrada ES Redonda
6 Rond NL Aviator
7 Ovaal NL Wayfarer
8 Vierkant NL Rond

The complete message dictionary and frame list are supplied in Listing 1 and Listing 2.

Listing 1: Greeting and recommendation messages by language.
LANGUAGE_MESSAGES = {
    "EN": {
        "greeting": (
            "Hello! What is your face shape? Options: "
            "Round, Oval, Square."
        ),
        "recommendation": (
            "Based on your face shape, we recommend "
            "the style of glasses:"
        ),
    },
    "ES": {
        "greeting": (
            "¡Hola! ¿Cuál es la forma de tu cara? "
            "Opciones: Redonda, Ovalada, Cuadrada."
        ),
        "recommendation": (
            "Según la forma de tu cara, recomendamos "
            "las gafas de estilo:"
        ),
    },
    "NL": {
        "greeting": (
            "Hallo! Wat is de vorm van je gezicht? "
            "Opties: Rond, Ovaal, Vierkant."
        ),
        "recommendation": (
            "Op basis van de vorm van je gezicht, "
            "raden we de brilstijl aan:"
        ),
    },
}
Listing 2: Face shapes and frame styles in three language blocks.
FRAME_ROWS = [
    {
        "face_shape": "Round",
        "language": "EN",
        "style": "Aviator",
    },
    {
        "face_shape": "Oval",
        "language": "EN",
        "style": "Wayfarer",
    },
    {
        "face_shape": "Square",
        "language": "EN",
        "style": "Round",
    },
    {
        "face_shape": "Redonda",
        "language": "ES",
        "style": "Aviador",
    },
    {
        "face_shape": "Ovalada",
        "language": "ES",
        "style": "Wayfarer",
    },
    {
        "face_shape": "Cuadrada",
        "language": "ES",
        "style": "Redonda",
    },
    {
        "face_shape": "Rond",
        "language": "NL",
        "style": "Aviator",
    },
    {
        "face_shape": "Ovaal",
        "language": "NL",
        "style": "Wayfarer",
    },
    {
        "face_shape": "Vierkant",
        "language": "NL",
        "style": "Rond",
    },
]

The function recommend_glasses in Listing 3 selects the language block and prepares two messages. Its call to find_glasses uses one of the four implementations in Listing 4. To run the example, first run both data listings, define one of those implementations, and then run the main listing.

Listing 3: The recommend_glasses function receives the language, face shape, message dictionary, and frame list, and returns two messages. It normalizes the language code, selects the start of the corresponding three-item block, and calls find_glasses.
def recommend_glasses(
    language, face, texts, frames
):
    language = language.strip().upper()

    if language == "EN":
        offset = 0
    elif language == "ES":
        offset = 3
    else:
        # Other language codes use the Dutch block.
        language = "NL"
        offset = 6

    style = find_glasses(
        face, offset, frames
    )
    greeting = texts[language]["greeting"]
    prompt = texts[language]["recommendation"]
    return greeting, f"{prompt} {style}"


greeting, result = recommend_glasses(
    "ES", "  REDONDA  ",
    LANGUAGE_MESSAGES, FRAME_ROWS,
)
print(greeting)
print(result)

Compare the four implementations of find_glasses in Listing 4. The input may include extra whitespace or use a different letter case. A shape absent from the selected block must produce "No matching glasses style found.".

Listing 4: Alternative implementations of find_glasses (Question 1).
(a) Implementation 1.
def find_glasses(
    face, offset, frames
):
    shape = face.strip().lower()

    row1 = frames[offset]
    row2 = frames[offset + 1]
    row3 = frames[offset + 2]

    if shape == (
        row1["face_shape"]
        .strip().lower()
    ):
        return row1["style"]
    elif shape == (
        row2["face_shape"]
        .strip().lower()
    ):
        return row2["style"]
    elif shape == (
        row3["face_shape"]
        .strip().lower()
    ):
        return row3["style"]
    else:
        return (
            "No matching glasses "
            "style found."
        )
(b) Implementation 2.
def find_glasses(
    face, offset, frames
):
    shape = face.strip().lower()

    for index in range(
        offset, offset + 3
    ):
        candidate = (
            frames[index]
            ["face_shape"]
            .strip().lower()
        )
        if shape == candidate:
            return (
                frames[index]
                ["style"]
            )

    return (
        "No matching glasses "
        "style found."
    )
(c) Implementation 3.
def find_glasses(
    face, offset, frames
):
    shape = face.strip().lower()

    for index in range(
        offset, offset + 3
    ):
        candidate = (
            frames[index]
            ["language"]
            .strip().lower()
        )
        if shape == candidate:
            return (
                frames[index]
                ["style"]
            )

    return (
        "No matching glasses "
        "style found."
    )
(d) Implementation 4.
def find_glasses(
    face, offset, frames
):
    shape = face.strip().lower()
    result = (
        "No matching glasses "
        "style found."
    )

    for index in range(
        offset, offset + 3
    ):
        candidate = (
            frames[index]
            ["face_shape"]
            .strip().lower()
        )
        if shape == candidate:
            result = (
                frames[index]
                ["style"]
            )
            break

Which pair of implementations correctly retrieves the frame style for the supplied face shape in its language block?

  • find_glasses(face, offset, frames) should return the frame style for face in the three records starting at offset in frames. For example, find_glasses("Oval", 0, FRAME_ROWS) should return "Wayfarer". If the shape is absent, it should return "No matching glasses style found.".
  • recommend_glasses(language, face, texts, frames) returns the greeting and completed recommendation as a tuple of two strings. It receives a language code, a face shape, the message dictionary, and the frame list. Language codes other than "EN" and "ES" select the Dutch block. With English and "Oval", the recommendation ends in "Wayfarer".
  • text.strip() removes leading and trailing whitespace; text.lower() and text.upper() return lowercase and uppercase copies. For example, " OVAL ".strip().lower() is "oval", and "es".upper() is "ES".
  • range(start, stop) visits integers from start up to, but excluding, stop. For example, list(range(2, 4)) is [2, 3].
  • list(values) collects the supplied values in a list. For example, list((2, 3)) is [2, 3].
  • print(message) displays a message and ends with a new line. For example, print("Hello!") displays Hello!.
  • An f-string inserts values into text; with style = "Round", f"Style: {style}" produces "Style: Round".