Reputation: 25902
How can I make an IntStream
that starts in the middle of a given sequential range, and then streams the next numbers starting in the middle and alternating to the left and right. For example, for a given sequential range of numbers 1 2 3 4 5
, the custom sequence would be 3 2 4 1 5
or 3 4 2 5 1
depending whether you start with left or right first.
I basically am trying to iterate through an array starting from the middle and going outward evenly (not going to the left or right fully first).
I have tried this using just for
loops but the code is messy and I think it would be much better to just line up a collection or stream of numbers instead of checking for it on the fly (because of all the index out of bounds exceptions that have to be checked for). Here is the original code that I think would be much better as a pre computed stream of ints:
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
}
}
Upvotes: 12
Views: 1274
Reputation: 31
Used formula to generate stream for any sequential range from start to end
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);
}
Upvotes: 1
Reputation: 298439
Since you said, you want to use the sequence to iterate over an array, I changed it to produce numbers including zero, but excluding n, so you can directly pass in an array length and get valid indices.
Then you can use
static IntStream altSeq(int n) {
int mid = (n-1)/2;
return IntStream.rangeClosed(1, n)
.map(i -> mid + (i>>>1)*signum(rotateRight(i,1)));
}
The approach is similar to Stuart Marks’ answer, but uses an IntStream.rangeClosed()
as base, which creates a sized stream, which works much more efficient than creating an infinite stream (like iterate
) and applying a limit
, especially for operations like toArray
, count
, and for parallel
streams. In other words, the performance is on par with iterating over the range/array in the usual order.
The natural number range is converted by using the lowest bit as sign, which is alternating for ascending numbers, and shifting the numbers one bit to the right, which is equivalent to dividing the magnitude of the numbers by two.
An alternative notation performing only one bitshift per element would be
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));
}
Upvotes: 2
Reputation: 132460
Try this:
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);
}
For example, altSeq(5)
produces:
[3, 2, 4, 1, 5]
and running altSeq(6)
produces:
[3, 4, 2, 5, 1, 6]
Briefly, we generate an ascending sequence that alternates sign:
1, -2, 3, -4, 5, ...
That's what the i -> -i - signum(i)
expression does. Then, we divide by two in order to get the offsets from the midpoint:
0, -1, 1, -2, 2, ...
That's where the i / 2
in the first term of the map
expression comes from. Then we add the midpoint of the range 1..n which is (n + 1) / 2
, the second term of the map
expression. For n = 5, this gives us
3, 2, 4, 1, 5, ...
Starting at 1 works for odd length sequences. For even lengths, we want to start with -1. The seed expression 2 * (n % 2) - 1
computes the right seed value for both even and odd length sequences.
Finally, we apply limit(n)
to terminate the sequence.
Upvotes: 8
Reputation: 9096
As you are willing to consider a collection, the following "low tech" approach is quite straightforward:
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;
}
The call to f(5),
returns: [2, 1, 3, 0, 4]
The direction ( left to right or right to left ) can be be modified by changing direction()
. If you really need 1-based indices, modify f()
to have: indices.add(index+1)
Upvotes: 1
Reputation: 120978
Well, I can think of this, don't know if it fits your requirements though:
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);
}
Upvotes: 2
Reputation: 94
This solution uses iterators and streams:
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);
Output (toLeft = true):
3
4
2
5
1
Output (toLeft = false):
3
2
4
1
5
Upvotes: 2
Reputation: 12817
we can customise the IntStream.generate
method to generate the IntStream
in the sequence we need
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;
}
}
output
[4, 3, 5, 2, 6, 1, 7, 0, 8]
Upvotes: 1