    001/*-
    002 *******************************************************************************
    003 * Copyright (c) 2011, 2016 Diamond Light Source Ltd.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the Eclipse Public License v1.0
    006 * which accompanies this distribution, and is available at
    007 * http://www.eclipse.org/legal/epl-v10.html
    008 *
    009 * Contributors:
    010 *    Peter Chang - initial API and implementation and/or initial documentation
    011 *******************************************************************************/
    012
    013package org.eclipse.january.dataset;
    014
    015import java.io.Serializable;
    016
    017/**
    018 * Class to represent a slice through a single dimension of a multi-dimensional dataset. A slice
    019 * comprises a starting position, a stopping position (not included) and a stepping size.
    020 */
    021public class Slice implements Cloneable, Serializable {
    022
    023        /**
    024         *
    025         */
    026        private static final long serialVersionUID = 3714928852236201310L;
    027        private Integer start;
    028        private Integer stop;
    029        private int step;
    030
    031        private int length; // max length of dimension
    032
    033        public Slice() {
    034                this(null, null, 1);
    035        }
    036
    037        /**
    038         * Default to starting at 0 with steps of 1
    039         * @param stop if null, then default to whatever shape is when converted
    040         */
    041        public Slice(final Integer stop) {
    042                this(null, stop, 1);
    043        }
    044
    045        /**
    046         * Default to steps of 1
    047         * @param start if null, then default to 0
    048         * @param stop if null, then default to whatever shape is when converted
    049         */
    050        public Slice(final Integer start, final Integer stop) {
    051                this(start, stop, 1);
    052        }
    053
    054        /**
    055         *
    056         * @param start if null, then default to bound
    057         * @param stop if null, then default to bound
    058         * @param step if null, then default to 1
    059         */
    060        public Slice(final Integer start, final Integer stop, final Integer step) {
    061                this.start = start;
    062                this.stop = stop;
    063                this.step = step == null ? 1 : step;
    064                length = -1;
    065        }
    066
    067        /**
    068         * Copy another slice
    069         * @param other
    070         */
    071        private Slice(final Slice other) {
    072                start  = other.start;
    073                stop   = other.stop;
    074                step   = other.step;
    075                length = other.length;
    076        }
    077
    078        @Override
    079        public Slice clone() {
    080                return new Slice(this);
    081        }
    082
    083        /**
    084         * Set maximum value of slice
    085         * @param length
    086         * @return this slice
    087         */
    088        public Slice setLength(int length) {
    089                if (stop != null && step > 0 && length < stop) {
    090                        throw new IllegalArgumentException("Length must be greater than or equal to stop");
    091                }
    092                if (start != null && step < 0 && length < start) {
    093                        throw new IllegalArgumentException("Length must be greater than or equal to start");
    094                }
    095                this.length = length;
    096                return this;
    097        }
    098
    099        /**
    100         * @return true if slice represents complete dimension
    101         */
    102        public boolean isSliceComplete() {
    103                if (start == null && stop == null && (step == 1 || step == -1))
    104                        return true;
    105                if (length > 0) {
    106                        return getNumSteps() == length;
    107                }
    108
    109                return true;
    110        }
    111
    112        /**
    113         * @return maximum value of slice
    114         */
    115        public int getLength() {
    116                return length;
    117        }
    118
    119        /**
    120         * @return starting position of slice
    121         */
    122        public Integer getStart() {
    123                return start;
    124        }
    125
    126        /**
    127         * @return stopping position of slice
    128         */
    129        public Integer getStop() {
    130                return stop;
    131        }
    132
    133        /**
    134         * @return step size of slice
    135         */
    136        public int getStep() {
    137                return step;
    138        }
    139
    140        /**
    141         * Set starting position of slice
    142         * @param start (can be null)
    143         */
    144        public void setStart(Integer start) {
    145                if (start != null && length > 0) {
    146                        if (step > 0) {
    147                                int end = stop == null ? length : stop;
    148                                if (start >= end) {
    149                                        throw new IllegalArgumentException("Non-null start must be less than end");
    150                                }
    151                        } else {
    152                                int end = stop == null ? -1 : stop;
    153                                if (start < end) {
    154                                        throw new IllegalArgumentException("Non-null start must be greater than end for negative step");
    155                                }
    156                        }
    157                }
    158                this.start = start;
    159        }
    160
    161        /**
    162         * Set stopping position of slice
    163         * @param stop (can be null)
    164         */
    165        public void setStop(Integer stop) {
    166                if (stop != null && length > 0) {
    167                        if (step > 0) {
    168                                int beg = start == null ? 0 : start;
    169                                if (stop < beg) {
    170                                        throw new IllegalArgumentException("Non-null stop must be greater than or equal to beginning");
    171                                }
    172                        } else {
    173                                int beg = start == null ? length - 1 : start;
    174                                if (stop >= beg) {
    175                                        throw new IllegalArgumentException("Non-null stop must be less than beginning for negative step");
    176                                }
    177                        }
    178                        if (stop > length)
    179                                stop = length;
    180                }
    181                this.stop = stop;
    182        }
    183
    184        /**
    185         * Set start and end from implied number of steps. I.e. shift start to position given by
    186         * parameter whilst keeping size of slice fixed
    187         * @param beg
    188         * @return true if end reached
    189         */
    190        public boolean setPosition(int beg) {
    191                boolean end = false;
    192                int len = getNumSteps();
    193                int max = getNumSteps(beg, length, step);
    194                if (len > max) {
    195                        len = max;
    196                        end = true;
    197                }
    198                start = beg;
    199                stop = start + (len-1) * step + 1;
    200                return end;
    201        }
    202
    203        /**
    204         * Get position of n-th step in slice
    205         * @param n
    206         * @return position
    207         */
    208        public int getPosition(int n) {
    209                if (n < 0)
    210                        throw new IllegalArgumentException("Given n-th step should be non-negative");
    211                if (n >= getNumSteps())
    212                        throw new IllegalArgumentException("N-th step exceeds extent of slice");
    213                int beg;
    214                if (start == null) {
    215                        if (step < 0) {
    216                                if (length < 0) {
    217                                        if (stop == null) {
    218                                                throw new IllegalStateException("Length or stop should be set");
    219                                        }
    220                                        beg = stop - 1;
    221                                } else {
    222                                        beg = length - 1;
    223                                }
    224                        } else {
    225                                beg = 0;
    226                        }
    227                } else {
    228                        beg = start;
    229                }
    230                return beg + step*n;
    231        }
    232
    233        /**
    234         * Set step size of slice
    235         * @param step
    236         */
    237        public void setStep(int step) {
    238                if (step == 0) {
    239                        throw new IllegalArgumentException("Step must not be zero");
    240                }
    241                this.step = step;
    242        }
    243
    244        /**
    245         * Append string representation of slice
    246         * @param s
    247         * @param len
    248         * @param beg
    249         * @param end
    250         * @param del
    251         */
    252        public static void appendSliceToString(final StringBuilder s, final int len, final int beg, final int end, final int del) {
    253                int o = s.length();
    254                if (del > 0) {
    255                        if (beg != 0)
    256                                s.append(beg);
    257                } else {
    258                        if (beg != len-1)
    259                                s.append(beg);
    260                }
    261
    262                int n = getNumSteps(beg, end, del);
    263                if (n == 1) {
    264                        if (s.length() == o) {
    265                                s.append(beg);
    266                        }
    267                        return;
    268                }
    269
    270                s.append(':');
    271
    272                if (del > 0) {
    273                        if (end != len)
    274                                s.append(end);
    275                } else {
    276                        if (end != -1)
    277                                s.append(end);
    278                }
    279
    280                if (del != 1) {
    281                        s.append(':');
    282                        s.append(del);
    283                }
    284        }
    285
    286        @Override
    287        public String toString() {
    288                StringBuilder s = new StringBuilder();
    289                appendSliceToString(s, length, start != null ? start : (step > 0 ? 0 : length - 1), stop != null ? stop : (step > 0 ? length : -1), step);
    290                return s.toString();
    291        }
    292
    293        /**
    294         * @return number of steps in slice
    295         */
    296        public int getNumSteps() {
    297                if (length < 0) {
    298                        if (stop == null)
    299                                throw new IllegalStateException("Slice is underspecified - stop is null and length is negative");
    300                        int beg = start == null ? (step > 0 ? 0: stop-1) : start;
    301                        if (step > 0 && stop <= beg)
    302                                return 0;
    303                        if (step < 0 && stop > beg)
    304                                return 0;
    305                        return getNumSteps(beg, stop, step);
    306                }
    307                int beg = start == null ? (step > 0 ? 0: length-1) : start;
    308                int end = stop == null ? (step > 0 ? length : -1) : stop;
    309                return getNumSteps(beg, end, step);
    310        }
    311
    312        /**
    313         * @param beg
    314         * @param end (exclusive)
    315         * @return number of steps between limits
    316         */
    317        public int getNumSteps(int beg, int end) {
    318                return getNumSteps(beg, end, step);
    319        }
    320
    321        private static int getNumSteps(int beg, int end, int step) {
    322                int del = step > 0 ? 1 : -1;
    323                return Math.max(0, (end - beg - del) / step + 1);
    324        }
    325
    326        /**
    327         * @return last value in slice (< stop if step > 0, > stop if step < 0)
    328         */
    329        public int getEnd() {
    330                int n = getNumSteps() - 1;
    331                if (n < 0)
    332                        throw new IllegalStateException("End is not defined");
    333
    334                return getPosition(n);
    335        }
    336
    337        /**
    338         * Flip slice direction so slice begins at previous end point, steps
    339         * in the opposite direction, and finishes at the previous start point  .
    340         * <p>
    341         * Note the stop value may not be preserved across two flips
    342         */
    343        public Slice flip() {
    344                if (length < 0) {
    345                        Integer tmp = start == null ? null : start - step;
    346                        start = stop == null ? null : getEnd();
    347                        stop = tmp;
    348                } else {
    349                        Integer tstart = start;
    350                        start = stop == null ? null : getEnd();
    351                        stop = tstart == null ? null : tstart - step;
    352                }
    353                step = -step;
    354
    355                return this;
    356        }
    357
    358        /**
    359         * Populate given start, stop, step arrays from given slice array
    360         * @param shape
    361         * @param start
    362         * @param stop
    363         * @param step
    364         * @param slice
    365         */
    366        public static void convertFromSlice(final Slice[] slice, final int[] shape, final int[] start, final int[] stop, final int[] step) {
    367                final int rank = shape.length;
    368                final int length = slice == null ? 0 : slice.length;
    369
    370                int i = 0;
    371                for (; i < length; i++) {
    372                        if (length > rank)
    373                                break;
    374
    375                        Slice s = slice[i];
    376                        if (s == null) {
    377                                start[i] = 0;
    378                                stop[i] = shape[i];
    379                                step[i] = 1;
    380                                continue;
    381                        }
    382                        int n;
    383                        if (s.start == null) {
    384                                start[i] = s.step > 0 ? 0 : shape[i] - 1;
    385                        } else {
    386                                n = s.start;
    387                                if (n < 0)
    388                                        n += shape[i];
    389                                if (n < 0 || n >= shape[i]) {
    390                                        throw new IllegalArgumentException(String.format("Start is out of bounds: %d is not in [%d,%d)",
    391                                                        n, s.start, shape[i]));
    392                                }
    393                                start[i] = n;
    394                        }
    395
    396                        if (s.stop == null) {
    397                                stop[i] = s.step > 0 ? shape[i] : -1;
    398                        } else {
    399                                n = s.stop;
    400                                if (n < 0)
    401                                        n += shape[i];
    402                                if (n < 0 || n > shape[i]) {
    403                                        throw new IllegalArgumentException(String.format("Stop is out of bounds: %d is not in [%d,%d)",
    404                                                        n, s.stop, shape[i]));
    405                                }
    406                                stop[i] = n;
    407                        }
    408
    409                        n = s.step;
    410                        if (n == 0) {
    411                                throw new IllegalArgumentException("Step cannot be zero");
    412                        }
    413                        if (n > 0) {
    414                                if (start[i] > stop[i])
    415                                        throw new IllegalArgumentException("Start must be less than stop for positive steps");
    416                        } else {
    417                                if (start[i] < stop[i])
    418                                        throw new IllegalArgumentException("Start must be greater than stop for negative steps");
    419                        }
    420                        step[i] = n;
    421                }
    422                for (; i < rank; i++) {
    423                        start[i] = 0;
    424                        stop[i] = shape[i];
    425                        step[i] = 1;
    426                }
    427        }
    428
    429        /**
    430         * Convert from a set of integer arrays to a slice array
    431         * @param start
    432         * @param stop
    433         * @param step
    434         * @return a slice array
    435         */
    436        public static Slice[] convertToSlice(final int[] start, final int[] stop, final int[] step) {
    437                int orank = start.length;
    438
    439                if (stop.length != orank || step.length != orank) {
    440                        throw new IllegalArgumentException("All arrays must be same length");
    441                }
    442
    443                Slice[] slice = new Slice[orank];
    444
    445                for (int j = 0; j < orank; j++) {
    446                        slice[j] = new Slice(start[j], stop[j], step[j]);
    447                }
    448
    449                return slice;
    450        }
    451
    452        /**
    453         * Convert a string to a slice array
    454         *
    455         * @param sliceString
    456         * @return a slice array
    457         */
    458        public static Slice[] convertFromString(String sliceString) {
    459
    460                String clean = sliceString.replace("[", "");
    461                clean = clean.replace("]", "");
    462
    463                String[] sub = clean.split(",");
    464
    465                Slice[] slices = new Slice[sub.length];
    466
    467                for (int i = 0; i < sub.length; i++) {
    468                        String s = sub[i];
    469
    470                        Slice slice = new Slice();
    471                        slices[i] = slice;
    472
    473                        int idx0 = s.indexOf(":");
    474
    475                        int n = 0;
    476                        if (idx0 == -1) {
    477                                n = Integer.parseInt(s);
    478                                slice.setStart(n);
    479                                slice.setStop(n + 1);
    480                                continue;
    481                        } else if (idx0 != 0) {
    482                                n = Integer.parseInt(s.substring(0, idx0));
    483                        }
    484                        slice.setStart(n);
    485
    486                        idx0++;
    487                        int idx1 = s.indexOf(":", idx0);
    488                        if (idx1 == -1) {
    489                                String t = s.substring(idx0).trim();
    490                                if (t.length() == 0)
    491                                        continue;
    492
    493                                slice.setStop(Integer.parseInt(t));
    494                                continue;
    495                        } else if (idx1 != idx0) {
    496                                slice.setStop(Integer.parseInt(s.substring(idx0, idx1)));
    497                        }
    498
    499                        String t = s.substring(idx1 + 1).trim();
    500                        if (t.length() > 0)
    501                                slice.setStep(Integer.parseInt(t));
    502                }
    503
    504                return slices;
    505        }
    506
    507        /**
    508         * Create a string representing the slice taken from given shape
    509         * @param shape
    510         * @param start
    511         * @param stop
    512         * @param step
    513         * @return string representation
    514         */
    515        public static String createString(final int[] shape, final int[] start, final int[] stop, final int[] step) {
    516                final int rank = shape.length;
    517                if (rank == 0) {
    518                        return "";
    519                }
    520                StringBuilder s = new StringBuilder();
    521                for (int i = 0; i < rank; i++) {
    522                        int l = shape[i];
    523                        int d = step == null ? 1 : step[i];
    524                        int b = start == null ? (d > 0 ? 0 : l - 1) : start[i];
    525                        int e = stop == null ? (d > 0 ? l : -1) : stop[i];
    526
    527                        appendSliceToString(s, l, b, e, d);
    528                        s.append(',');
    529                }
    530
    531                return s.substring(0, s.length()-1);
    532        }
    533
    534        /**
    535         * @param slices
    536         * @return string specifying slices
    537         */
    538        public static String createString(Slice... slices) {
    539                if (slices == null || slices.length == 0) {
    540                        return "";
    541                }
    542
    543                StringBuilder t = new StringBuilder();
    544                for (Slice s : slices) {
    545                        t.append(s != null ? s.toString() : ':');
    546                        t.append(',');
    547                }
    548
    549                return t.substring(0, t.length() - 1);
    550        }
    551}