降低六向笛卡尔积的认知复杂性

时间:2018-01-24 14:54:21

标签: java

我有一段代码Cognitive Complexity为21

for (String item1 : itemList1){
    for (String item2 : itemList2){
        for (String item3 : itemList3){
            for (String item4 : itemList4){
               for (String item5 : itemList5){
                   for (String item6 : itemList6){
                       methodToRun(item1, item2, item3, item4, item5, item6);
                   }
               }
            }
        }
    }
}

我们的linter指定最大认知复杂度为15,所以我应该按照我们一直遵循的标准来减少它。

有人可以为这段代码提出替代解决方案吗?或者尽管复杂性太高,它仍然可以接受吗?

我知道这可能是个人意见,但我正在寻找以前遇到类似情况的人的真实解决方案或答案。

编辑:我无法从我正在开发的开发机器上访问很多库和软件包。我可以访问一些(列表太多),所以在建议使用之前请注意这一点。

6 个答案:

答案 0 :(得分:3)

您可以选择递归解决方案。可以说它的可读性较差,但嵌套级别要小得多,这降低了复杂性度量:

static void recursiveRun(List<List<String>> list, int pos, String[] item) {
    if (pos == 6) {
          methodToRun(item[0], item[1], item[2], item[3], item[4], item[5]);
    } else {
        for (String s : list.get(pos)) {
            item[pos] = s;
            recursiveRun(list, pos+1, item);
        }
    }
}

初始通话如下:

recursiveRun(
    Arrays.asList(itemList1, itemList2, itemList3, itemList4, itemList5, itemList6)
,   0
,   new String[6]
);

答案 1 :(得分:2)

Google Guava解决方案

将数据打包到List<List<String>>后,您就可以使用 n-ary Cartesian产品来保留Google Guava中实现的元素顺序(词典编纂)。

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

List<List<String>> input = Arrays.asList(
    ImmutableList.of("Mary", "Alice"),
    ImmutableList.of("Smith", "Darcy", "Brown"),
    ImmutableList.of("Ford", "Saab")
);

List<List<String>> result = Lists.cartesianProduct(input); //Cognitive Complexity of 0

for (List<String> shuffle: result) {
    System.out.println(String.join(",", shuffle));
}

...生产:

Mary,Smith,Ford
Mary,Smith,Saab
Mary,Darcy,Ford
Mary,Darcy,Saab
Mary,Brown,Ford
Mary,Brown,Saab
Alice,Smith,Ford
Alice,Smith,Saab
Alice,Darcy,Ford
Alice,Darcy,Saab
Alice,Brown,Ford
Alice,Brown,Saab

纯Java半解决方案

这是一个快速解决方案,其中包含3个列表的硬编码值,可能会在不产生太多复杂性惩罚的情况下进行推广。它基本上会做一些简洁(又名难以遵循)的索引计算。

String[] list0 = new String[] {"0", "1"};
String[] list1 = new String[] {"4", "5"};
String[] list2 = new String[] {"8", "9"};

int[] indexes = new int[3];

long totalPermutations = list0.length * list1.length * list2.length;

for(int i = 0; i < totalPermutations; i++) {
    indexes[0] = i % list0.length;
    indexes[1] = (i / list0.length) % list1.length;
    indexes[2] = (i / (list0.length * list1.length)) % list2.length;
    System.out.println(list0[indexes[0]] + "," + list1[indexes[1]] + "," + list2[indexes[2]]);

}

度量标准讨论

纯Java解决方案是一个完美的例子,为了保持度量标准的快乐,我们实际上增加了复杂性和可维护性。

整个指数计算都非常可怕,并且很少有人去做对。无论如何,一般解决方案很可能会花费一些惩罚,因为需要进行迭代。我在网上找到的其他解决方案(包括递归和功能)并不比一堆嵌套循环更清晰。

在这里发明笛卡儿的产品例程将使IMO更加复杂(即使评分较低的复杂性)也无法理解。

软件必须建立在抽象的基础上,并且使用开放的,精心设计的第三方依赖性可以使整个问题得到很好的解决。

答案 2 :(得分:2)

这是一个基于迭代器的解决方案。

class CartesianProductIterator<T> implements Iterator<List<T>>, Iterable<List<T>> {
    private List<List<T>> data;
    private int size;
    private int[] sizes;
    private int[] cursors;
    private boolean done;

    public CartesianProductIterator(List<List<T>> data) {
        this.data = data;
        this.size = data.size();
        this.sizes = new int[this.size];
        this.cursors = new int[this.size];
        setSizes(data);
    }

    @Override
    public boolean hasNext() {return !done;}

    @Override
    public List<T> next() {
        if (! hasNext()) throw new NoSuchElementException();
        ArrayList<T> tuple = new ArrayList<>();
        for (int i = 0; i < size; i++) {tuple.add(data.get(i).get(cursors[i]));}
        updateCursors();
        return tuple;
    }

