如何实现嵌套列表迭代器

How to implement a Nested List Iterator

提问人:John Doe 提问时间:4/25/2022 最后编辑:Alexander IvanchenkoJohn Doe 更新时间:9/19/2022 访问量:805

问:

在一次采访中,我被问到这个问题:

“如何实现自定义嵌套列表迭代器?”

方法应首先返回每个列表的第一个元素,然后返回第二个元素,依此类推。next()

输入示例

[[1,2,3],[4,5],[6],[],[7,8,9]]

输出

[1,4,6,7,2,5,8,3,9]

存根代码

public class CustomListIterator implements Iterator<Integer> {
    
    public CustomListIterator(List<List<Integer>> nestedList) {
        
    }

    @Override
    public boolean hasNext() {
        
    }
    
    @Override
    public Integer next() {
        
    }
}

如何实施?

Java 列表 算法 迭代器 嵌套列表

评论


答:

0赞 Alexander Ivanchenko 4/25/2022 #1

您可以通过利用 和 的组合来实现这一点。这种方法使您可以摆脱维持任何头寸的必要性。ListIteratorIterator

一般的想法是创建一个迭代器列表和一个将遍历这些列表的 listIterator

方法对列表执行迭代,并检查它是否至少有一个未耗尽的迭代器。hasNext()

方法更复杂一些。首先,它将尝试将 listIterator 从最后一个元素后面的位置“重置”到列表第一个元素之前的位置。next()

然后在循环中,它将检查列表中的下一个迭代器,如果此迭代器耗尽,它将被删除。为了使此操作快速,迭代器存储在 .如果找到该元素,它将被退回。LinkedList

为了处理“空”迭代器出现在列表末尾并且 listIterator 到达最后一个位置,但列表中可能还有更多未访问的迭代器的情况,在循环的末尾放置了一个额外的检查。

在下面的实现中,为了简洁起见,我在内部使用了流和构造函数。即使你对流不是很熟悉,整体逻辑也应该很清楚,你可以很容易地用流代替它。hasNext()

public class CustomListIterator<T> implements Iterator<T> {
    private List<Iterator<T>> iterators;
    private ListIterator<Iterator<T>> listIterator;
    
    public CustomListIterator(List<List<T>> nestedList) {
        this.iterators = nestedList.stream()
            .map(List::iterator)
            .collect(Collectors.toCollection(LinkedList::new));
        
        this.listIterator = iterators.listIterator();
    }
    
    @Override
    public boolean hasNext() {        
        return iterators.stream().anyMatch(Iterator::hasNext);
    }
    
    @Override
    public T next() {
        if (!iterators.isEmpty() && !listIterator.hasNext()) tryReset();

        while (!iterators.isEmpty() && listIterator.hasNext()) {
            Iterator<T> current = listIterator.next();

            if (!current.hasNext()) {
                listIterator.remove(); // removing exhausted iterator
            } else {
                return current.next();
            }
    
            if (!listIterator.hasNext()) tryReset();
        }
        throw new IllegalStateException();
    }
    
    private void tryReset() {
        while (listIterator.hasPrevious()) {
            listIterator.previous();
        }
    }
}

main()-演示

public static void main(String[] args) {
    List<List<Integer>> numbers =
        List.of(List.of(1, 4, 6, 7),
                List.of(),
                List.of(2, 5),
                List.of(3));

    CustomListIterator<Integer> nestedIterator = new CustomListIterator<>(numbers);
    
    while (nestedIterator.hasNext()) {
        System.out.print(nestedIterator.next() + "\t");
    }
}

输出

1   2   3   4   5   6   7   
0赞 dani-vta 4/25/2022 #2

为了实现迭代器,我们需要一个光标来跟踪我们当前所在的元素。根据底层数据结构,所述光标可以是索引或指针,它将用于从一个元素前进到另一个元素。这是在 next() 方法中完成的,该方法返回当前元素,光标前进到下一个元素。

因为在您的问题中,您要求为泛型列表(不是专门针对 ArrayList 或 LinkedList)的自定义迭代器;我决定使用 int 索引作为光标,以便指向两种数据结构的元素。

为了回答您的问题,我将您的列表列表描绘成一个具有不同长度行的矩阵,我们首先遍历当前列的行(外部列表光标),然后移动到下一列(内部列表或子列表光标)。

class CustomListIteratorVert implements Iterator<Integer> {

    private List<List<Integer>> nestedList;
    private int listCur, subListCur;

    //value to understand when the iteration has completed since the "column" cursor has reached its end
    private int maxSublistSize;

    public CustomListIteratorVert(List<List<Integer>> nestedList) {
        this.nestedList = nestedList;
        this.listCur = 0;
        this.subListCur = 0;

        //Identifying the greates size among the sublists
        this.maxSublistSize = nestedList.stream().map(list -> list.size()).max(Integer::compareTo).orElse(0);
    }

    @Override
    public boolean hasNext() {
        //If the sublist cursor hasn't exceeded the last "column" then there are further elements to iterate (worst case scenario: only the current element)
        return subListCur < maxSublistSize;
    }

    @Override
    public Integer next() {
        //If the last column has been exceeded (no further elements to iterate) then no value is returned
        if (subListCur == maxSublistSize) {
            return null;
        }

        //Saving the current value to return
        Integer value = nestedList.get(listCur).get(subListCur);

        //Incrementing the outer list cursor to point to the next "row" (matrix visualization)
        if (listCur < nestedList.size() - 1) {
            listCur++;
        } else {
            //Incrementing the sublist cursors to point to the next column and resetting the "row" index (matrix visualization)
            subListCur++;
            listCur = 0;
        }

        // In case there are still elements left and the current cursors do not point to an existing element (sublist cursor greater than the sublist's size)
        // then these "empty spots" are skipped as long as the last column hasn't been exceeded or the next element hasn't been met
        while (subListCur < maxSublistSize && subListCur >= nestedList.get(listCur).size()) {
            if (listCur < nestedList.size() - 1) {
                listCur++;
            } else {
                subListCur++;
                listCur = 0;
            }
        }

        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        //Added extra empty lists to create more "empty sposts"
        List<List<Integer>> nestedList = new ArrayList<>(List.of(List.of(1, 2, 3), List.of(4, 5), List.of(6), List.of(), List.of(7, 8, 9), List.of(), List.of()));

        System.out.println("------------ Iterator ------------");
        CustomListIteratorVert custIt = new CustomListIteratorVert(nestedList);
        while (custIt.hasNext()) {
            System.out.println(custIt.next());
        }

        
        System.out.println("\n\n------------ Flattened List foreach loop ------------");
        List<Integer> listFlattenedFor = new ArrayList<>();
        for (List<Integer> sublist : nestedList) {
            listFlattenedFor.addAll(sublist);
        }
        listFlattenedFor.forEach(i -> System.out.println(i));


        System.out.println("\n\n------------ Flattened List Stream ------------");
        List<Integer> listFlattenedStream = new ArrayList<>();
        nestedList.stream().forEach(list -> listFlattenedStream.addAll(list));
        listFlattenedFor.forEach(i -> System.out.println(i));
    }
}

编辑

我的主要内容还包含您的列表的扁平化,因为它在问题编辑之前就已经问过了。


警告

第二种具有有状态 lambda 的扁平化方法应该只用于非并行流,并且绝对用于非并行流。此外,简单的循环方法不仅更安全,而且效率更高。我刚刚发布了流方法,只是为了展示实现相同结果的不同方法。