Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update an deeply nested array in a freezed class in flutter

I have a freezed class in flutter as follows:

@freezed
abstract class Data with _$Data {
  const factory Data({
    String id,
    String name,
    String parentId,//null if it is the root element
    @Default([]) List<Data> children,
  }) = _Data;
}

The class contains a property called children which is a list of the same class i.e Data.

The max nesting that is currently allowed 20 levels deep. The problem I am facing is how to update a particular deeply nested children list by adding or removing items to it. Also this updating should be done keeping immutability and return a new updated Data class.

I tried using copyWith() method on the freezed class but could not figure out the same when there is deeply nested as in my scenario.

like image 954
boomboxboy Avatar asked Jun 11 '20 15:06

boomboxboy


Video Answer


1 Answers

I think it's not enough info to write a precise answer. But I will try to show an idea of how to do it.

Here is the code:

/// recursive func to update children (should not be used directly, use `updateChildren` instead)
Data updateChildrenAt(Data data, List<Data> Function(List<Data>) update,
    List<int> indices, int depth) {
  final children = data.children;
  List<Data> newChildren;

  if (depth < indices.length) {
    final index = indices[depth];
    final child = children[index];
    newChildren = children.toList();
    newChildren[index] = updateChildrenAt(child, update, indices, depth + 1);
  } else {
    newChildren = update(children);
  }
  return data.copyWith(children: newChildren);
}

/// func to update children in the tree
Data updateChildren(
    Data data, List<Data> Function(List<Data>) update, List<int> indices) {
  return updateChildrenAt(data, update, indices, 0);
}

It is a simple recursive function (you should be aware of the fact, that if the depth is too deep it would fail with an exception, but depth like 20 is OK) that searches for children list to update by provided path (indices), because I don't know exactly what info you have and decided to write some kind of a generic algo (I hope, so...).

And here is an example how to use it:

import 'data.dart';

/// func for demonstration
Data data(String name, [List<Data> children]) {
  return Data(name: name, children: children ?? []);
}

/// func for demonstration
String dataToStr(Data data, [int level = 0]) {
  var str = ' ' * level + data.name + ':\n';

  for (var i = 0; i < data.children.length; ++i) {
    str += dataToStr(data.children[i], level + 1);
  }

  return str;
}

/// func for demonstration
void printData(Data data) {
  print(dataToStr(data, 0));
}

void main(List<String> arguments) {
  final r = data('root', [
    data('level 1', [
      data('level 2_1', [
        data('level 3', [
          data('level 4', [
            data('level 5_1'),
            data('level 5_2',
                [data('level 6_1'), data('level 6_2'), data('level 6_3')]),
            data('level 5_3')
          ])
        ])
      ]),
      data('level_2_2'),
    ]),
  ]);

  // print initial value
  printData(r);
  // root:
  // level 1:
  //  level 2_1:
  //   level 3:
  //    level 4:
  //     level 5_1:
  //     level 5_2:
  //      level 6_1:
  //      level 6_2:
  //      level 6_3:
  //     level 5_3:
  //  level_2_2:

  // removing first element and adding new element at the end deep in the tree
  printData(updateChildren(r, (List<Data> children) {
    return children.sublist(1, children.length)..add(data('level 7 (added)'));
  }, [0, 0, 0, 0, 1]));
  // root:
  // level 1:
  //  level 2_1:
  //   level 3:
  //    level 4:
  //     level 5_1:
  //     level 5_2:
  //      level 6_2:
  //      level 6_3:
  //      level 7 (added):
  //     level 5_3:
  //  level_2_2:

  // adding new element in the start and in the end not very deep in the tree
  printData(updateChildren(r, (List<Data> children) {
    return [data('level 1_0 (added)'), ...children, data('level 1_2 (added)')];
  }, []));
  // root:
  // level 1_0 (added):
  // level 1:
  //  level 2_1:
  //   level 3:
  //    level 4:
  //     level 5_1:
  //     level 5_2:
  //      level 6_1:
  //      level 6_2:
  //      level 6_3:
  //     level 5_3:
  //  level_2_2:
  // level 1_2 (added):
}

You should be aware of the fact that all checks have been omitted for simplicity (I think it's not very difficult to add them, btw).

Upd: What are indices in this example?

As I said due to lack of precise info (or maybe I haven't understood it well enough) I've decided to use indices.

The problem is before the update a list of children we are to find that list (the list we want to update). We can't find it only with info about the depth because the specified data represents a tree-like data structure. Let's look at an example:

                        root
                          |
0                     [level 1 ]
                          |
1                    [level 2_1, level 2_2]
                          |          |
2                    [level 3]      [ ]
                          |
3                    [level 4]
                          |
4 [level 5_1,         level 5_2,             level 5_3]
       |                  |                      |
5     [ ]   [level 6_1, level 6_2, level 6_3]   [ ]

It's a representation of a root object from an example code (simplified, I hope I've made it right...). If we want (like in the first example) to update a list of [level 6_1, level 6_2, level 6_3] info about depth is not enough. Because on the same depth there are 3 lists. What exactly list we want to update? That's why I've added indices. The numbers 0-5 mean the required indices list length on specified depth. And index in this list means what exactly list we want to update on specified depth (because, as you can see on the image on every depth could be any number of lists). In the first example, we are updating [level 6_1, level 6_2, level 6_3] list. And indices just like specify a precise path to it. So [0, 0, 0, 0, 1] means that we want to update root.children[0].children[0].children[0].children[0].children[1].children list. If we want, for example, to update an empty list under level 2_2 we should be able to do it with indices: [0, 1]. In the last example, we specified an empty indices, that's mean we want to update the root children list, i.e. [level 1] list.

Because you haven't specified enough info, I couldn't say is this the code you really want. Maybe you should provide a bit more info about a task you want to accomplish. For example, maybe you already have a list that you want to update, in that case, the code above would not work without modification. Another example, maybe you want to update all lists on a specified depth. In that case, we need to change code either. Or maybe a data structure is wrong (for example), and should be changed. I hope it's a bit clearer now.

like image 199
ChessMax Avatar answered Sep 29 '22 23:09

ChessMax