输入聚食的人数,预算和口味偏好,自动推荐附近的餐厅,还能生成聚餐菜单。

38 阅读9分钟

智能聚餐规划系统

  1. 项目概述

实际应用场景

在朋友、同事或家庭聚会时,组织一次成功的聚餐需要考虑多个因素:人数、预算、不同人的口味偏好、餐厅选择、菜品搭配等。传统方式需要人工查询、比较、协调,过程繁琐且容易遗漏重要信息。

痛点分析

  • 信息分散:需要分别查询餐厅、价格、评价等信息
  • 决策困难:面对众多选择难以快速找到最优方案
  • 需求平衡:难以同时满足不同人的口味偏好和预算限制
  • 时间成本:从搜索到确定方案耗时较长
  1. 核心逻辑

系统架构

输入层 → 数据处理层 → 推荐算法层 → 输出层 ↓ ↓ ↓ ↓ 用户需求 数据整合 多维度匹配 推荐结果

关键算法

  1. 多维度评分模型:综合距离、价格、评分、口味匹配度等因素

  2. 协同过滤推荐:基于用户偏好历史数据

  3. 约束满足算法:在预算和人数限制下优化选择

  4. 代码实现

项目结构

smart_dining/ ├── main.py # 主程序入口 ├── data/ # 数据目录 │ ├── restaurants.json │ └── user_preferences.json ├── modules/ # 功能模块 │ ├── input_handler.py │ ├── restaurant_recommender.py │ ├── menu_generator.py │ └── utils.py ├── config.py # 配置文件 └── README.md

核心代码

main.py - 主程序

#!/usr/bin/env python3 """ 智能聚餐规划系统主程序 """

import json from modules.input_handler import InputHandler from modules.restaurant_recommender import RestaurantRecommender from modules.menu_generator import MenuGenerator from utils import setup_logging, validate_inputs

class SmartDiningPlanner: """智能聚餐规划器"""

def __init__(self):
    self.logger = setup_logging()
    self.input_handler = InputHandler()
    self.recommender = RestaurantRecommender()
    self.menu_generator = MenuGenerator()
    
def run(self):
    """运行主流程"""
    try:
        print("🍽️  欢迎使用智能聚餐规划系统!")
        
        # 步骤1:获取用户输入
        user_inputs = self.input_handler.get_user_inputs()
        
        # 验证输入
        if not validate_inputs(user_inputs):
            raise ValueError("输入参数无效")
            
        # 步骤2:推荐餐厅
        recommended_restaurants = self.recommender.recommend(
            user_inputs['people_count'],
            user_inputs['budget'],
            user_inputs['preferences']
        )
        
        # 步骤3:生成菜单
        menus = self.menu_generator.generate_menus(
            recommended_restaurants[:3],  # 取前3个推荐餐厅
            user_inputs['people_count'],
            user_inputs['budget'],
            user_inputs['preferences']
        )
        
        # 步骤4:展示结果
        self.display_results(recommended_restaurants, menus)
        
    except Exception as e:
        self.logger.error(f"程序运行出错: {e}")
        print(f"❌ 抱歉,程序运行出错: {e}")

def main(): planner = SmartDiningPlanner() planner.run()

if name == "main": main()

input_handler.py - 输入处理模块

""" 输入处理模块 负责获取和验证用户输入 """

import re from typing import Dict, List, Tuple

class InputHandler: """输入处理器"""

def get_user_inputs(self) -> Dict:
    """
    获取用户输入
    返回包含人数、预算、偏好的字典
    """
    print("\n📝 请填写以下信息:")
    
    # 获取人数
    people_count = self._get_people_count()
    
    # 获取预算
    budget = self._get_budget(people_count)
    
    # 获取口味偏好
    preferences = self._get_preferences()
    
    return {
        'people_count': people_count,
        'budget': budget,
        'preferences': preferences
    }

def _get_people_count(self) -> int:
    """获取并验证人数输入"""
    while True:
        try:
            count = int(input("👥 聚餐人数 (2-20人): "))
            if 2 <= count <= 20:
                return count
            else:
                print("⚠️  人数应在2-20人之间")
        except ValueError:
            print("⚠️  请输入有效的数字")

