【字节跳动】数据挖掘面试题0003:有一个文件,每一行是一个数字,如何用 MapReduce 进行排序和求每个用户每个页面停留时间

MapReduce 是一种适合处理大规模数据的分布式计算框架,其核心思想是将计算任务分解为 Map(映射)Reduce(归约)两个阶段。

对文件中的数字进行排序,可以利用 MapReduce 的特性来实现。

要使用 MapReduce 对文件中的数字进行排序,需要实现一个 MapReduce 作业,将数字作为键处理,利用 Hadoop 的默认排序机制对键进行排序。

以下是实现步骤和示例代码:

文章大纲

题目一:有一个文件,每一行是一个数字,如何用 MapReduce 进行排序

实现思路

  1. Mapper将每行的数字转换为整数键,值可以设为空或占位符
  2. Shuffle 阶段:MapReduce 框架自动对键进行排序,确保相同键或不同键按顺序发送到 Reduce 节点。
  3. Reducer直接输出键(已排序),忽略值
  4. 排序机制:Hadoop 默认会对 Mapper 输出的键进行排序,因此无需额外处理。
具体实现步骤
  1. Map 阶段
    • 输入:每行一个数字(如:10\n5\n8\n3)。
    • 处理:将每行数字转换为整数作为键,值设为1(或任意常量)。
    • 输出:<key=数字, value=1>。
  2. Shuffle 阶段
    • MapReduce 自动按键排序,确保所有键按升序发送到 Reduce 节点。
  3. Reduce 阶段
    • 输入:排序后的键值对(如:< 3,1>, <5,1>, <8,1>, <10,1>)。
    • 处理:直接输出键,忽略值。
    • 输出:排序后的数字(每行一个)。
关键点说明
  • 1.排序机制
    • MapReduce 默认按键的字典序排序。若处理整数,需确保 Map 输出的键为数值类型(而非字符串),否则会按字符串排序(如:10会排在5前面)。
  • 2.全排序 vs 分区排序
    • 全排序:若要全局有序,需确保所有数据进入同一个 Reduce 任务(设置numReduceTasks=1),但这会导致单点性能瓶颈。
    • 分区排序:若数据量极大,可通过自定义分区函数将数据分到多个 Reduce 任务,每个任务内部有序(如:键1-100到 Reduce1,101-200到 Reduce2)。
  • 3.优化技巧
    • 使用 Combiner 在 Map 端预聚合,减少网络传输。
    • 若数据分布不均,可使用二次排序或采样分区避免数据倾斜

示例代码

下面是一个完整的 Python 实现,使用 Hadoop Streaming:

python 复制代码
# mapper.py
import sys

for line in sys.stdin:
    line = line.strip()
    if line:  # 跳过空行
        try:
            num = int(line)
            print(f"{num}\t1")  # 输出格式: 数字<TAB>1
        except ValueError:
            continue  # 忽略非数字行
python 复制代码
# reducer.py
import sys

for line in sys.stdin:
    line = line.strip()
    if line:
        try:
            num, _ = line.split('\t', 1)
            print(num)  # 直接输出数字
        except ValueError:
            continue  # 忽略格式错误的行

执行命令

假设输入文件为 numbers.txt,可以使用以下 Hadoop Streaming 命令执行排序:

bash 复制代码
hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \
-input numbers.txt \
-output output \
-mapper "python3 mapper.py" \
-reducer "python3 reducer.py" \
-numReduceTasks 1  # 确保所有数据在一个 reducer 中排序

代码说明

  • Mapper :读取每一行,转换为整数作为键,值设为 1(占位符)。
  • Reducer:读取键值对,直接输出键(排序后的数字)。
  • 排序保证Hadoop 会自动对 Mapper 输出的键进行排序,因此 Reducer 接收到的键已经是有序的

这个实现利用了 Hadoop 的默认排序机制,确保最终输出的数字是有序的。如果输入文件非常大,可以增加 Reducer 数量,但需要额外处理分区以保证全局有序
利用 MapReduce 的默认排序特性,通过简单的 Map 和 Reduce 函数即可实现数字排序。核心是将数字作为键,让框架自动完成排序工作,最终由 Reduce 输出有序结果。这种方式适用于 TB 级数据的分布式排序

题目二:求每个用户每个页面停留时间

bash 复制代码
有一张网页访问日志表,记录了user_id,session_id,page_id,timestamp

用户在每点击一个连接跳转,就会记录一个时间戳,并且page_id排序后与时间戳的排序一致,现要求每个用户的每个页面所停留的时间。
比如:
1 1 1 10:00 1 1 3 12:00

页面停留时间的计算方式: 用户在页面的停留时间 = 下一个页面的访问时间 - 当前页面的访问时间

要计算每个用户在每个页面上的停留时间,需按用户和会话分组,对页面访问记录按时间排序,然后计算相邻页面之间的时间差。以下是实现思路和示例代码:

实现思路

    1. 数据预处理 :按 user_idsession_id 分组,并按 timestamp 排序(确保页面访问顺序正确)。
    1. 计算停留时间 :对每个用户的每个会话,用后一个页面的 timestamp 减去当前页面的 timestamp,得到停留时间。
    1. 处理边界情况最后一个页面的停留时间可设为 0,或根据业务需求估算(如平均停留时间、页面退出率等)

示例代码

