【问题标题】:Programming - Java - Disassociate the merge algorithm from the actions编程 - Java - 将合并算法与动作分离
【发布时间】:2015-12-01 14:49:32
【问题描述】:

我的意思是,目前我正在开发 T 类型元素的两个排序集合之间的合并(只要您提供一种比较类型的方法,类型并不重要,例如,在 Java 中,A Comparator<T> 将做这项工作)。

我不希望必须合并合并过程中涉及的两个数据结构(我不希望获得一个包含两个元素合并的全新结构)。我想要的是有某种合并过程的观察者,以便定义如何处理另一个类中的每个合并元素。例如,a 想要这样的东西:

merger.merge(leftCollection,rightCollection,theComparator,theObserver).

观察者是一个观察合并算法并收到动作通知的对象,我的意思是:

interface MergeObserver<T> {

            /**
             * Triggered when the merge algorithm decides to merge only the left entry.
             * This case correspond to the case when there is no equivalent entry on the right collection.
             */
            public void mergeLeft(T entry);

            /**
             * Triggered when the merge algorithm decides to merge both entries.
             * This case correspond to the case when there exists the same entry on both collections.
             */
            public void mergeBoth(T left, T right);

            /**
             * Triggered when the merge algorithm decides to merge only the right entry.
             * This case correspond to the case when there is no equivalent entry on the left collection.
             */
            public void mergeRight(T entry);
        }

我已经实现了排序集合的实现,但是......我想分享这种感觉,问题来了,如果有人以前想过这个,特别是在 Guava 库中,什么是正确的使用的术语。

我认为这是问这个问题的正确地方。对现有解决方案的任何想法将不胜感激。非常感谢你。不要删除我的感谢。 谢谢。

【问题讨论】:

  • 我怀疑这对于通用库来说太特殊了,因为 MergeObserver 抽象仅在合并(或以其他方式连接)两个数据集时有用,这在企业计算中通常由数据库完成,很少这样做。
  • 您是否使用比较器始终将来自left 的值与来自right 的值进行比较?如果是这种情况,请使用您自己的比较器包装给定的比较器,它只是委托比较,然后根据比较结果通知观察者(&lt;0 用于mergeLeft==0 用于mergeBoth 和@ 987654330@mergeRight)。

标签: java merge guava terminology


【解决方案1】:

用于分离数据结构的遍历和数据处理的两种最常用的模式是访问者模式和迭代器模式。这两种模式不仅可以应用于内存中存在的真实数据结构,还可以应用于“虚拟”数据结构(这可能不是正确的术语)。例如Java API 中的 List.subList 方法创建列表的一部分的视图。所以它返回的 List 对象只是对另一个列表数据的一部分的引用。当然你也可以组合数据结构。例如,您可以有一个方法,该方法将两个迭代器作为参数并返回一个新的迭代器,该迭代器将两个迭代器合并而不使用任何额外的内存,因为该合并列表实际上并不存在于 RAM 中。 如果您使用 Scala 而不是 Java,您将有很多可用的方法可以以多种不同的方式转换迭代器来实现这样的效果。

import java.util.function.Predicate;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.NoSuchElementException;

interface MyIterator<T> extends Iterator<T> {
  class Peekable<T> {
    private final MyIterator<T> iter;
    private T next = null;
    private boolean isNextBuffered = false;
    private boolean atEnd = false;

    private Peekable(MyIterator<T> iter) {
      this.iter = iter;
    }

    private void advance() {
      if(atEnd) throw new NoSuchElementException();
      if(iter.hasNext()) {
        next = iter.next();
        isNextBuffered = true;
      } else {
        atEnd = true;
      }
    }
    private boolean hasNext() {
      if(atEnd) return false;
      if(!isNextBuffered) advance();
      return !atEnd;
    }
    private T next() {
      T next = peek();
      advance();
      return next;
    }
    private T peek() {
      if(hasNext()) return next;
      throw new NoSuchElementException();
    }
  }

  static <T> MyIterator<T> of(BooleanSupplier hasNext, Supplier<T> next) {
    return new MyIterator<T>() {
      public boolean hasNext() {
        return hasNext.getAsBoolean();
      }
      public T next() {
        return next.get();
      }
    };
  }

  static <T> MyIterator<T> of(Iterator<T> iter) {
    return of(iter::hasNext, iter::next);
  }

  static MyIterator<Integer> range(int start, int end) {
    int[] value = {start};
    return of(() -> value[0] < end, () -> value[0]++);
  }

  default <R> MyIterator<R> map(Function<? super T,? extends R> mapper) {
    return of(this::hasNext, () -> mapper.apply(this.next()));
  }

  default MyIterator<T> filter(Predicate<? super T> predicate) {
    Peekable<T> iter = new Peekable<T>(this);

    return new MyIterator<T>() {
      public boolean hasNext() {
        while(iter.hasNext() && !predicate.test(iter.peek())) iter.advance();
        return iter.hasNext();
      }
      public T next() {
        hasNext();
        return iter.next();
      }
    };
  }

