如何最好地对HashMap进行展开和收起

5
我想像这个例子那样展开一个HashMap实例。请注意,数据不是以JSON格式呈现的,这只是伪代码。
nested = {
  "one": {
    "two": {
      "2a": "x",
      "2b": "y"
    }
  },
  "side": "value"
}

// output: { "one.two.2a": "x", "one.two.2b": "y", "side": "value" }

很不幸,我没有找到任何相关的参考实现,所以我想出了下面这个递归解决方案。是否有更好的方法(指不使用递归或在性能、安全性或代码清晰度方面更好的方法)来实现此目的?输出应该是另一个扁平化的 HashMap

我将为这种目的使用结果 https://redislabs.com/redis-best-practices/data-storage-patterns/object-hash-storage/

public class Flat {

  public static void flatten(Map<String, ?> target, Map<String, String> result, String path) {
    for (var entry : target.entrySet()) {
      var next = path.equals("") ? entry.getKey() : path + "." + entry.getKey();
      if (entry.getValue() instanceof Map) {
        flatten((Map) entry.getValue(), result, next);
      } else {
        result.put(next, entry.getValue().toString());
      }
    }
  }

  public static Map unflatten(Map<String, String> target) {
    var result = new HashMap<String, Object>();
    for (var entry : target.entrySet()) {
      if (entry.getKey().split(".").length == 1) {
        result.put(entry.getKey(), entry.getValue());
      } else {
        var path = entry.getKey().split(".");
        Map<String, Object> current = new HashMap<>();
        for (var i = 0; i < path.length - 1; i++) {
          if (result.containsKey(path[i])) {
            current = (Map) (result.get(path[i]));
          } else {
            current = new HashMap<>();
            result.put(path[i], current);
          }
        }
        current.put(path[path.length - 1], entry.getValue());
      }
    }
    return result;
  }
}

你从哪里获取这些数据?JSON格式的吗? - Lino
1
你的问题是关于JSON还是关于HashMap?它们完全不同。 - Kayaman
@Kayaman 哈希映射表。我有一个哈希映射实例需要展开。对于混淆感到抱歉。 - Cemre Mengü
@Lino 我有一个哈希映射实例,所以没有 JSON。输出应该是另一个扁平化形式的哈希映射。 - Cemre Mengü
1
请查看 https://github.com/wnameless/json-flattener。 - Nikolai Shevchenko
@NikolaiShevchenko 我试过了,但我首先需要将其序列化为JSON,然后扁平化再反序列化,这在性能方面太耗费工作了。 - Cemre Mengü
1个回答

1
如果您想清理递归代码,可以按以下方式更新它:
public static Map<String, String> flatten(Map<String, ?> source) {
    Map<String, String> converted = new HashMap<>();

    for (var entry : source.entrySet()) {
        if (entry.getValue() instanceof Map) {
            flatten((Map<String, Object>) entry.getValue())
                    .forEach((key, value) -> converted.put(entry.getKey() + "." + key, value));
        } else {
            converted.put(entry.getKey(), entry.getValue().toString());
        }
    }

    return converted;
}

感谢一条评论,我也查看了堆栈解决方案。你可以按照以下示例重写flatten函数。哪种方法应该使用取决于开发者的技能水平,因为堆叠版本有点更难理解。

private static class StackElement {
    Optional<String> key;
    Map<String, ?> elements;

    public StackElement(String key, Map<String, ?> elements) {
        this.key = Optional.ofNullable(key);
        this.elements = elements;
    }
}

public static Map<String, String> flattenNonRecursive(Map<String, ?> source) {
    Map<String, String> converted = new HashMap<>();

    Stack<StackElement> stack = new Stack();
    stack.push(new StackElement(null, source));

    while (!stack.empty()) {
        var frame = stack.pop();

        for (var entry : frame.elements.entrySet()) {
            var frameKey = frame.key
                    .map(k -> k + ".")
                    .orElse("") + entry.getKey();

            if (entry.getValue() instanceof Map) {
                stack.push(new StackElement(frameKey, (Map<String, ?>) entry.getValue()));
            } else {
                converted.put(frameKey, entry.getValue().toString());
            }
        }
    }

    return converted;
}

就性能而言,非递归方式更快。我进行了一个小实验,使用一个带有Map.of("sample.test.two", "one", "test.sample.two", "three", "four", "file")的映射。

调用该方法1000次的性能差异如下:

Recursive took:         20957300
Non recursive took:     13376000

关于你的“unflatten”函数,它存在缺陷。在我进行的一个简单映射测试中,仅包含两个元素,该函数由于索引越界而崩溃。这与你在错误的位置使用“result”和“current”有关。下面是稍作修改的可工作副本:

public static Map<String, ?> unflatten(Map<String, String> target) {
    var result = new HashMap<String, Object>();

    for (var entry : target.entrySet()) {
        var split = entry.getKey().split("\\.");
        if (split.length == 1) {
            result.put(entry.getKey(), entry.getValue());
            continue;
        }

        var current = result;
        for (int i = 0; i < split.length - 1; i++) {
            current = (HashMap<String, Object>) current.computeIfAbsent(
                    split[i], p -> new HashMap<String, Object>());
        }
        current.put(split[split.length - 1], entry.getValue());
    }

    return result;
}

您可以通过使用显式堆栈来避免递归。 - user207421
1
请将以下关于编程的内容从英语翻译成中文。仅返回翻译后的文本:然后,请添加作者要求的示例。然后请添加作者要求的示例。 - Gerben Jongerius
我稍微研究了一下显式堆栈解决方案,并写了一个没有递归的示例。但我不确定它是否是首选方案,因为它更复杂且不够简洁。 - Gerben Jongerius
@GerbenJongerius 谢谢!使用基于堆栈的解决方案,展开操作看起来如何?你认为也有可能非递归地完成吗? - Cemre Mengü
我不明白你的问题。对于unflatten,不需要堆栈,因为没有递归。 - Gerben Jongerius
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接