Восстановление дерева значений по его плоской карте
Есть плоская карта (flatMap) значений типа Map<String, String>, по которой нужно восстановить исходное дерево значений. Элементами дерева могут быть либо карты значений Map<String, Object>, либо списки значений List<Object>, либо просто String. Глубина вложенности дерева не ограничена. Например:
public static void main(String[] args) {
TreeMap<String, String> flatMap = new TreeMap<String, String>() {{
put("1999.3:1", "23");
put("1999.3:2", "24");
put("1999.3:3", "25");
put("1999.4:1", "1");
put("1999.4:2", "2");
put("1999.4:3.10", "42");
put("2001.11.7:1", "23");
put("2001.11.7:2", "24");
put("2001.11.7:3", "25");
put("2001.11.9:1", "1");
put("2001.11.9:2", "2");
put("2001.11.9:3", "3");
put("2001.12", "45");
}};
System.out.println(flatMap);
}
Плоская карта:
{1999.3:1=23, 1999.3:2=24, 1999.3:3=25, 1999.4:1=1, 1999.4:2=2, 1999.4:3.10=42,
2001.11.7:1=23, 2001.11.7:2=24, 2001.11.7:3=25,
2001.11.9:1=1, 2001.11.9:2=2, 2001.11.9:3=3, 2001.12=45}
Исходное дерево:
{1999={3=[23, 24, 25], 4=[1, 2, {10=42}]},
2001={11={7=[23, 24, 25], 9=[1, 2, 3]}, 12=45}}
См. Представление дерева значений в виде плоской карты
Ответы (3 шт):
Не знаю, а чем смысл такой плоской карты. Раз уж конкатинируете строки, то тогда HashMap больше имеет смысла сделать, вместо словаря.
Ниже код для сборки оригинала из плоской карты.
public static void main(String[] args) {
TreeMap<String, String> flatMap = new TreeMap<String, String>() {{
put("1999.3:1", "23");
put("1999.3:2", "24");
put("1999.3:3", "25");
put("1999.4:1", "1");
put("1999.4:2", "2");
put("1999.4:3.10", "42");
put("2001.11.7:1", "23");
put("2001.11.7:2", "24");
put("2001.11.7:3", "25");
put("2001.11.9:1", "1");
put("2001.11.9:2", "2");
put("2001.11.9:3", "3");
put("2001.12", "45");
}};
System.out.println(flatMap);
Код поиска оригинала ниже
TreeMap<String, Object> original = new TreeMap<>();
flatMap.forEach((key, value) -> {
String[] splitted = key.split("\\.");
TreeMap<String, Object> node = original;
for (int i = 0; i < splitted.length - 1; i++) {
TreeMap<String, Object> child = null;
if (node.containsKey(splitted[i])) child = (TreeMap<String, Object>) node.get(splitted[i]);
else {
child = new TreeMap<>();
node.put(splitted[i], child);
}
node = child;
}
String lastSegment =splitted[splitted.length - 1];
if (lastSegment.contains(":")){
String[] lastSegmentSplitted = lastSegment.split(":");
ArrayList<String> array = null;
if(node.containsKey(lastSegmentSplitted[0]))
array = (ArrayList<String>) node.get(lastSegmentSplitted[0]);
else{
array = new ArrayList<>();
node.put(lastSegmentSplitted[0], array);
}
array.add(value);;
}
else
node.put(lastSegment, value);
});
System.out.println(original);
}
Вывод в консоль
{1999.3:1=23, 1999.3:2=24, 1999.3:3=25, 1999.4:1=1, 1999.4:2=2, 1999.4:3.10=42, 2001.11.7:1=23, 2001.11.7:2=24, 2001.11.7:3=25, 2001.11.9:1=1, 2001.11.9:2=2, 2001.11.9:3=3, 2001.12=45}
{1999={3=[23, 24, 25], 4=[1, 2], 4:3={10=42}}, 2001={11={7=[23, 24, 25], 9=[1, 2, 3]}, 12=45}}
Разбираем ключи плоской карты - восстанавливаем исходное дерево. Для каждого ключа слева направо ищем символы-разделители:
- Точка
.- вложенный объект - это карта. Следующее за ней число - ключ в карте. - Двоеточие
:- вложенный объект - это лист. Следующее за ним число - порядок в листе.
Далее рекурсивно обрабатываем вложенные объекты, смещаясь вправо по ключу плоской карты:
public static void main(String[] args) {
TreeMap<String, String> flatMap = new TreeMap<String, String>() {{
put("1999.3:1", "23");
put("1999.3:2", "24");
put("1999.3:3", "25");
put("1999.4:1", "1");
put("1999.4:2", "2");
put("1999.4:3.10", "42");
put("2001.11.7:1", "23");
put("2001.11.7:2", "24");
put("2001.11.7:3", "25");
put("2001.11.9:1", "1");
put("2001.11.9:2", "2");
put("2001.11.9:3", "3");
put("2001.12", "45");
}};
TreeMap<String, Object> originalMap = new TreeMap<>();
flatMap.forEach((key, value) -> processMap(key, value, originalMap));
System.out.println(flatMap);
System.out.println(originalMap);
}
@SuppressWarnings("unchecked")
private static void processMap(String flatMapKey,
String flatMapValue,
Map<String, Object> map) {
int dot = flatMapKey.indexOf('.');
int colon = flatMapKey.indexOf(':');
// nested map
if (dot > -1 && (dot < colon || colon == -1)) {
String key = flatMapKey.substring(0, dot);
Object nestedMap = map.get(key);
if (nestedMap == null) {
map.put(key, new TreeMap<>());
}
nestedMap = map.get(key);
processMap(flatMapKey.substring(dot + 1),
flatMapValue, (Map<String, Object>) nestedMap);
}
// nested list
else if (colon > -1 && (colon < dot || dot == -1)) {
String key = flatMapKey.substring(0, colon);
Object nestedList = map.get(key);
if (nestedList == null) {
map.put(key, new ArrayList<>());
}
nestedList = map.get(key);
processList(flatMapKey.substring(colon + 1),
flatMapValue, (List<Object>) nestedList);
}
// insert value
else if (dot == colon) {
map.put(flatMapKey, flatMapValue);
}
}
@SuppressWarnings("unchecked")
private static void processList(String flatMapKey,
String flatMapValue,
List<Object> list) {
int dot = flatMapKey.indexOf('.');
int colon = flatMapKey.indexOf(':');
// nested map
if (dot > -1 && (dot < colon || colon == -1)) {
int index = Integer.parseInt(flatMapKey.substring(0, dot));
while (list.size() < index) {
list.add(null);
}
Object nestedMap = list.get(index - 1);
if (nestedMap == null) {
list.set(index - 1, new TreeMap<>());
}
nestedMap = list.get(index - 1);
processMap(flatMapKey.substring(dot + 1),
flatMapValue, (Map<String, Object>) nestedMap);
}
// nested list
else if (colon > -1 && (colon < dot || dot == -1)) {
int index = Integer.parseInt(flatMapKey.substring(0, colon));
while (list.size() < index) {
list.add(null);
}
Object nestedList = list.get(index - 1);
if (nestedList == null) {
list.set(index - 1, new ArrayList<>());
}
nestedList = list.get(index - 1);
processList(flatMapKey.substring(colon + 1),
flatMapValue, (List<Object>) nestedList);
}
// insert value
else if (dot == colon) {
int index = Integer.parseInt(flatMapKey);
while (list.size() < index) {
list.add(null);
}
list.set(index - 1, flatMapValue);
}
}
Плоская карта:
{1999.3:1=23, 1999.3:2=24, 1999.3:3=25, 1999.4:1=1, 1999.4:2=2, 1999.4:3.10=42,
2001.11.7:1=23, 2001.11.7:2=24, 2001.11.7:3=25,
2001.11.9:1=1, 2001.11.9:2=2, 2001.11.9:3=3, 2001.12=45}
Исходное дерево:
{1999={3=[23, 24, 25], 4=[1, 2, {10=42}]},
2001={11={7=[23, 24, 25], 9=[1, 2, 3]}, 12=45}}
Решил добавить вторым ответом, так как подход тут совсем другой.
Можно из ваших данных сначала составить промежуточное дерево по типу Trie, где узел может быть как ключем, так и операцией, так и просто значением.
После построения Trie можно будет достаточно просто по нему пробежаться и восстановить ваше исходное дерево.
Пусть вас не смущает обилие кода, зато код достаточно структурированный и понятный + его легко адаптировать под новые типы данных.
Выглядит это так
class TrieNode
{
private TreeMap<String, TrieNode> children = new TreeMap<>();
private String value = null;
Методы для построения Trie
public void add(String template, String value){
ArrayList<String> splitted = splitString(template);
add(splitted, 0, value);
}
private void add( ArrayList<String> keys, int index, String value){
if (index >= keys.size()){
this.value = value;
return;
}
String key = keys.get(index);
if (children.containsKey(key)) children.get(key).add(keys, index+1, value);
else{
TrieNode node = new TrieNode();
node.add(keys, index+1, value);
children.put(key, node);
}
}
Тут мы разделим путь на части, то есть вот такой путь 1999.4:3.10 превратится в 1999+.+4+:+3+.+10и каждый элемент пути будет отдельным узлом + ещё узел для значения
private ArrayList<String> splitString(String str){
ArrayList<String> ret = new ArrayList<>();
int start = 0;
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
if (c == '.' || c == ':'){
str.substring(start, i);
ret.add(str.substring(start, i));
ret.add(str.substring(i, i+1));
start = i+1;
}
}
ret.add(str.substring(start));
return ret;
}
Метод для построения словаря. Он публичный так как мы знаем, что верхний уровень будет всегда словарем.
public TreeMap<String, Object> buildMap(){
TreeMap<String, Object> ret = new TreeMap<>();
children.forEach((k,node)->{
Object val = node.getObject();
ret.put(k, val);
});
return ret;
}
Метод для построения списка
public ArrayList<Object> buildList(){
ArrayList<Object> ret = new ArrayList<>();
children.forEach((k,node)->{
Object val = node.getObject();
ret.add(val);
});
return ret;
}
Метод для извлечения элемента словаря или списка. Результатом этого метода может быть словать, список или строка.
private Object getObject(){
if (this.value != null) return this.value;
for (Map.Entry<String, TrieNode> item : children.entrySet()){
if (item.getKey().equals(".")){
return item.getValue().buildMap();
}
if (item.getKey().equals(":")){
return item.getValue().buildList();
}
}
return null; // до этой точки мы никогда не должны дойти
}
}
Проверка выглядит так
public static void main(String[] args) {
TreeMap<String, String> flatMap = new TreeMap<String, String>() {{
put("1999.3:1", "23");
put("1999.3:2", "24");
put("1999.3:3", "25");
put("1999.4:1", "1");
put("1999.4:2", "2");
put("1999.4:3.10", "42");
put("2001.11.7:1", "23");
put("2001.11.7:2", "24");
put("2001.11.7:3", "25");
put("2001.11.9:1", "1");
put("2001.11.9:2", "2");
put("2001.11.9:3", "3");
put("2001.12", "45");
}};
System.out.println(flatMap);
TrieNode root = new TrieNode();
flatMap.forEach((key, value)->{
root.add(key, value);
});
System.out.println(root.buildMap());
}
Вывод
{1999.3:1=23, 1999.3:2=24, 1999.3:3=25, 1999.4:1=1, 1999.4:2=2, 1999.4:3.10=42, 2001.11.7:1=23, 2001.11.7:2=24, 2001.11.7:3=25, 2001.11.9:1=1, 2001.11.9:2=2, 2001.11.9:3=3, 2001.12=45}
{1999={3=[23, 24, 25], 4=[1, 2, {10=42}]}, 2001={11={7=[23, 24, 25], 9=[1, 2, 3]}, 12=45}}