  default MyIterator<T> merge(MyIterator<T> other, BiPredicate<? super T,? super T> smallerEqual) {
    Peekable<T> iter1 = new Peekable<T>(this);
    Peekable<T> iter2 = new Peekable<T>(other);

    return of(() -> iter1.hasNext() || iter2.hasNext(),
              () -> {
                if(!iter1.hasNext()) return iter2.next();
                else if(!iter2.hasNext()) return iter1.next();
                else {
                  T elem1 = iter1.peek();
                  T elem2 = iter2.peek();
                  return smallerEqual.test(elem1, elem2) ? iter1.next() : iter2.next();
                }
              });
  }
}

interface MyIterable<T> extends Iterable<T> {
  default Iterator<T> iterator() {
    return myIterator();
  }

  MyIterator<T> myIterator();

  static <T> MyIterable<T> of(Supplier<MyIterator<T>> myIterator) {
    return new MyIterable<T>() {
      public MyIterator<T> myIterator() {
        return myIterator.get();
      }
    };
  }

  static <T> MyIterable<T> of(Iterable<T> iterable) {
    return of(() -> MyIterator.of(iterable.iterator()));
  }

  static MyIterable<Integer> range(int start, int end) {
    return of(() -> MyIterator.range(start, end));
  }

  default <R> MyIterable<R> map(Function<? super T,? extends R> mapper) {
    return of(() -> this.myIterator().map(mapper));
  }

  default MyIterable<T> filter(Predicate<? super T> predicate) {
    return of(() -> this.myIterator().filter(predicate));
  }

  default MyIterable<T> merge(MyIterable<T> other, BiPredicate<? super T,? super T> smallerEqual) {
    return of(() -> this.myIterator().merge(other.myIterator(), smallerEqual));
  }
}


public class Test {
  public static void main(String[] args) {
    MyIterable<Integer> iterable = MyIterable.range(0, 10);

    MyIterable<Integer> iter1 = iterable.map(x -> 2 * x).filter(x -> x < 10);
    MyIterable<Integer> iter2 = iterable.map(x -> 2 * x + 1).filter(x -> x < 10);
    MyIterable<Integer> iterMerged = iter1.merge(iter2, (x, y) -> x <= y);

    iter1.forEach(System.out::println);
    System.out.println();
    iter2.forEach(System.out::println);
    System.out.println();
    iterMerged.forEach(System.out::println);
  }
}

【讨论】:

  • 非常有趣的话,谢谢。是的,观察者可以被视为一种具有反向控制流的迭代器,我的意思是,带有回调(而不是像 next() 这样的直接调用,所以每次算法定义谁将成为下一个元素时,你都会被调用);所以这可能会导致这样一种想法,即观察者的具体类型可以更像我猜的访客。合并算法的特殊情况是两个元素相等的情况。反转控制流将是一个非常有趣的旅行...... Scala?我已经和 JAVA 结婚了;P。谢谢蜘蛛
  • 通过使用 Java 8 中引入的新功能,您也可以完成 Scala 在 Java 中的大部分功能。我添加了一个示例,展示了如何定义自己的可合并、过滤等迭代,而无需分配太多内存,因为没有创建实际的数据结构。先看代码底部的例子。它创建了两个可迭代对象,一个包含数字 (0, 2, 4, 6, 8),一个包含数字 (1, 3, 5, 7, 9),然后将两者合并为一个可迭代对象。
  • 哇....这是一个很好的例子..我缺乏一些用 JAVA 8 引入的语言元素压缩以便完全理解代码,所以我会采取一些直到我处理它...我仍然在底部得到应用程序的想法,我发现非常有趣(学习 java 8 的一个很好的理由).. 尽管如此,当两个相等元素根据比较器在两个集合,因为在某些情况下我需要同时进行,而在其他情况下只需要一个。
【解决方案2】:

可能更惯用的“java”是编写与侦听器的合并:

public interface Merger {
    public Collection<T> merge(Collection<T> left, Collection<T> right, Comparator comparator);
    public void addListener(Observer observer);
    public void notifyListener(Message message);
}

public interface Observer {
    public void notify(Message message);
}

【讨论】:

  • 嗯.. 很好的考虑@dwoz。感谢您的贡献...所以您同意该术语,您只提出了一种添加许多观察者的方法...是公平的,在我的特定情况下没有必要。你知道,我可以存档,虽然我可以说实际的实现可以更改为merger.merge(Collection&lt;T&gt; left, Collection&lt;T&gt; right, Comparator&lt;? super T&gt; theComparator,Collection&lt;MergeObserver&lt;T&gt;&gt; theObsersers)
  • @dwoz:为什么这会更惯用?
  • @meriton,我说它的主要原因是侦听器/观察者模式在 OP 的设计中有点颠倒了。在他的设计中,被观察对象必须了解观察者的内部结构,这与观察者模式的概念背道而驰。与其说它的“java 特性”,不如说他的实现是“面向对象的反模式”可能更贴切。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多