创建自定义交替数字流

时间:2017-12-30 15:57:54

标签: java java-8 java-stream

如何创建一个从给定顺序范围中间开始的IntStream,然后从中间开始流式传输下一个数字,并交替向左和向右。例如,对于给定的连续数字范围1 2 3 4 5,自定义序列将为3 2 4 1 53 4 2 5 1,具体取决于您是先从左侧还是右侧开始。

我基本上试图从中间开始迭代一个数组并向外均匀地向外(不要先完​​全向左或向右)。

我使用for循环尝试了这个但是代码很麻烦,我认为只需排列一个集合或数字流而不是检查它就会更好在运行中(因为必须检查的所有索引越界异常)。这是原始代码,我认为作为预先计算的整数流更好:

        int middle = myArray.length / 2;
        Object value = myArray[middle]; //have to reference middle since the for loop won't
        //do operation on value
        for (int offset = 1; true; offset++) {
            int nextRight = middle + offset;
            int nextLeft = middle - offset;
            if (nextRight < myArray.length) { // Have to guard against exception, but can't catch exception ahead of time because left or null may not be empty.
                Object value = myArray[nextRight];
                //do operation on value
            }
            if (nextLeft >= 0) {
                Object value = myArray[nextRight];
                //do operation on value
            }
            if (nextRight >= myArray.length) {
                break; //exit logic
            }
            if (nextLeft < 0) {
                break; //exit logic
            }
        }

7 个答案:

答案 0 :(得分:8)

试试这个:

import static java.lang.Integer.signum;

static IntStream altSeq(int n) {
    return IntStream.iterate(2 * (n % 2) - 1, i -> -i - signum(i))
                    .map(i -> i / 2 + (n + 1) / 2)
                    .limit(n);
}

例如,altSeq(5)生成:

[3, 2, 4, 1, 5]

并运行altSeq(6)会产生:

[3, 4, 2, 5, 1, 6]

简而言之,我们生成一个交替符号的递增序列:

1, -2, 3, -4, 5, ...

那是i -> -i - signum(i)表达式的作用。然后,我们除以2以便从中点获得偏移量:

0, -1, 1, -2, 2, ...

i / 2表达式第一项map的来源。然后我们添加范围1..n的中点(n + 1) / 2map表达式的第二项。对于n = 5,这给了我们

3, 2, 4, 1, 5, ...

从1开始适用于奇数长度序列。对于偶数长度,我们希望从-1开始。种子表达式2 * (n % 2) - 1计算偶数和奇数长度序列的正确种子值。

最后,我们应用limit(n)来终止序列。

答案 1 :(得分:2)

好吧,我能想到这一点,不知道它是否符合你的要求:

public static IntStream generate(int[] x, boolean direction) {
    int length = x.length / 2;

    IntStream right = IntStream.range(1, length + 1)
            .mapToObj(i -> {
                return direction ? new Integer[] { i, -1 * i } : new Integer[] { -1 * i, i };
            })
            .flatMap(Arrays::stream)
            .mapToInt(i -> x[length + i]);

    return IntStream.concat(IntStream.of(x[length]), right);
}

答案 2 :(得分:2)

此解决方案使用迭代器和流:

boolean toLeft = false;
int size = 5;

int half = size % 2 == 0 ? size / 2 : size / 2 + 1;
IntStream inferiorStream = IntStream.iterate (half, x -> x - 1);
IntStream superiorStream = IntStream.iterate (half, x -> x + 1);

OfInt a = toLeft 
          ? inferiorStream.iterator () 
          : superiorStream.iterator ();
OfInt b = toLeft 
          ? superiorStream.skip (1).iterator () 
          : inferiorStream.skip (1).iterator ();

IntStream stream = Stream.generate (() -> IntStream.concat (
    a.hasNext () ? IntStream.of (a.nextInt ()) : IntStream.empty (),
    b.hasNext () ? IntStream.of (b.nextInt ()) : IntStream.empty ()))
    .flatMapToInt (Function.identity ())
    .limit (size);

stream.forEach (System.out :: println);

输出(toLeft = true):

3
4
2
5
1

输出(toLeft = false):

3
2
4
1
5

答案 3 :(得分:2)

既然你说过,你想用序列迭代一个数组,我把它改为生成包括零的数字,但不包括 n ,所以你可以直接传入一个数组长度并得到有效指数。

然后你可以使用

static IntStream altSeq(int n) {
    int mid = (n-1)/2;
    return IntStream.rangeClosed(1, n)
                    .map(i -> mid + (i>>>1)*signum(rotateRight(i,1)));
}

该方法类似于Stuart Marks’ answer,但使用IntStream.rangeClosed()作为基础,创建大小的流,其效果比创建无限流更有效(如iterate)并应用limit,尤其适用于toArraycountparallel流等操作。换句话说,性能与按通常顺序迭代范围/数组相同。

通过使用最低位作为符号来转换自然数范围,该符号对于递增的数字是交替的,并且将数字向右移位一位,这相当于将数字的大小除以2。

每个元素只执行一次比特移位的替代符号

static IntStream altSeq(int n) {
    int mid = (n-1)/2;
    return IntStream.rangeClosed(1, n)
                    .map(i -> Integer.rotateRight(i, 1))
                    .map(i -> mid + (i&Integer.MAX_VALUE)*signum(i));
}

答案 4 :(得分:1)

我们可以自定义IntStream.generate方法,以便按照我们需要的顺序生成IntStream

public void run(String... args) throws Exception {
    final int[] arr = IntStream.range(0, 9).toArray(); //test data
    int mid = arr.length / 2;
    SeqGen seq = new SeqGen(arr, mid);
    List<Integer> ints = IntStream.generate(() -> seq.gen())
            .limit(arr.length)
            .boxed()
            .collect(Collectors.toList());
    System.out.println(ints);
}

SeqGen

private class SeqGen {
    int[] arr;
    int start;
    int curr;
    boolean flag;

    public SeqGen(int[] arr, int start) {
        this.arr = arr;
        this.start = start;
    }

    public int gen() {
        int ret = -1;
        int l = arr[start + curr];
        int r = arr[arr.length - curr - start - 1];
        if (!flag) {
            ret = l;
            curr++;
        } else {
            ret = r;
        }
        flag = !flag;
        return ret;
    }
}

输出

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

答案 5 :(得分:1)

由于您愿意考虑一个集合,以下&#34;低技术&#34;方法很简单:

public static List<Integer> f(int len) {
    int offset = len / 2;

    ArrayList<Integer> indices = new ArrayList<>(len);

    for(int i = 0 ; i < len; i++) {
        int index = offset + i * direction(i);
        indices.add(index);
        offset = index;
    }

    return indices;
}


private static int direction(int size) {
    return (size & 1) == 0 ? 1 : -1;
}

f(5),的调用会返回:[2, 1, 3, 0, 4]

可以通过更改direction()来修改方向(从左到右或从右到左)。如果您确实需要基于1的索引,请修改f()以获得:indices.add(index+1)

答案 6 :(得分:1)

使用公式为从开始到结束的任何顺序范围生成流

public static IntStream shuffle(int start, int end){
    int size = end - start + 1;
    int center = start + ((size + 1) >> 1) - 1;
    int even = (size + 1) & 1;
    int direction = 1 - (even << 1); //for left first: (even << 1) - 1;
    return IntStream.range(1, size + 1)
            .map(i -> center + (even + i * direction * ((((i + 1) & 1) << 1) - 1)) / 2);
}