2

I want to iterate some string from voice input through python dict that contains one, two or more word in key and add the strings to a list if exist in dict keys. I know how to add the string (voiceinput) to a new list (fruit_cart), if it only contains one word in dict key (dict_fruit) and iterating its values, like this:

# one word only of dict keys
dict_fruit = {"apple": 2, "melon": 6, "mango": 3, "banana": 4, "grape": 5}

voiceinput = 'I want 2 Melons 3 apples 1 mango 4 grapes and 1 banana'

def show_order(cart, quantity, price, gross_price):
    print(); print("PYTHON FRUIT STORE".center(55, " "))
    print("=" * 55); print(f"{'# ':<5}{'Item':<15}{'Item':<10}{'Order':<10}{'Order':>8}")
    print(f"{' ':<5}{'Name':<15}{'Price':<10}{'Quantity':<10}{'Price':>8}"); print("-" * 55)
    total_price = 0
    for idx, item in enumerate(cart):
        print(f"{idx + 1:<5}{item:<15}{'$':<2}{price[idx]:>2}{quantity[idx]:^15}{'$':>3} {gross_price[idx]:.2f}")
        total_price = total_price + gross_price[idx]
    tax = 10 if total_price > 4.99 else 11
    vat = total_price * tax / 100
    final_price = total_price + vat
    print("-" * 55); print("\t\t\t\t\tTotal Price           ", f"{'$':<1} {total_price:.2f}")
    print(f"\t\t\t\t\tVAT {tax}%               ", f"{'$':<1} {vat:.2f}")
    print("\t\t\t\t\tFinal Price           ", f"{'$':<1} {final_price:.2f}"); print()
    print("  Thank You  ".center(55, "="))
    return final_price

def order_process(userinput):
    userlist = [char.removesuffix("s") for char in userinput.lower().split()]
    fruit_cart = [item for item in userlist if item in dict_fruit]
    fruit_quantity = [int(num) for num in userlist if num.isdigit() and num != '0']
    if len(fruit_cart) != len(fruit_quantity) or len(fruit_quantity) == 0 or len(fruit_cart) == 0: return exit()
    fruit_price = [dict_fruit[item] for item in fruit_cart]
    gross_price = [qty * prc for qty, prc in zip(fruit_quantity, fruit_price)]
    return show_order(fruit_cart, fruit_quantity, fruit_price, gross_price)


order_process(voiceinput) if any(word in voiceinput for word in fruit) else exit()

For the two words, I've tried spliting the first word and second word into separate 2 list (first_word and second_word) and iterating them with tuples (fruit_color and fruit_names) then join them at the end (join_word and fruit_cart), but it seems it's not the elegant way or solution since it works only on 2 words:

# two words only of dict keys    
dict_fruit = {"red apple": 2, "green apple": 2, "black grape": 5}
fruit_color = ('red', 'green', 'black')
fruit_names = ('apple', 'grape')

voiceinput = 'I want 3 green Apples 2 red apples and 1 black grape'


def order_process(userinput):
    userlist = [char.removesuffix("s") for char in userinput.lower().split()]
    first_word = [word for word in userlist if word in fruit_color]
    second_word = [word for word in userlist if word in fruit_names]
    if len(first_word) != len(second_word): return exit()
    join_word = [word for pair in zip(first_word, second_word) for word in pair]
    fruit_cart = [' '.join(word) for word in zip(join_word[0::2], join_word[1::2])]
    fruit_quantity = [int(num) for num in userlist if num.isdigit() and num != '0']
    if len(fruit_cart) != len(fruit_quantity) or len(fruit_quantity) == 0 or len(fruit_cart) == 0: return exit()
    elif not any(word in fruit_cart for word in dict_fruit): return exit()    
    fruit_price = [dict_fruit[item] for item in fruit_cart]
    gross_price = [qty * prc for qty, prc in zip(fruit_quantity, fruit_price)]
    return show_order(fruit_cart, fruit_quantity, fruit_price, gross_price)


order_process(voiceinput) if any(word in voiceinput for word in fruit) else exit()

How if the strings and dict keys consists 1, 2, or more words like this:

dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
voiceinput = 'I want 2 red Apples 3 mangos 1 melon and 4 black grapes'

then add the strings (voiceinput) if it exist in dict keys (dict_fruit) or vice-versa to a new list (fruit_cart) like below:

fruit_cart = ['red apple', 'mango', 'melon', 'black grape']

I know the code is messy, I'm new to python by the way.

Arifa Chan
  • 947
  • 2
  • 6
  • 23
  • it would be easier if you were checking if dict keys is contained in the string and not the opposite (which leads to those kind of issue) – Bil11 Jul 18 '22 at 20:06
  • @Bil11 Got it. So you suggest it should be using for loop instead, cause changing it to "if any(word in fruit for word in voiceinput)" won't do it. Or should I change the list comprehension of fruit_cart to [item for item in fruit if item in userlist]? – Arifa Chan Jul 18 '22 at 21:04
  • Yes, I think that the second solution is far better. Another point, If you want a better solution for making more reliable comparison, you should look at [stemming](https://stackoverflow.com/a/24663617/18514407). – Bil11 Jul 18 '22 at 21:17
  • I can't edit my previous comment anymore so here it goes. If you want to make a comprehension like this, you can't split your string as you did before. You should stem your string and iterate over dict keys (stemming them on the fly) and checking for inclusion in your stemmed string (with number of occurence and whatever you want) – Bil11 Jul 18 '22 at 21:28
  • @Bil11 Cool! Got it. I'll learn more about stemming or lemmatization. However, I didn't expect NLP to be involved in this simple code. :) – Arifa Chan Jul 18 '22 at 21:42

2 Answers2

4

This is a suitable job for regular expressions (regex).

For dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5},
the appropriate regex would be
(\d+) (red apple|green apple|melon|mango|banana|black grape),
which can be built with
f"(\\d+) ({'|'.join(dict_fruit.keys())})".

import re

dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
voiceinput = 'I want 1 apple 2 red Apples 3 mangos 1 melon and 4 black grapes'

# dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
# voiceinput = 'I want 1 apple 2 red Apples 3 mangos 1 melon and 4 black grapes'

# dict_fruit = {"apple": 1, "red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
# voiceinput = 'I want 1 apple 2 red Apples 3 mangos 1 melon and 4 black grapes'

def show_order(cart, quantity, price, gross_price):
    print(); print("PYTHON FRUIT STORE".center(55, " "))
    print("=" * 55); print(f"{'# ':<5}{'Item':<15}{'Item':<10}{'Order':<10}{'Order':>8}")
    print(f"{' ':<5}{'Name':<15}{'Price':<10}{'Quantity':<10}{'Price':>8}"); print("-" * 55)
    total_price = 0
    for idx, item in enumerate(cart):
        print(f"{idx + 1:<5}{item:<15}{'$':<2}{price[idx]:>2}{quantity[idx]:^15}{'$':>3} {gross_price[idx]:.2f}")
        total_price = total_price + gross_price[idx]
    tax = 10 if total_price > 4.99 else 11
    vat = total_price * tax / 100
    final_price = total_price + vat
    print("-" * 55); print("\t\t\t\t\tTotal Price           ", f"{'$':<1} {total_price:.2f}")
    print(f"\t\t\t\t\tVAT {tax}%               ", f"{'$':<1} {vat:.2f}")
    print("\t\t\t\t\tFinal Price           ", f"{'$':<1} {final_price:.2f}"); print()
    print("  Thank You  ".center(55, "="))
    return final_price

def order_process(userinput):
    matches = re.findall(f"(\\d+) ({'|'.join(dict_fruit.keys())})", userinput.lower())
    fruit_quantity, fruit_cart = zip(*((int(quantity), fruit) for quantity, fruit in matches))
    fruit_price = [dict_fruit[item] for item in fruit_cart]
    gross_price = [qty * prc for qty, prc in zip(fruit_quantity, fruit_price)]
    return show_order(fruit_cart, fruit_quantity, fruit_price, gross_price)