def _get_budget(self, people_count: int) -> float:
    """获取并验证预算输入"""
    while True:
        try:
            budget = float(input(f"💰 总预算 (人均{50*people_count//100*100}-2000元): "))
            min_budget = 50 * people_count
            max_budget = 2000
            
            if min_budget <= budget <= max_budget:
                return budget
            else:
                print(f"⚠️  预算应在{min_budget}-{max_budget}元之间")
        except ValueError:
            print("⚠️  请输入有效的金额")

def _get_preferences(self) -> List[str]:
    """获取口味偏好"""
    print("\n🍜 请选择口味偏好 (可多选,用逗号分隔):")
    print("选项: 川菜, 粤菜, 湘菜, 鲁菜, 京菜, 日料, 韩料, 西餐, 火锅, 烧烤, 素食, 海鲜")
    
    available_cuisines = [
        "川菜", "粤菜", "湘菜", "鲁菜", "京菜", 
        "日料", "韩料", "西餐", "火锅", "烧烤", 
        "素食", "海鲜"
    ]
    
    while True:
        preference_input = input("您的偏好: ").strip()
        preferences = [p.strip() for p in preference_input.split(',')]
        
        # 验证偏好是否在可选范围内
        valid_preferences = []
        for pref in preferences:
            if pref in available_cuisines:
                valid_preferences.append(pref)
            else:
                print(f"⚠️  '{pref}' 不是有效选项")
        
        if valid_preferences:
            return valid_preferences
        else:
            print("⚠️  请至少选择一个有效的口味偏好")

class PreferenceAnalyzer: """偏好分析器"""

@staticmethod
def analyze_preference_strength(preferences: List[str]) -> Dict[str, float]:
    """
    分析偏好强度
    基于常见组合给出权重
    """
    cuisine_weights = {
        "川菜": 1.2, "粤菜": 1.1, "湘菜": 1.1, "鲁菜": 1.0,
        "京菜": 1.0, "日料": 1.3, "韩料": 1.2, "西餐": 1.2,
        "火锅": 1.4, "烧烤": 1.3, "素食": 1.0, "海鲜": 1.2
    }
    
    result = {}
    for pref in preferences:
        base_weight = cuisine_weights.get(pref, 1.0)
        
        # 特殊组合加成
        if len(preferences) > 1:
            combo_bonus = 1.1 if pref in ["火锅", "烧烤"] else 1.05
            result[pref] = base_weight * combo_bonus
        else:
            result[pref] = base_weight
            
    return result

restaurant_recommender.py - 餐厅推荐模块

""" 餐厅推荐模块 基于多维度评分模型推荐合适餐厅 """

import json import math from typing import List, Dict, Tuple from collections import defaultdict

class RestaurantRecommender: """餐厅推荐器"""

def __init__(self):
    self.restaurants = self._load_restaurants()
    
