掘金 后端 ( ) • 2024-03-28 23:58

theme: fancy

最近我遇到了一个有趣的问题,我的导师想要提前将下一届的学生分配给各位老师。这听起来似乎没什么大不了的,但实际上,这可是个挺头疼的事情。

想象一下,你作为一名导师,要负责领导一群研究生。你希望这些学生和你的研究方向相符,又能发挥他们的潜力。但问题是,如果分配不公平,可能会导致资源浪费,甚至影响到学生的学习和研究。

所以,我决定动手写一个随机分配的脚本来解决这个问题。这样一来,分配就不会受到个人喜好或偏见的影响,而是完全随机的,公平而且透明。

在这篇博客里,我将和大家分享我的思路和实现过程。希望这对于遇到类似问题的人有所启发和帮助!

主要技术栈

  • Dash框架:Dash是基于Python的Web应用框架,用于构建交互式Web应用程序。
  • Pandas库:Pandas是Python中用于数据处理和分析的强大库。

分配过程

首先,让我们简要地了解一下这个脚本的工作原理。我编写了一个基于Dash的Python应用程序,其中包含了两个重要功能:手动分配和随机分配。

  • 手动分配: 用户可以手动输入学生的学号和导师的姓名,然后单击按钮将学生分配给指定的导师。这对于一些特殊情况下的指定分配非常有用。
  • 随机分配: 用户可以上传包含导师和学生信息的CSV或Excel文件,然后单击按钮执行随机分配。在这种模式下,脚本将根据每个导师可以分配的学生数量随机将学生分配给导师。

大致逻辑

现在,让我们深入了解一下脚本的逻辑。我使用了Dash来构建用户界面,这是一个基于Python的Web应用程序框架。通过Dash,我可以轻松地创建交互式组件,并将它们与Python函数进行连接。

在数据处理方面,我使用了Pandas库来处理上传的CSV或Excel文件。Pandas提供了丰富的数据操作功能,使我能够轻松地读取、处理和操作数据。

随机分配的部分则利用了Python的随机模块。我首先将导师和他们可以分配的学生数量存储在一个字典中,然后根据每个导师的指定数量随机选择学生,并将它们分配给对应的导师。

最后,我使用了Dash的回调函数来实现交互逻辑。每当用户执行操作时,回调函数将被触发,并相应地更新界面或执行其他操作。

代码

当我设计这个随机分配脚本时,我将功能分为几个部分。让我逐段解释每个部分的代码:

1. 导入必要的库和模块


import random
import time

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import pandas as pd
import base64
import io

在这一部分,我导入了所需的Python库和模块。这些库包括Dash用于构建Web应用程序,Pandas用于数据处理,以及其他一些用于处理文件上传和编码的模块。

2. 设置Dash应用程序


app = dash.Dash(__name__)

这行代码创建了一个Dash应用程序实例。我将其命名为app,并指定__name__作为应用程序的名称。

3. 设计应用程序的布局


app.layout = html.Div([
    # 上传导师信息文件的组件
    dcc.Upload(
        id='upload-data1',
        children=html.Div([
            '上传CSV或Excel文件(教师)'
        ]),
        style={...},
        accept='.csv, .xlsx'
    ),
    # 上传学生信息文件的组件
    dcc.Upload(
        id='upload-data2',
        children=html.Div([
            '上传一个CSV或Excel文件(学生)'
        ]),
        style={...},
        accept='.csv, .xlsx'
    ),
    # 输入学生学号和老师姓名的组件
    html.Div([
        html.Label('学生学号:'),
        dcc.Input(id='student-id', type='text', value=''),
        html.Label('老师姓名:'),
        dcc.Input(id='teacher-name', type='text', value=''),
        html.Button('分配老师', id='assign-button', n_clicks=0),
        html.Div(id='assign-output')
    ]),
    # 开始随机分配按钮和设置每个老师分配的学生数量
    html.Div([
        html.Label('每个老师分配的学生数量:'),
        html.Button('开始随机分配', id='random-button', n_clicks=0),
        html.Div(id='random-output')
    ])
])

这段代码定义了Dash应用程序的布局。我创建了几个上传文件和输入框组件,以便用户上传导师和学生的信息文件,并手动分配学生给指定的导师。同时,我还添加了一个按钮,用于开始随机分配学生。

4. 解析上传的文件


# 上传函数
def parse_contents(contents, filename,target):
    global teacher_df, student_df,teacher_df_all,student_df_all
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # 读取上传的CSV文件,同时指定学号列的数据类型为字符串
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')), dtype={'学号': str})
        elif 'xls' in filename or 'xlsx' in filename:
            # 读取上传的Excel文件,同时指定学号列的数据类型为字符串
            df = pd.read_excel(io.BytesIO(decoded), dtype={'学号': str})
    except Exception as e:
        print(e)
        return html.Div([
            '发生错误,无法读取文件'
        ])

    # 根据目标标识存储到相应的全局变量中
    if target == 'teacher':
        teacher_df = df
        teacher_df_all = df
    elif target == 'student':
        student_df = df
        student_df_all = df
    return df.to_dict('records')

这个函数用于解析上传的CSV或Excel文件,并将其转换为Pandas DataFrame格式。我使用了Dash的上传组件来处理文件上传操作,并在这个函数中实现了具体的解析逻辑。

5. 手动分配学生给指定导师


@app.callback(Output('assign-output', 'children'),
              Input('assign-button', 'n_clicks'),
              State('student-id', 'value'),
              State('teacher-name', 'value'))
def assign_student_to_teacher(n_clicks, student_id, teacher_name):
    ...

