如何在Elixir中获取两个嵌套结构体的匹配键值对?
提取两个嵌套Elixir结构体的匹配键值对
问题描述
我有两个结构大致相同的嵌套Elixir结构体(示例如下),需要获取二者所有层级中键值完全匹配的部分。此前尝试的for {k, v} <- m1, m2[k] == v, do: {k, v}方法仅适用于普通Map,且只能处理第一层结构,无法适配结构体和嵌套场景。
示例结构体:
%Test.Identities.MetaDetails{ browser_name: "Chrome", browser_version: "108.0.0", confidence: %Test.Identities.MetaDetails.Confidence{ score: 0.9989648033126294 }, device: "Other", first_seen_at: %Test.Identities.MetaDetails.SeenAt{ global: ~U[2022-11-25 14:56:09Z], subscription: ~U[2022-11-30 14:24:49Z] }, incognito: true, ip: "206.84.141.32", ip_location: %Test.Identities.MetaDetails.IpLocation{ accuracy_radius: 5.0, city: nil, continent: nil, country: nil, latitude: 33.7233, longitude: 73.0435, postal_code: "44000", subdivisions: [], timezone: "Asia/Karachi" }, last_seen_at: %Test.Identities.MetaDetails.SeenAt{ global: ~U[2022-12-13 11:52:24Z], subscription: ~U[2022-12-13 11:52:24Z] }, meta: %Test.Identities.MetaDetails.Meta{ version: "v1.1.1032+f754139d" }, os: "Linux", os_version: nil, request_id: "1670933504035.XjTnVT", visitor_found: true, visitor_id: "nzpkWv3ggKJsgXPx97Z0" }
解决方案
可以通过递归函数处理结构体的嵌套匹配,同时保留结构体的类型信息:
defmodule StructMatcher do @doc """ 递归匹配两个同类型嵌套结构体的键值对,返回包含所有匹配部分的结构体 """ def match_structs(s1, s2) when is_struct(s1) and is_struct(s2) and struct(s1) == struct(s2) do s1 |> Map.from_struct() |> Map.drop([:__struct__]) |> Enum.reduce(%{}, fn {k, v}, acc -> case {v, Map.get(s2, k)} do # 递归处理嵌套的同类型结构体 {v1, v2} when is_struct(v1) and is_struct(v2) -> case match_structs(v1, v2) do # 嵌套无匹配结果则跳过 %{} -> acc # 将嵌套匹配结果转为对应结构体类型后加入 nested -> Map.put(acc, k, struct(struct(v1), nested)) end # 非结构体类型直接判断值是否相等 {v1, v2} when v1 == v2 -> Map.put(acc, k, v1) # 不匹配的情况跳过 _ -> acc end end) # 将匹配结果转为原结构体类型返回 |> then(fn matched -> struct(struct(s1), matched) end) end # 非结构体或不同类型结构体返回空Map def match_structs(_, _), do: %{} end
使用示例
假设你有两个待匹配的结构体s1和s2:
# 初始化两个结构体(示例中仅展示部分差异) s1 = %Test.Identities.MetaDetails{...} s2 = %Test.Identities.MetaDetails{ browser_name: "Chrome", confidence: %Test.Identities.MetaDetails.Confidence{score: 0.9989648033126294}, browser_version: "109.0.0", # 与s1不同 first_seen_at: %Test.Identities.MetaDetails.SeenAt{ global: ~U[2022-11-25 14:56:09Z], # 与s1相同 subscription: ~U[2022-12-01 10:00:00Z] # 与s1不同 } } # 获取匹配的键值对 matched_struct = StructMatcher.match_structs(s1, s2)
执行后matched_struct会包含:
- 顶层的
browser_name、confidence first_seen_at下的global字段(因为该子字段值匹配)- 其他所有值不匹配的字段会被排除
注意事项
- 仅处理同类型的结构体,不同模块的结构体不会进行匹配
- 嵌套结构体如果没有任何匹配的键值对,会被排除在最终结果之外
- 支持所有Elixir基础类型的直接值匹配(字符串、数字、nil、列表等)
内容的提问来源于stack exchange,提问作者Junaid Farooq
相关产品推荐
相关产品推荐

