如何用非递归Guard校验字符串仅含0和1?(Elixir)
问题描述
需要实现一个校验逻辑,确保输入字符串的每个字符均为"0"或"1"。尝试通过递归defguard实现时出现编译错误:
(CompileError) cannot find or invoke local is_bit_string/1 inside guards. Only macros can be invoked in a guards and they must be defined before their invocation. Called as: is_bit_string(binary_part(str, 1, byte_size(str) - 1))
若在每个函数体内编写校验逻辑会造成代码冗余,求无需重复编写的替代方案。
此前尝试的代码:
defguard is_bit_string(str) when is_binary(str) and byte_size(str) == 1 and binary_part(str, 0, 1) in ["0", "1"] defguard is_bit_string(str) # byte_size(str) == 1 and when is_binary(str) and binary_part(str, 0, 1) in ["0", "1"] and is_bit_string(binary_part(str, 1, byte_size(str) - 1))
解决方案
方案1:正则表达式配合defguard
直接用正则匹配所有字符为0或1的字符串,简洁且适配guard场景:
defguard is_bit_string(str) when is_binary(str) and Regex.match?(~r/^[01]*$/, str)
- 正则
^[01]*$表示从字符串开头到结尾,所有字符只能是0或1,默认允许空字符串;若要禁止空字符串,将*替换为+即可。 Regex.match?/2是Elixir允许在guard中调用的纯函数,符合guard的约束要求。
方案2:字节级检查的函数封装(非guard场景)
如果需要更底层的字节检查,可封装一个纯函数,在需要校验的地方统一调用:
def is_bit_string?(str) when is_binary(str) do Enum.all?(String.to_charlist(str), &(&1 in [?0, ?1])) end
- 将字符串转为字符列表后,检查每个字符的ASCII值是否为
?0(48)或?1(49)。 - 该函数无法直接用于guard,但能避免在多个函数体内重复编写校验逻辑。
方案3:模式匹配+多子句函数(固定长度场景)
若目标字符串长度固定或范围有限,可通过模式匹配覆盖所有合法情况:
defguard is_bit_string(str) when is_binary(str) and (str == "" or str == "0" or str == "1" or str == "00" or str == "01" or str == "10" or str == "11")
此方式仅适用于长度有限的场景,不支持任意长度的字符串校验。
补充说明
递归defguard不可行的原因:Elixir的defguard本质是宏,但guard clauses不支持递归调用自定义guard——宏展开时会出现未定义引用(第二个defguard覆盖了第一个,递归调用时宏尚未完全定义),同时guard要求所有表达式必须是编译时可确定的纯逻辑,递归调用不符合该约束。
内容的提问来源于stack exchange,提问作者lorem1213
相关产品推荐
相关产品推荐