    private void updateCursors() {
        for (int i = size - 1; i >= 0; i--) {
            if (cursors[i] < sizes[i] - 1) {
                cursors[i]++;
                break;
            } else {
                cursors[i] = 0;
                if (i == 0) done = true;
            }
        }
    }

    private void setSizes(List<List<T>> data) {
        for (int i = 0; i < size; i++) {sizes[i] = data.get(i).size();}
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("remove is not supported here");
    }

    @Override
    public Iterator<List<T>> iterator() {return this;}
}

可用于按需创建交叉产品

List<List<String>> data = new ArrayList<>();
data.add(Arrays.asList("a", "b", "c"));
data.add(Arrays.asList("1", "2"));
data.add(Arrays.asList("A", "B", "C"));

Iterator<List<String>> dataIterator = new CartesianProductIterator<String>(data);
while (dataIterator.hasNext()) {
    System.out.println(dataIterator.next());
}

现在使用双Iterable / Iterator接口可以替代地用作

for(List<String>  combination: new CartesianProductIterator<>(data)) {
        System.out.println(combination);
}

答案 3 :(得分:0)

我想用一些可行的代码跟进我的评论。然后我意识到递归部分非常像@dasblinkenlight(我向你保证这不是有意的),所以我犹豫发布这个(只是参考他的)。然而,这更通用一点。我正在推动@dasblinkenlight。

public class CartesianProduct {

public static void main(String[] args) {

    List<String> l1 = new ArrayList<String>(Arrays.asList("a", "b", "c"));
    List<String> l2 = new ArrayList<String>(Arrays.asList("d", "e", "f"));
    List<String> l3 = new ArrayList<String>(Arrays.asList("g", "h"));

    processCartesianProduct(new MyCartesianProductTask(), l1, l2, l3);
}

private static void processCartesianProduct(CartesianProductTask task, List<String>... lists) {
    processCP(task, new String[lists.length], 0, lists);

}

private static void processCP(CartesianProductTask task, String[] element, int pos, List<String>... lists) {
    if (pos == lists.length)
        task.doTask(element);
    else {
        for (String s : lists[pos]) {
            element[pos] = s;
            processCP(task, element, pos+1, lists);
        }
    }
}


interface CartesianProductTask {
    public void doTask(String[] element);
}

static class MyCartesianProductTask implements CartesianProductTask {

    @Override
    public void doTask(String[] element) {
        System.out.println("Performing task on: "+Arrays.asList(element));
        // Business logic goes here
    }

}

}

产地:

Performing task on: [a, d, g]
Performing task on: [a, d, h]
Performing task on: [a, e, g]
Performing task on: [a, e, h]
Performing task on: [a, f, g]
Performing task on: [a, f, h]
Performing task on: [b, d, g]
Performing task on: [b, d, h]
Performing task on: [b, e, g]
Performing task on: [b, e, h]
Performing task on: [b, f, g]
Performing task on: [b, f, h]
Performing task on: [c, d, g]
Performing task on: [c, d, h]
Performing task on: [c, e, g]
Performing task on: [c, e, h]
Performing task on: [c, f, g]
Performing task on: [c, f, h]

答案 4 :(得分:0)

这是一个基于计算笛卡尔积中的索引的解决方案:

  • 通过将下一个子空间的大小乘以当前向量的大小来计算每个子空间的大小; a&#34;点空间的大小&#34;是1。
  • 迭代空间中的所有索引。空间i中的索引可以通过除以子空间的大小和将结果与向量的大小进行MOD来计算。

这是一个实现:

static void iterateCartesian(List<List<String>> lists) {
    int[] size = new int[lists.size()+1];
    size[lists.size()] = 1;
    for (int i = lists.size()-1 ; i >= 0 ; i--) {
        size[i] = size[i+1]*lists.get(i).size();
    }
    for (int i = 0 ; i != size[0] ; i++) {
        methodToRun(
            lists.get(0).get((i/size[1]) % lists.get(0).size())
        ,   lists.get(1).get((i/size[2]) % lists.get(1).size())
        ,   lists.get(2).get((i/size[3]) % lists.get(2).size())
        ,   lists.get(3).get((i/size[4]) % lists.get(3).size())
        ,   lists.get(4).get((i/size[5]) % lists.get(4).size())
        ,   lists.get(5).get((i/size[6]) % lists.get(5).size())
        );
    }
}

这个解决方案相当平稳,#34;但它需要相当数量的定量能力来理解。

Demo.

答案 5 :(得分:-1)

我不主张采用这种方法,但我认为它(a)会抑制警告,(b)可能会让你思考如何突破代码以帮助它更有意义。我发现它比原版更清晰或更具可读性,但根据所有术语在您的上下文中的含义,它可能会提示有用的新方向。

Select Rules -> Add Rule -> required tag