/** * Copyright (c) Rich Hickey. All rights reserved. * The use and distribution terms for this software are covered by the * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) * which can be found in the file epl-v10.html at the root of this distribution. * By using this software in any fashion, you are agreeing to be bound by * the terms of this license. * You must not remove this notice, or any other, from this software. **/ package clojure.lang; import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; /** * Implements the special common case of a finite range based on long start, end, and step, * with no more than Integer.MAX_VALUE items. */ public class LongRange extends ASeq implements Counted, IChunkedSeq, IReduce, IDrop { private static final long serialVersionUID = -1467242400566893909L; // Invariants guarantee this is never an empty or infinite seq // assert(start != end && step != 0) final long start; final long end; final long step; final int count; private LongRange(long start, long end, long step, int count){ this.start = start; this.end = end; this.step = step; this.count = count; } private LongRange(IPersistentMap meta, long start, long end, long step, int count){ super(meta); this.start = start; this.end = end; this.step = step; this.count = count; } // returns exact size of remaining items OR throws ArithmeticException for overflow case static long rangeCount(long start, long end, long step) { // (1) count = ceiling ( (end - start) / step ) // (2) ceiling(a/b) = (a+b+o)/b where o=-1 for positive stepping and +1 for negative stepping // thus: count = end - start + step + o / step return Numbers.add(Numbers.add(Numbers.minus(end, start), step), step > 0 ? -1 : 1) / step; } public static ISeq create(long end) { if(end > 0) { try { return new LongRange(0L, end, 1L, Math.toIntExact(rangeCount(0L, end, 1L))); } catch(ArithmeticException e) { return Range.create(end); // count > Integer.MAX_VALUE } } else { return PersistentList.EMPTY; } } public static ISeq create(long start, long end) { if(start >= end) { return PersistentList.EMPTY; } else { try { return new LongRange(start, end, 1L, Math.toIntExact(rangeCount(start, end, 1L))); } catch(ArithmeticException e) { return Range.create(start, end); } } } public static ISeq create(final long start, long end, long step) { if(step > 0) { if(end <= start) return PersistentList.EMPTY; try { return new LongRange(start, end, step, Math.toIntExact(rangeCount(start, end, step))); } catch(ArithmeticException e) { return Range.create(start, end, step); } } else if(step < 0) { if(end >= start) return PersistentList.EMPTY; try { return new LongRange(start, end, step, Math.toIntExact(rangeCount(start, end, step))); } catch(ArithmeticException e) { return Range.create(start, end, step); } } else { if(end == start) return PersistentList.EMPTY; return Repeat.create(start); } } public Obj withMeta(IPersistentMap meta){ if(meta == _meta) return this; return new LongRange(meta, start, end, step, count); } public Object first() { return start; } public ISeq next() { if(count > 1) { return new LongRange(start + step, end, step, count-1); } else { return null; } } private static final int CHUNK_SIZE = 32; public IChunk chunkedFirst() { return new LongChunk(start, step, Math.min(count, CHUNK_SIZE)); } public ISeq chunkedNext() { return chunkedMore().seq(); } public ISeq chunkedMore() { if(count <= CHUNK_SIZE) { return PersistentList.EMPTY; } else { return LongRange.create(start + (step * CHUNK_SIZE), end, step); } } public Sequential drop(int n) { if(n <= 0) { return this; } else if(n < count) { return new LongRange(start+(step*n), end, step, count - n); } else { return null; } } public int count() { return count; } public Object reduce(IFn f) { Object acc = start; long i = start + step; int n = count; while(n > 1) { acc = f.invoke(acc, i); if (acc instanceof Reduced) return ((Reduced)acc).deref(); i += step; n--; } return acc; } public Object reduce(IFn f, Object val) { Object acc = val; int n = count; long i = start; do { acc = f.invoke(acc, i); if (RT.isReduced(acc)) return ((Reduced)acc).deref(); i += step; n--; } while(n > 0); return acc; } public Iterator iterator() { return new LongRangeIterator(); } class LongRangeIterator implements Iterator { private long next; private int remaining; public LongRangeIterator() { this.next = start; this.remaining = count; } public boolean hasNext() { return remaining > 0; } public Object next() { if (remaining > 0) { long ret = next; next = next + step; remaining = remaining - 1; return ret; } else { throw new NoSuchElementException(); } } public void remove() { throw new UnsupportedOperationException(); } } private static class LongChunk implements IChunk, Serializable { final long start; final long step; final int count; public LongChunk(long start, long step, int count) { this.start = start; this.step = step; this.count = count; } public long first(){ return start; } public Object nth(int i){ return start + (i * step); } public Object nth(int i, Object notFound){ if(i >= 0 && i < count) return start + (i * step); return notFound; } public int count(){ return count; } public LongChunk dropFirst(){ if(count <= 1) throw new IllegalStateException("dropFirst of empty chunk"); return new LongChunk(start+step, step, count-1); } public Object reduce(IFn f, Object init) { long x = start; Object ret = init; for(int i=0; i