要在 Hive 中计算每个用户在每个页面上的停留时间,可以使用窗口函数对日志数据进行排序并计算相邻记录的时间差。以下是具体的 Hive SQL 实现:

  • 适用于大规模日志数据的分析
实现思路
  • 数据准备:假设已有表 page_views,包含 user_id、session_id、page_id 和 timestamp 字段。

  • 时间转换:将 timestamp 转换为 Hive 可处理的时间格式(如 UNIX_TIMESTAMP)。

  • 窗口函数排序:按 user_id 和 session_id 分组,按时间排序。

  • 计算停留时间:使用 LEAD() 函数获取下一条记录的时间戳,计算差值。

    sql 复制代码
    -- 使用窗口函数计算每个用户在每个页面的停留时间
    WITH sorted_views AS (
      SELECT 
        user_id,
        session_id,
        page_id,
        timestamp,
        -- 将时间戳转换为 UNIX 时间(秒)
        UNIX_TIMESTAMP(timestamp, 'HH:mm') AS ts_seconds,
        -- 获取同一用户和会话的下一条记录的时间戳
        LEAD(UNIX_TIMESTAMP(timestamp, 'HH:mm'), 1) OVER (
          PARTITION BY user_id, session_id 
          ORDER BY UNIX_TIMESTAMP(timestamp, 'HH:mm')
        ) AS next_ts_seconds
      FROM page_views
    )
    -- 计算停留时间(秒转分钟),最后一页的停留时间设为 0
    SELECT 
      user_id,
      session_id,
      page_id,
      timestamp,
      -- 计算时间差(分钟),最后一页默认为 0
      COALESCE(ROUND((next_ts_seconds - ts_seconds) / 60, 2), 0) AS time_spent_minutes
    FROM sorted_views
    ORDER BY user_id, session_id, ts_seconds;

假设使用 Python 和 Pandas 处理日志数据:

python 复制代码
import pandas as pd

# 示例数据(实际应从日志表读取)
data = [
    (1, 1, 1, '10:00'),
    (1, 1, 3, '12:00'),
    (1, 1, 2, '15:00'),
    (2, 1, 4, '09:30'),
    (2, 1, 5, '11:00')
]

df = pd.DataFrame(data, columns=['user_id', 'session_id', 'page_id', 'timestamp'])

# 将 timestamp 转换为 datetime 类型以便计算时间差
df['timestamp'] = pd.to_datetime(df['timestamp'], format='%H:%M')

# 按 user_id、session_id 分组,并按 timestamp 排序
df_sorted = df.sort_values(['user_id', 'session_id', 'timestamp'])

# 计算每个页面的停留时间(使用下一个时间戳减去当前时间戳)
df_sorted['time_spent'] = (
    df_sorted.groupby(['user_id', 'session_id'])['timestamp']
    .diff(-1)  # 下一行减去当前行
    .abs()  # 取绝对值(确保时间差为正)
    .dt.total_seconds() / 60  # 转换为分钟
)

# 最后一个页面的停留时间设为 0(或根据业务需求处理)
df_sorted['time_spent'] = df_sorted['time_spent'].fillna(0)

# 输出结果
print(df_sorted[['user_id', 'session_id', 'page_id', 'timestamp', 'time_spent']])

输出示例

关键说明

  1. 时间计算

    • 使用 diff(-1) 计算当前行与下一行的时间差。
    • fillna(0) 处理最后一个页面(无后续记录)的停留时间。
  2. 数据排序

    • 必须按 user_idsession_idtimestamp 排序,确保时间顺序正确。
  3. 边界处理

    • 最后一个页面的停留时间可根据业务逻辑调整(如设置默认值、关联退出事件等)。

注意事项

  • 如果日志表数据量大,建议使用分布式计算框架(如 Spark)处理。
  • 确保 timestamp 格式统一,否则需先进行数据清洗。
  • 对于单页会话(只有一个页面访问记录),停留时间会被设为 0,需根据需求调整。
相关推荐
xingshanchang4 小时前
PyTorch 不支持旧GPU的异常状态与解决方案:CUDNN_STATUS_NOT_SUPPORTED_ARCH_MISMATCH
人工智能·pytorch·python
reddingtons5 小时前
Adobe Firefly AI驱动设计:实用技巧与创新思维路径
大数据·人工智能·adobe·illustrator·photoshop·premiere·indesign
CertiK5 小时前
IBW 2025: CertiK首席商务官出席,探讨AI与Web3融合带来的安全挑战
人工智能·安全·web3
Deepoch6 小时前
Deepoc 大模型在无人机行业应用效果的方法
人工智能·科技·ai·语言模型·无人机
Deepoch6 小时前
Deepoc 大模型:无人机行业的智能变革引擎
人工智能·科技·算法·ai·动态规划·无人机
Binary_ey6 小时前
AR衍射光波导设计遇瓶颈,OAS 光学软件来破局
人工智能·软件需求·光学软件·光波导
昵称是6硬币7 小时前
YOLOv11: AN OVERVIEW OF THE KEY ARCHITECTURAL ENHANCEMENTS目标检测论文精读(逐段解析)
图像处理·人工智能·深度学习·yolo·目标检测·计算机视觉
平和男人杨争争7 小时前
机器学习2——贝叶斯理论下
人工智能·机器学习
静心问道7 小时前
XLSR-Wav2Vec2:用于语音识别的无监督跨语言表示学习
人工智能·学习·语音识别