按标准将数组划分为子数组的最优雅方法

时间:2014-05-19 15:50:22

标签: java arrays

我有:非常大的数组:{o1,o2,o3,...,o100000}。在某些情况下,元素序列具有相同的字段值。我的意思是o1.getField()等于o2.getField()等于o3.getField()。让我们说o1,o2和o3 类似

我想:对每个类似的元素子阵列进行一些事后处理(例如{o1, o2, o3}{o4, o5},...)。

这很重要:由于数组非常大,缓存(意味着将数组移动到另一个集合或者部署其中)是不可接受的!

问题:最优雅的方式是什么?

P.S。我没有必要划分整个数组而只需要进行postAction。我可以获得第一个{o1, o2, o3},制作postAction,而不是获得第二个等等......

4 个答案:

答案 0 :(得分:1)

我首先要挑战"阵列无法复制"前提。如果您使用System.arrayCopy()

,阵列复制速度非常快

这是我写的一个小样本程序,用于创建,填充和复制100,000,000个元素的数组。

public static void main(final String[] args) {

    final Stopwatch stopwatch = new Stopwatch().start();
    final String[] arr = new String[100_000_000];
    Arrays.fill(arr, "foo");

    final String[] arr2 = new String[arr.length];
    System.arraycopy(arr, 0, arr2, 0, arr.length);
    stopwatch.stop();
    System.out.println(stopwatch.elapsed(MILLISECONDS));

}

在我令人印象深刻的机器上,这需要683毫秒。

  1. 这是不可接受的,因为代码可能会在比我快得多的机器上运行吗?
  2. 或者您的阵列是否明显大于100000000个元素?
  3. 如果这些都不是,我会选择一些复制解决方案。

答案 1 :(得分:1)

我假设你正在处理T类型的对象并且有一个方法isSimilar(T o1, T o2)。 我还假设你有一个Processor<T>课,其方法为postAction(Iterator<T> i)(可以适应postAction(Iterable<T> i))。

然后我会继续这些行。它只是一个方向,不幸的是我没有任何东西可以测试甚至编译。

public <T>  void process(T[] array, Processor<T> p) {
  for (int i=0, j=1; i<array.length && j<array.length; i=j, j++) {
    while (j<array.length && isSimilar(array[i], array[j])) {
      j++;
    }
    ArrayIterator<T> ai = new ArrayIterator<T>(array, i, j-1);
    p.postAction(ai);
  }
}

class ArrayIterator<T> implements Iterator<T> {
  T[] array;
  int current;
  int end;

  public ArrayIterator(T[] a, int s, int e) {
    array = a;
    current = s;
    end = e;
  }

  public boolean hasNext() {
    return current <= end;
  }

  public T next() {
    return array[current++];
  }
}

如果postAction采用List或其他强加的集合,您可以创建由数组备份的此类集合,例如Arrays.asList(),但它将是更多的样板代码。如果postAction采用数组,我认为你必须复制......

答案 2 :(得分:1)

我们有这个班级

public static class MisteriousItem {

    public int oddlyFamiliarValue;

    public MisteriousItem(int oddlyFamiliarValue) {
        this.oddlyFamiliarValue = oddlyFamiliarValue;
    }

}
我们用来放入数组的

    Random rand = new Random();
    MisteriousItem[] magicBox = new MisteriousItem[1_000];
    for (int i = 0; i < magicBox.length; i++) {
        magicBox[i] = new MisteriousItem(rand.nextInt(3));
    }
然后我们准备了一些价值

    List<Entry<Integer, String>> something;
    something = new ArrayList<>();

    MisteriousItem x = magicBox[0];
    StringBuilder sb = new StringBuilder("{");
    sb.append(x.oddlyFamiliarValue);
    int id = 0;

我知道你说没有副本,但这不是算法的一部分,只是与熟悉的对象有关

现在我们迭代

    for (int i = 1; i < magicBox.length; i++) {
        MisteriousItem mi = magicBox[i];
        if (mi.oddlyFamiliarValue == x.oddlyFamiliarValue) {
            sb.append(",");
            sb.append(id++);
        } else {
            sb.append("}");
            something.add(new Entry<>(x.oddlyFamiliarValue, sb.toString()));
            x = mi;
            sb = new StringBuilder("{");
            sb.append(id++);
        }
    }

正如您所看到的,我们正在获取第一个对象并开始处理,然后从第二个项开始迭代整个数组,在执行任何操作之前,我们检查它是否与先前的对象相似。然后我们根据比较的结果采取行动。 如果您正在寻找一些更优雅的方式,请发表评论并说明应该有什么不同。

修改

也许这就是你正在寻找的更多

    int end=0;
    int start= 1;
    MisteriousItem x = magicBox[0];
    for (int i = 1; i < magicBox.length; i++) {
        MisteriousItem mi = magicBox[i];
        if (mi.oddlyFamiliarValue == x.oddlyFamiliarValue) {
            end++;
        } else {
            process(magicBox,start,end);
            //process lost starting from start(inclusive) to end(exclusive)
            start=i;
        }
    }

答案 3 :(得分:0)

getField进行就地排序,然后按顺序进行迭代,仅在getField更改时调用操作。我相信这是唯一不会产生相同长度数组开销的解决方案(因为我能想到的任何其他解决方案都涉及一些机制来跟踪您使用的getField值。) / p>