order_process(voiceinput) if any(word in voiceinput for word in dict_fruit) else exit()
aaron
  • 39,695
  • 6
  • 46
  • 102
  • Great! This works well when the item does not followed by the quantity, so it doesn't have to check the value or the length of each list. Now I'll try to adapt this code to my language for the typical ordering conversation and check some bug that might occurs in main code. (regex... Why didn't I think of that before) – Arifa Chan Jan 01 '23 at 18:37
0

This workaround solve the 1, 2 or more words in dictionary by iterating the input with nested for loop and check the every possibilty of joined word for each splitted word to dict:

dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
voiceinput = 'I want 2 red Apples 3 mangos 1 melon and 4 black grapes'  

def show_order(cart, quantity, price, gross_price):
    print(); print("PYTHON FRUIT STORE".center(55, " "))
    print("=" * 55); print(f"{'# ':<5}{'Item':<15}{'Item':<10}{'Order':<10}{'Order':>8}")
    print(f"{' ':<5}{'Name':<15}{'Price':<10}{'Quantity':<10}{'Price':>8}"); print("-" * 55)
    total_price = 0
    for idx, item in enumerate(cart):
        print(f"{idx + 1:<5}{item:<15}{'$':<2}{price[idx]:>2}{quantity[idx]:^15}{'$':>3} {gross_price[idx]:.2f}")
        total_price = total_price + gross_price[idx]
    tax = 10 if total_price > 4.99 else 11
    vat = total_price * tax / 100
    final_price = total_price + vat
    print("-" * 55); print("\t\t\t\t\tTotal Price           ", f"{'$':<1} {total_price:.2f}")
    print(f"\t\t\t\t\tVAT {tax}%               ", f"{'$':<1} {vat:.2f}")
    print("\t\t\t\t\tFinal Price           ", f"{'$':<1} {final_price:.2f}"); print()
    print("  Thank You  ".center(55, "="))
    return final_price

def order_process(userinput):
    userlist = [char.removesuffix("s") for char in userinput.lower().split()]
    fruit_cart = [
                   ' '.join(userlist[i:i+j])
                   for i in range(len(userlist))
                   for j in range(1, len(userlist) - i + 1)
                   if ' '.join(userlist[i:i+j]) in dict_fruit
                 ]
    fruit_quantity = [int(num) for num in userlist if num.isdigit() and num != '0']
    if len(fruit_cart) != len(fruit_quantity) or len(fruit_quantity) == 0 or len(fruit_cart) == 0: return exit()
    elif not any(word in fruit_cart for word in dict_fruit): return exit()    
    fruit_price = [dict_fruit[item] for item in fruit_cart]
    gross_price = [qty * prc for qty, prc in zip(fruit_quantity, fruit_price)]
    return show_order(fruit_cart, fruit_quantity, fruit_price, gross_price)


order_process(voiceinput) if any(word in voiceinput for word in dict_fruit) else exit()

However, if there's a word in input that contains word of two words key in dict this code ends because the length of fruit_cart and fruit_quantity aren't the same due to that one word were not added to the fruit_cart:

dict_fruit = {"red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
voiceinput = 'I want 1 apple 2 red Apples 3 mangos 1 melon and 4 black grapes'

# userlist = ['i', 'want', '1', 'apple', '2', 'red', 'apple', '3', 'mango', '1', 'melon', 'and', '4', 'black', 'grape']
# fruit_cart = ['red apple', 'mango', 'melon', 'black grape']
# fruit_quantity = [1, 2, 3, 1, 4]

Even though word has been added to the dict, e.g 'apple':

dict_fruit = {"apple": 1, "red apple": 2, "green apple": 1, "melon": 6, "mango": 3, "banana": 4, "black grape": 5}
voiceinput = 'I want 1 apple 2 red Apples 3 mangos 1 melon and 4 black grapes'

# userlist = ['i', 'want', '1', 'apple', '2', 'red', 'apple', '3', 'mango', '1', 'melon', 'and', '4', 'black', 'grape']
# fruit_cart = ['apple', 'red apple', 'apple', 'mango', 'melon', 'black grape']
# fruit_quantity = [1, 2, 3, 1, 4]
Arifa Chan
  • 947
  • 2
  • 6
  • 23