这个回调函数用于处理手动分配学生给指定导师的操作。当用户点击分配按钮时,该函数将被触发,并根据输入的学生学号和导师姓名执行分配操作。

6. 更新上传文件后的界面



# 渲染第一个表格
@app.callback(Output('output-data-upload1', 'children'),
              Input('upload-data1', 'contents'),
              Input('upload-data1', 'filename'),
              Input('assign-output', 'children'))
def update_output1(contents, filename, assign_output):
    global teacher_df
    if contents is not None or teacher_df is not None:
        # 判断哪个输入触发了回调
        ctx = dash.callback_context
        triggered_input = ctx.triggered[0]['prop_id'].split('.')[0]

        if triggered_input == 'assign-output':
            # 如果是 assign-output 触发的回调,执行相应的操作
            # 返回更新后的内容
            print("触发分配更新渲染回调")
        else:
            children = parse_contents(contents, filename, "teacher")

        return html.Div([  # 返回一个 Div 包裹,确保在回调中可以更新内容
            html.Table([
                html.Thead(
                    [html.Tr([html.Th(col) for col in teacher_df.columns])]
                ),
                html.Tbody([
                    html.Tr([
                        html.Td(val) for val in row
                    ]) for row in teacher_df.values
                ])
            ]),
            assign_output  # 显示分配输出结果
        ])
    else:
        return assign_output  # 显示分配输出结果

这个回调函数用于更新上传文件后的界面。当用户上传了导师信息文件时,该函数将被触发,并更新界面以显示上传的文件内容。

7. 执行随机分配学生操作


# 执行随机分配学生操作
# 在回调函数中添加保存到CSV的逻辑
@app.callback(
    Output('random-output', 'children'),
    Input('random-button', 'n_clicks')
)
def random_assign_students_callback(n_clicks):
    global teacher_df, student_df, teacher_students, teacher_df_all, student_df_all
    if n_clicks > 0 and teacher_df is not None and student_df is not None:
        # 以教师名单中的人数列为依据,将老师及其对应的人数转换为字典
        teacher_dict = dict(zip(teacher_df['教师'], teacher_df['人数']))
        # 调用随机分配学生函数,传入教师人数字典和学生列表
        random_results = random_assign_students(teacher_dict, student_df)

        # 将已指定的教师和学生信息加入到随机分配结果中
        for teacher, students in teacher_students_zhiding.items():
            if teacher in random_results:
                random_results[teacher].extend(students)
            else:
                random_results[teacher] = students

        # 将随机分配结果保存为CSV文件
        save_random_results_to_csv(random_results)

        # 显示分配结果
        output = html.Div([
            html.H3('随机分配结果:'),
        ])
        # 遍历每个教师的学生列表
        for teacher, students in random_results.items():
            # 创建一个包含教师名和学生数量的表格
            teacher_table = html.Table([
                html.Thead(html.Tr([html.Th('教师'), html.Th('学生数量')])),
                html.Tbody([
                    html.Tr([
                        html.Td(teacher),
                        html.Td(len(students))
                    ])
                ])
            ])
            # 创建一个包含学生学号、姓名、班级和电话的表格
            student_table = html.Table([
                html.Thead(html.Tr([html.Th('学生学号'), html.Th('学生姓名'), html.Th('班级'), html.Th('电话')])),
                html.Tbody([
                    html.Tr([
                        html.Td(student[0]),  # 学生学号
                        html.Td(student[1]),  # 学生姓名
                        html.Td(student_df_all.loc[student_df_all['学号'] == student[0], '班级'].values[0]),  # 班级
                        html.Td(student_df_all.loc[student_df_all['学号'] == student[0], '电话'].values[0])  # 电话
                    ]) for student in students
                ])
            ])
            # 将教师表格和学生表格放入一个 Div 中
            output.children.append(html.Div([teacher_table, student_table]))

    elif n_clicks > 0:
        output = html.Div('请先上传教师和学生信息文件')
    else:
        output = html.Div()
    return output

这个回调函数用于执行随机分配学生的操作。当用户点击随机分配按钮时,该函数将被触发,并随机将学生分配给导师。

8. 保存随机分配结果到CSV文件



def save_random_results_to_csv(random_results):
    # 创建一个空的DataFrame来存储结果
    result_df = pd.DataFrame(columns=['教师', '学生学号', '学生姓名', '班级', '电话'])

    # 遍历每个教师的学生列表,将结果添加到DataFrame中
    for teacher, students in random_results.items():
        temp_df = pd.DataFrame(students, columns=['学生学号', '学生姓名'])
        temp_df['教师'] = teacher
        temp_df['班级'] = temp_df['学生学号'].apply(lambda x: student_df_all.loc[student_df_all['学号'] == x, '班级'].values[0])
        temp_df['电话'] = temp_df['学生学号'].apply(lambda x: student_df_all.loc[student_df_all['学号'] == x, '电话'].values[0])
        result_df = pd.concat([result_df, temp_df], ignore_index=True)

    # 保存DataFrame为CSV文件
    result_df.to_csv('随机分配结果.csv', index=False)

这个函数用于将随机分配的结果保存到CSV文件中。我将每个导师分配的学生信息保存到一个CSV文件中,以便进一步分析或导入到其他系统中。

结束语

这个脚本被我上传到了Gitee,有jy感兴趣的话可以下载下来玩玩,如果各位感兴趣的大佬给个star。 导师随机分配: 这个脚本是一个基于Dash框架的Web应用程序,用于教师和学生信息的管理和随机、指定分配。 (gitee.com)