3

假设我们有一个迭代器:

Iterator i;

和两个Java方法:

static X f(Iterator i)
static Y g(Iterator i)

它们都通过迭代器进行处理并产生结果,例如“sum”、“product”或其他内容。

假设这些调用对给定的结果i是:

x == f(i)
y == g(i)

假设我们还有另一个类:

class Pair<X,Y>
{
  public X x;
  public Y y;
}

我想写一个方法:

static Pair<X,Y> h(Iterator i)

以便:

h(i) == Pair<X,Y>(x,y);

如何在h不修改或重写的情况下编写fg?问题是我可能无法复制迭代器(例如,它可能是输入流),所以我觉得两者fg必须“并行”使用迭代器。我宁愿不将迭代器的全部内容放入列表中,这可能是不可能的,就好像我正在流式传输一个大型源我可能没有足够的内存一样。但是,我不要求解决方案具有多个线程,并且如果可能的话,我更喜欢更简单的方法。

4

3 回答 3

2

仅使用您拥有的迭代器是不可能的。您将需要一个非常精细的方案,类似于 *nix 的tee命令,仅适用于 Java 迭代器:一个包含初始迭代器并根据需要使用它的对象,并且可以被请求生成一个或多个“输出”迭代器。在内部,您可以使用队列来存储未使用的元素。

如果您不想要多线程,那么从理论上很容易实现,您绝对必须缓存初始迭代器的整个生成。这是因为您的两个迭代器消耗方法都基于“拉”方法,并且会坚持在返回之前消耗整个序列。

顺便说一句,在 Clojure 中,项目lamina做了一些非常相似的事情,只是不是使用 Java 迭代器,而是使用了一个称为“通道”的概念。

于 2013-06-05T08:04:38.007 回答
1

诀窍是让堆栈跟踪知道这两个方法中的哪一个正在调用 iterator.next 方法。

只要两个调用者都没有调用 next(),你就阻止前面的调用者。

  • 这不是优化的。
  • 我试图让这段代码线程安全,但因为我写得很快,一些同步问题可能仍然存在。我让你复习一下;)
  • 我让你删除很多 System.out.println

originalIterator 基于 List,但使用您希望的数据结构。如您所见,stream.iterrator() 只被调用一次。

public static class InterleavingIterator<E> implements Iterator<E> {

    private boolean hasNext;
    private E next;

    private final Iterator<E> originalIterator;

    private final Map<String, Integer> stepCaller = Collections.synchronizedMap(new HashMap<String, Integer>());
    private final AtomicInteger curStep = new AtomicInteger(0);

    public InterleavingIterator(Iterator<E> originalIterator, String caller1, String caller2) {
        this.originalIterator = originalIterator;
        stepCaller.put(caller1, 0);
        stepCaller.put(caller2, 0);

        hasNext = originalIterator.hasNext();
    }

    public boolean hasNext() {
        return hasNext;
    }

    public E next() {
        String caller = getCurrentCaller();
        int currentStep = curStep.get();
        System.out.println("caller is " + caller);
        if (stepCaller.get(caller) == currentStep) {
            System.out.println("Caller " + caller + " is on current step. We need to move on.");
            // we should go on next step. But first check that all caller are done with this step.
            while (otherCallerBehind(currentStep)) {
                System.out.println("Other caller are behind. Waiting for them...");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("Ok, everybody is on current step. Going on.");

            synchronized (curStep) {
                if (currentStep == curStep.get()) {
                    // ok, go on.
                    curStep.incrementAndGet();
                    next = hasNext ? originalIterator.next() : null;
                    hasNext = originalIterator.hasNext();
                    System.out.println("hasNext=" + hasNext + ", next=" + next + ", currentStep=" + curStep.get());
                }
            }

        }
        E next2 = next;
        stepCaller.put(caller, stepCaller.get(caller) + 1);
        System.out.println("Caller " + caller + " is now at step " + stepCaller.get(caller) + ". Returning already fetch next : "
                + next);
        return next2;
    }

    private boolean otherCallerBehind(int currentStep) {
        for (Integer step : stepCaller.values()) {
            if (step < currentStep) {
                return true;
            }
        }
        return false;
    }

    public void remove() {
        throw new RuntimeException("Method remove not supported");
    }

    public String getCurrentCaller() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        //            System.out.println(stackTraceElements[3].getMethodName());
        return stackTraceElements[3].getMethodName();
    }

}

public static void main(String[] args) {

    List<Integer> stream = Arrays.asList(1, 2, 3, 4, 5);

    final InterleavingIterator<Integer> interleavingIterator = new InterleavingIterator<Integer>(stream.iterator(), "f", "g");

    final AtomicInteger fres = new AtomicInteger(-1);
    final AtomicInteger gres = new AtomicInteger(-1);

    new Thread() {
        @Override
        public void run() {
            fres.set(f(interleavingIterator));
        };
    }.start();

    new Thread() {
        @Override
        public void run() {
            gres.set(g(interleavingIterator));
        };
    }.start();

    while (fres.get() == -1 && gres.get() == -1) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    System.out.println(fres);
    System.out.println(gres);
}

public static Integer f(Iterator<Integer> i) {
    Integer sum = 0;
    while (i.hasNext()) {
        sum += i.next();
    }
    return sum;
}

public static Integer g(Iterator<Integer> i) {
    Integer prod = 1;
    while (i.hasNext()) {
        prod *= i.next();
    }
    return prod;
}
于 2013-06-05T12:03:24.213 回答
0

由于您可能知道要迭代的内容,因此您可以获取值,将其放入新列表中,然后将其迭代器传递给函数。

Pair<X,Y> getPair(Iterator i) {
  ValObj value = null;
  List<ValObj> artList = new LinkedList<ValObj>();

  while ((value=i.next())!=null) {
    artList.add(value);
  } 

  X x = f(artList.iterator());
  Y y = g(artList.iterator());

  return new Pair<X,Y>(x,y);
}

现在我注意到您不想将内容存储在列表中。如果是这样的话,如果没有关于迭代器的更多细节,我相信这是不可能的。

于 2013-06-05T07:58:09.193 回答