def _load_restaurants(self) -> List[Dict]:
    """加载餐厅数据"""
    try:
        with open('data/restaurants.json', 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        return self._generate_sample_data()

def _generate_sample_data(self) -> List[Dict]:
    """生成示例餐厅数据"""
    sample_restaurants = [
        {
            "id": 1,
            "name": "蜀香园川菜馆",
            "cuisine": "川菜",
            "price_range": "中等",
            "average_cost": 120,
            "rating": 4.5,
            "distance": 1.2,
            "specialties": ["水煮鱼", "麻婆豆腐", "回锅肉"],
            "capacity": 15
        },
        {
            "id": 2,
            "name": "海之味日料店",
            "cuisine": "日料",
            "price_range": "较高",
            "average_cost": 200,
            "rating": 4.7,
            "distance": 2.1,
            "specialties": ["寿司", "刺身", "天妇罗"],
            "capacity": 10
        },
        {
            "id": 3,
            "name": "老北京涮肉坊",
            "cuisine": "火锅",
            "price_range": "中等",
            "average_cost": 150,
            "rating": 4.3,
            "distance": 0.8,
            "specialties": ["涮羊肉", "手切鲜羊肉", "糖蒜"],
            "capacity": 20
        }
    ]
    
    # 确保数据目录存在
    import os
    os.makedirs('data', exist_ok=True)
    
    with open('data/restaurants.json', 'w', encoding='utf-8') as f:
        json.dump(sample_restaurants, f, ensure_ascii=False, indent=2)
        
    return sample_restaurants

def recommend(self, people_count: int, budget: float, preferences: List[str]) -> List[Dict]:
    """
    推荐餐厅主函数
    
    Args:
        people_count: 聚餐人数
        budget: 总预算
        preferences: 口味偏好列表
        
    Returns:
        推荐的餐厅列表,按评分排序
    """
    scored_restaurants = []
    
    for restaurant in self.restaurants:
        # 计算各项评分
        score = self._calculate_restaurant_score(
            restaurant, people_count, budget, preferences
        )
        
        if score > 0:  # 只保留符合条件的餐厅
            scored_restaurants.append({
                **restaurant,
                'final_score': score
            })
    
    # 按最终评分排序
    scored_restaurants.sort(key=lambda x: x['final_score'], reverse=True)
    
    return scored_restaurants[:5]  # 返回前5个推荐

def _calculate_restaurant_score(self, restaurant: Dict, people_count: int, 
                              budget: float, preferences: List[str]) -> float:
    """
    计算餐厅综合评分
    
    评分维度:
    1. 口味匹配度 (40%)
    2. 预算适配度 (30%)
    3. 容量适配度 (15%)
    4. 距离便利性 (10%)
    5. 餐厅评分 (5%)
    """
    scores = {}
    
    # 1. 口味匹配度 (40%)
    cuisine_match = self._calculate_cuisine_match(restaurant['cuisine'], preferences)
    scores['cuisine'] = cuisine_match * 0.4
    
    # 2. 预算适配度 (30%)
    budget_fit = self._calculate_budget_fit(restaurant, people_count, budget)
    scores['budget'] = budget_fit * 0.3
    
    # 3. 容量适配度 (15%)
    capacity_fit = self._calculate_capacity_fit(restaurant, people_count)
    scores['capacity'] = capacity_fit * 0.15
    
    # 4. 距离便利性 (10%)
    distance_score = self._calculate_distance_score(restaurant['distance'])
    scores['distance'] = distance_score * 0.1
    
    # 5. 餐厅评分 (5%)
    rating_score = (restaurant['rating'] / 5.0) * 0.05
    scores['rating'] = rating_score
    
    # 计算总分
    total_score = sum(scores.values())
    
    return total_score

def _calculate_cuisine_match(self, restaurant_cuisine: str, 
                            preferences: List[str]) -> float:
    """计算口味匹配度"""
    if restaurant_cuisine in preferences:
        return 1.0
    else:
        # 相关菜系给予部分分数
        related_cuisines = {
            "川菜": ["湘菜", "粤菜"],
            "粤菜": ["港式", "茶餐厅"],
            "日料": ["韩料", "西餐"]
        }
        
        for pref in preferences:
            if pref in related_cuisines.get(restaurant_cuisine, []):
                return 0.6
                
        return 0.2  # 基础分

def _calculate_budget_fit(self, restaurant: Dict, people_count: int, 
                        budget: float) -> float:
    """计算预算适配度"""
    per_person_budget = budget / people_count
    restaurant_cost = restaurant['average_cost']
    
    if restaurant_cost <= per_person_budget * 0.8:
        return 1.0  # 很划算
    elif restaurant_cost <= per_person_budget:
        return 0.9  # 合适
    elif restaurant_cost <= per_person_budget * 1.2:
        return 0.7  # 略超预算但可接受
    else:
        return 0.3  # 超出较多

def _calculate_capacity_fit(self, restaurant: Dict, people_count: int) -> float:
    """计算容量适配度"""
    capacity = restaurant['capacity']
    
    if capacity >= people_count + 2:  # 留有余地
        return 1.0
    elif capacity >= people_count:
        return 0.8
    else:
        return 0.4  # 可能拥挤但勉强可以

def _calculate_distance_score(self, distance: float) -> float:
    """计算距离便利性评分"""
    if distance <= 1.0:
        return 1.0
    elif distance <= 3.0:
        return 0.8
    elif distance <= 5.0:
        return 0.6
    else:
        return 0.4

menu_generator.py - 菜单生成模块

""" 菜单生成模块 为选定餐厅生成合适的聚餐菜单 """

import random from typing import List, Dict, Tuple from dataclasses import dataclass

@dataclass class Dish: """菜品类""" name: str price: float category: str is_specialty: bool = False

class MenuGenerator: """菜单生成器"""

def __init__(self):
    self.dish_database = self._load_dish_database()

def _load_dish_database(self) -> Dict[str, List[Dish]]:
    """加载菜品数据库"""
    return {
        "川菜": [
            Dish("水煮鱼", 68, "主菜", True),
            Dish("麻婆豆腐", 28, "主菜"),
            Dish("回锅肉", 38, "主菜"),
            Dish("宫保鸡丁", 32, "主菜"),
            Dish("酸辣土豆丝", 18, "素菜"),
            Dish("蒜泥白肉", 35, "凉菜"),
            Dish("担担面", 25, "主食"),
            Dish("银耳莲子汤", 22, "甜品")
        ],
        "日料": [
            Dish("三文鱼刺身", 88, "主菜", True),
            Dish("寿司拼盘", 128, "主菜", True),
            Dish("天妇罗", 58, "主菜"),
            Dish("照烧鸡腿", 45, "主菜"),
            Dish("味增汤", 15, "汤品"),
            Dish("毛豆", 12, "小食"),
            Dish("日式炒饭", 32, "主食"),
            Dish("抹茶冰淇淋", 25, "甜品")
        ],
        "火锅": [
            Dish("手切鲜羊肉", 78, "肉类", True),
            Dish("精品肥牛", 68, "肉类"),
            Dish("虾滑", 48, "海鲜"),
            Dish("蔬菜拼盘", 38, "蔬菜"),
            Dish("菌菇拼盘", 42, "蔬菜"),
            Dish("手工面条", 18, "主食"),
            Dish("蘸料", 8, "调料"),
            Dish("酸梅汤", 15, "饮品")
        ]
    }

def generate_menus(self, restaurants: List[Dict], people_count: int, 
                  budget: float, preferences: List[str]) -> List[Dict]:
    """
    为推荐餐厅生成菜单
    
    Args:
        restaurants: 推荐餐厅列表
        people_count: 人数
        budget: 总预算
        preferences: 口味偏好
        
    Returns:
        每个餐厅对应的菜单方案
    """
    menus = []
    
    for restaurant in restaurants:
        restaurant_menu = self._generate_single_menu(
            restaurant, people_count, budget, preferences
        )
        menus.append({
            'restaurant': restaurant,
            'menu': restaurant_menu
        })
    
    return menus

def _generate_single_menu(self, restaurant: Dict, people_count: int, 
                         budget: float, preferences: List[str]) -> Dict:
    """
    为单个餐厅生成菜单
    """
    cuisine_type = restaurant['cuisine']
    available_dishes = self.dish_database.get(cuisine_type, [])
    
    if not available_dishes:
        return self._generate_generic_menu(people_count, budget)
    
    # 计算预算分配
    total_budget = budget
    food_budget = total_budget * 0.75  # 食物占75%
    drink_budget = total_budget * 0.15  # 饮料占15%
    service_budget = total_budget * 0.10  # 服务费占10%
    
    # 生成菜单
    selected_dishes = self._select_dishes(
        available_dishes, people_count, food_budget
    )
    
    # 计算总价
    total_price = sum(dish.price for dish in selected_dishes)
    
    return {
        'dishes': selected_dishes,
        'total_price': total_price,
        'estimated_service': service_budget,
        'grand_total': total_price + service_budget,
        'per_person_cost': (total_price + service_budget) / people_count
    }

def _select_dishes(self, dishes: List[Dish], people_count: int, 
                  budget: float) -> List[Dish]:
    """
    智能选择菜品组合
    """
    selected = []
    remaining_budget = budget
    
    # 优先选择招牌菜
    specialties = [d for d in dishes if d.is_specialty]
    regular_dishes = [d for d in dishes if not d.is_specialty]
    
    # 添加招牌菜(每人1道)
    for i in range(min(people_count, len(specialties))):
        if specialties[i].price <= remaining_budget:
            selected.append(specialties[i])
            remaining_budget -= specialties[i].price
    
    # 补充其他菜品
    categories_needed = ['主菜', '素菜', '主食', '汤品']
    current_categories = set(d.category for d in selected)
    
    for category in categories_needed:
        if category not in current_categories and remaining_budget > 20:
            suitable_dishes = [
                d for d in regular_dishes 
                if d.category == category and d.price <= remaining_budget
            ]
            
            if suitable_dishes:
                chosen = random.choice(suitable_dishes)
                selected.append(chosen)
                remaining_budget -= chosen.price
                current_categories.add(category)
    
    # 如果还有预算,随机添加一些小菜
    while remaining_budget > 30 and len(selected) < people_count * 3:
        affordable_dishes = [
            d for d in regular_dishes 
            if d.price <= remaining_budget and d not in selected
        ]
        
        if affordable_dishes:
            chosen = random.choice(affordable_dishes)
            selected.append(chosen)
            remaining_budget -= chosen.price
        else:
            break
    
    return selected

def _generate_generic_menu(self, people_count: int, budget: float) -> Dict:
    """生成通用菜单(当没有特定菜系数据时)"""
    # 简化的通用菜品
    generic_dishes = [
        Dish("招牌主菜", 60, "主菜"),
        Dish("特色素菜", 25, "素菜"),
        Dish("美味汤品", 20, "汤品"),
        Dish("精选主食", 18, "主食"),
        Dish("时令水果", 15, "水果")
    ]
    
    selected = generic_dishes[:min(len(generic_dishes), people_count + 2)]
    total_price = sum(dish.price for dish in selected)
    
    return {
        'dishes': selected,
        'total_price': total_price,
        'estimated_service': budget * 0.1,
        'grand_total': total_price + (budget * 0.1),
        'per_person_cost': (total_price + (budget * 0.1)) / people_count
    }

utils.py - 工具函数

""" 工具函数模块 """

import logging import os from datetime import datetime

def setup_logging(): """设置日志""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('dining_planner.log'), logging.StreamHandler() ] ) return logging.getLogger(name)

def validate_inputs(inputs: dict) -> bool: """验证输入参数""" required_fields = ['people_count', 'budget', 'preferences']

# 检查必需字段
for field in required_fields:
    if field not in inputs or not inputs[field]:
        return False

# 检查数值合理性
if inputs['people_count'] < 2 or inputs['people_count'] > 20:
    return False

if inputs['budget'] < 100 or inputs['budget'] > 5000:
    return False

return True

def save_session_result(restaurants, menus, inputs): """保存会话结果""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"sessions/session_{timestamp}.json"

os.makedirs('sessions', exist_ok=True)

session_data = {
    'timestamp': timestamp,
    'inputs': inputs,
    'recommendations': restaurants,
    'menus': menus
}

with open(filename, 'w', encoding='utf-8') as f:
    json.dump(session_data, f, ensure_ascii=False, indent=2)

def format_currency(amount: float) -> str: """格式化货币显示""" return f"¥{amount:.2f}"

def calculate_nutrition_estimate(menu): """估算营养信息(简化版)""" # 这里可以添加更复杂的营养计算逻辑 return { 'calories_per_person': 600, # 估算每人大卡数 'protein_grams': 30, 'carbohydrates_grams': 80 }

  1. README文件

🍽️ 智能聚餐规划系统

一个基于Python的智能聚餐规划工具,能够根据人数、预算和口味偏好自动推荐合适的餐厅并生成个性化菜单。

✨ 功能特点

  • 🎯 智能推荐: 基于多维度评分模型推荐最适合的餐厅
  • 🍜 菜单定制: 根据预算和人数生成合理的菜品搭配
  • 💡 偏好分析: 智能分析用户的口味偏好并给出建议
  • 📊 成本控制: 精确控制预算,避免超支
  • 📱 简单易用: 直观的交互界面,操作简单

🚀 快速开始

安装依赖

bash

pip install -r requirements.txt

运行程序

bash

python main.py

使用示例

👥 聚餐人数 (2- 如果你觉得这个工具好用,欢迎关注我!