    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 * The {@code Slice} class represents the set of indices (start, stop, step), that are used to extract specifics subsets of {@link org.eclipse.january.dataset.Dataset}.<br><br>
    019 * The start argument default to 0, stop argument default to the stop argument default to the end of the dimension that the slice is applied to, and the default argument for the step is 1.
    020 * <br><br>
    021 * The start index is inclusive, for example, if we want to get data from index 1, so sliceData will be <b>[2,3]</b> :
    022 * <pre>
    023* {@code
    024* final Dataset onedData = DatasetFactory.createFromObject(new int[]{1,2,3});
    025* Dataset sliceData = onedData.getSlice(new Slice(1, null, null));
    026* }
    027* </pre>
    028*
    029* If Slice is specified with only one argument, this will be the stop index which is exclusive. In this case sliceData will be <b>[1,2]</b> :
    030 * <pre>
    031* {@code
    032* final Dataset onedData = DatasetFactory.createFromObject(new int[]{1,2,3});
    033* Dataset sliceData = onedData.getSlice(new Slice(2));
    034* }
    035* </pre>
    036*
    037* To create a 1D Slice, so sliceData is : <b>[6, 5, 4]</b>, we will do :
    038* <pre>
    039* {@code
    040* final Dataset sliceData = DatasetFactory.createFromObject(new int[]{10,9,8,7,6,5,4,3,2,1,0});
    041* Dataset newOnedData = sliceData.getSlice(new Slice(4, 7, 1));
    042* }
    043* </pre>
    044* <br>
    045* For more informations, see the sliceFrom1D example in SlicingExamples.
    046 */
    047public class Slice implements Cloneable, Serializable {
    048
    049        private static final long serialVersionUID = 3714928852236201310L;
    050        private Integer start;
    051        private Integer stop;
    052        private int step;
    053
    054        private int length; // max length of dimension
    055
    056        /**
    057         * Constructs a Slice object with the start and the stop value representing
    058         * the entirety of the sliced dimension of the Dataset.
    059         */
    060        public Slice() {
    061                this(null, null, 1);
    062        }
    063
    064        /**
    065         * Constructs a Slice object with, by default the start set to 0 and with a
    066         * step of 1. If the stop point of the Slice is {@code null}, it will be set
    067         * to the stop argument default to the end of the dimension that the slice
    068         * is applied to.
    069         *
    070         * @param stop
    071         *            the stop point of the Slice
    072         */
    073        public Slice(final Integer stop) {
    074                this(null, stop, 1);
    075        }
    076
    077        /**
    078         * Constructs a Slice object with, by default a step of 1. If the start
    079         * point of the Slice is {@code null}, it will be set automatically to 0. If
    080         * the stop point of the Slice is {@code null}, it will be set to the stop
    081         * argument default to the end of the dimension that the slice is applied
    082         * to.
    083         *
    084         * @param start
    085         *            the start point of the Slice
    086         * @param stop
    087         *            the stop point of the Slice
    088         */
    089        public Slice(final Integer start, final Integer stop) {
    090                this(start, stop, 1);
    091        }
    092
    093        /**
    094         * Constructs a Slice object on which it is possible to chooe the start, the
    095         * stop and the step. If the start point of the Slice is {@code null}, it
    096         * will be set automatically to 0. If the stop point of the Slice is
    097         * {@code null}, it will be set to the stop argument default to the end of
    098         * the dimension that the slice is applied to. If the the wanted step is set
    099         * to {@code null}, it will be set by default to 1.
    100         *
    101         * @param start
    102         *            the start point of the Slice, may be {@code null}
    103         * @param stop
    104         *            the stop point of the Slice, may be {@code null}
    105         * @param step
    106         *            the step wanted to browse the Dataset, may be {@code null}
    107         */
    108        public Slice(final Integer start, final Integer stop, final Integer step) {
    109                this.start = start;
    110                this.stop = stop;
    111                this.step = step == null ? 1 : step;
    112                length = -1;
    113        }
    114
    115        /**
    116         * Copy another slice
    117         *
    118         * @param other
    119         */
    120        private Slice(final Slice other) {
    121                start = other.start;
    122                stop = other.stop;
    123                step = other.step;
    124                length = other.length;
    125        }
    126
    127        /**
    128         * Creates a deep copy of the Slice.
    129         *
    130         * @return New Slice with the current Slice properties
    131         */
    132        @Override
    133        public Slice clone() {
    134                return new Slice(this);
    135        }
    136
    137        /**
    138         * Sets the maximal dimensions length of the Slice.
    139         *
    140         * @param length
    141         *            Wanted size of dimensions
    142         * @return The Slice which the method is called on
    143         */
    144        public Slice setLength(int length) {
    145                if (stop != null && step > 0 && length < stop) {
    146                        throw new IllegalArgumentException("Length must be greater than or equal to stop");
    147                }
    148                if (start != null && step < 0 && length < start) {
    149                        throw new IllegalArgumentException("Length must be greater than or equal to start");
    150                }
    151                this.length = length;
    152                return this;
    153        }
    154
    155        /**
    156         * Returns {@code true} if the slice has a maximum size equal to the current
    157         * size, else {@code false}.
    158         *
    159         * @return {@code true} if slice represents complete dimension,
    160         *         {@code false} in the other case.
    161         */
    162        public boolean isSliceComplete() {
    163                if (start == null && stop == null && (step == 1 || step == -1))
    164                        return true;
    165                if (length > 0) {
    166                        return getNumSteps() == length;
    167                }
    168
    169                return true;
    170        }
    171
    172        /**
    173         * Returns the maximum value of the slice.
    174         *
    175         * @return Maximum value of the slice
    176         */
    177        public int getLength() {
    178                return length;
    179        }
    180
    181        /**
    182         * Returns the starting index of the slice.
    183         *
    184         * @return Start point of the slice
    185         */
    186        public Integer getStart() {
    187                return start;
    188        }
    189
    190        /**
    191         * Returns the stopping index of the slice.
    192         *
    193         * @return Stop point of the slice
    194         */
    195        public Integer getStop() {
    196                return stop;
    197        }
    198
    199        /**
    200         * Returns the step of the slice.
    201         *
    202         * @return Step of the slice
    203         */
    204        public int getStep() {
    205                return step;
    206        }
    207
    208        /**
    209         * Set the starting index of the slice. If the start point of the Slice is
    210         * {@code null}, it will be set automatically to 0.
    211         *
    212         * @param start
    213         *            Starting index of the Slice, may be {@code null}
    214         */
    215        public void setStart(Integer start) {
    216                if (start != null && length > 0) {
    217                        if (step > 0) {
    218                                int end = stop == null ? length : stop;
    219                                if (start >= end) {
    220                                        throw new IllegalArgumentException("Non-null start must be less than end");
    221                                }
    222                        } else {
    223                                int end = stop == null ? -1 : stop;
    224                                if (start < end) {
    225                                        throw new IllegalArgumentException("Non-null start must be greater than end for negative step");
    226                                }
    227                        }
    228                }
    229                this.start = start;
    230        }
    231
    232        /**
    233         * Set the stopping index of the slice. If the stop point of the Slice is
    234         * {@code null}, it will be set to the stop argument default to the end of
    235         * the dimension that the slice is applied to.
    236         *
    237         * @param stop
    238         *            Stopping index of the Slice, may be {@code null}
    239         */
    240        public void setStop(Integer stop) {
    241                if (stop != null && length > 0) {
    242                        if (step > 0) {
    243                                int beg = start == null ? 0 : start;
    244                                if (stop < beg) {
    245                                        throw new IllegalArgumentException("Non-null stop must be greater than or equal to beginning");
    246                                }
    247                        } else {
    248                                int beg = start == null ? length - 1 : start;
    249                                if (stop >= beg) {
    250                                        throw new IllegalArgumentException("Non-null stop must be less than beginning for negative step");
    251                                }
    252                        }
    253                        if (stop > length)
    254                                stop = length;
    255                }
    256                this.stop = stop;
    257        }
    258
    259        /**
    260         * Move the start and end to an other index keeping the same step and the
    261         * same gap between the two values
    262         *
    263         * @param beg
    264         *            New starting point
    265         * @return Return {@code true} if the end was reached, {@code false} in the
    266         *         other case.
    267         */
    268        public boolean setPosition(int beg) {
    269                boolean end = false;
    270                int len = getNumSteps();
    271                int max = getNumSteps(beg, length, step);
    272                if (len > max) {
    273                        len = max;
    274                        end = true;
    275                }
    276                start = beg;
    277                stop = start + (len - 1) * step + 1;
    278                return end;
    279        }
    280
    281        /**
    282         * Returns the index of the n-th step inside of the slice
    283         *
    284         * @param n
    285         *            Wanted step index in the slice
    286         * @return Return the index of the step inside of the Slice
    287         */
    288        public int getPosition(int n) {
    289                if (n < 0)
    290                        throw new IllegalArgumentException("Given n-th step should be non-negative");
    291                if (n >= getNumSteps())
    292                        throw new IllegalArgumentException("N-th step exceeds extent of slice");
    293                int beg;
    294                if (start == null) {
    295                        if (step < 0) {
    296                                if (length < 0) {
    297                                        if (stop == null) {
    298                                                throw new IllegalStateException("Length or stop should be set");
    299                                        }
    300                                        beg = stop - 1;
    301                                } else {
    302                                        beg = length - 1;
    303                                }
    304                        } else {
    305                                beg = 0;
    306                        }
    307                } else {
    308                        beg = start;
    309                }
    310                return beg + step * n;
    311        }
    312
    313        /**
    314         * Set the step size inside of the Slice. If the the wanted step is set to
    315         * {@code null}, it will be set by default to 1.
    316         *
    317         * @param step
    318         *            New wanted step, may be {@code null}
    319         */
    320        public void setStep(int step) {
    321                if (step == 0) {
    322                        throw new IllegalArgumentException("Step must not be zero");
    323                }
    324                this.step = step;
    325        }
    326
    327        /**
    328         * Returns a String representation of the Slice comparable to the python
    329         * representation.
    330         *
    331         * @param s
    332         *            String builder
    333         * @param len
    334         *            Maximal length of the Slice, or -1 if not set
    335         * @param beg
    336         *            Start index of the Slice
    337         * @param end
    338         *            Stop index of the Slice
    339         * @param del
    340         *            Step of the Slice
    341         */
    342        public static void appendSliceToString(final StringBuilder s, final int len, final int beg, final int end,
    343                        final int del) {
    344                int o = s.length();
    345                if (del > 0) {
    346                        if (beg != 0)
    347                                s.append(beg);
    348                } else {
    349                        if (beg != len - 1)
    350                                s.append(beg);
    351                }
    352
    353                int n = getNumSteps(beg, end, del);
    354                if (n == 1) {
    355                        if (s.length() == o) {
    356                                s.append(beg);
    357                        }
    358                        return;
    359                }
    360
    361                s.append(':');
    362
    363                if (del > 0) {
    364                        if (end != len)
    365                                s.append(end);
    366                } else {
    367                        if (end != -1)
    368                                s.append(end);
    369                }
    370
    371                if (del != 1) {
    372                        s.append(':');
    373                        s.append(del);
    374                }
    375        }
    376
    377        /**
    378         * Returns a string construction of the slice with the python form.
    379         *
    380         * @return Constructed String.
    381         */
    382        @Override
    383        public String toString() {
    384                StringBuilder s = new StringBuilder();
    385                appendSliceToString(s, length, start != null ? start : (step > 0 ? 0 : length - 1),
    386                                stop != null ? stop : (step > 0 ? length : -1), step);
    387                return s.toString();
    388        }
    389
    390        /**
    391         * Returns the number of steps inside of the Slice
    392         *
    393         * @return Number of steps inside of the Slice
    394         */
    395        public int getNumSteps() {
    396                if (length < 0) {
    397                        if (stop == null)
    398                                throw new IllegalStateException("Slice is underspecified - stop is null and length is negative");
    399                        int beg = start == null ? (step > 0 ? 0 : stop - 1) : start;
    400                        if (step > 0 && stop <= beg)
    401                                return 0;
    402                        if (step < 0 && stop > beg)
    403                                return 0;
    404                        return getNumSteps(beg, stop, step);
    405                }
    406                int beg = start == null ? (step > 0 ? 0 : length - 1) : start;
    407                int end = stop == null ? (step > 0 ? length : -1) : stop;
    408                return getNumSteps(beg, end, step);
    409        }
    410
    411        /**
    412         * Returns the number of steps inside of the Slice from a point to an other
    413         * point minus 1 because this is an exclusive index
    414         *
    415         * @param beg
    416         *            Starting point
    417         * @param end
    418         *            (exclusive) Stopping point
    419         * @return Numbers of steps between the 2 limits.
    420         */
    421        public int getNumSteps(int beg, int end) {
    422                return getNumSteps(beg, end, step);
    423        }
    424
    425        private static int getNumSteps(int beg, int end, int step) {
    426                int del = step > 0 ? 1 : -1;
    427                return Math.max(0, (end - beg - del) / step + 1);
    428        }
    429
    430        /**
    431         * Returns the last value inside of Slice.
    432         *
    433         * @return Last value in the slice, it can be a lower value than the start
    434         *         if the step is going backward
    435         */
    436        public int getEnd() {
    437                int n = getNumSteps() - 1;
    438                if (n < 0)
    439                        throw new IllegalStateException("End is not defined");
    440
    441                return getPosition(n);
    442        }
    443
    444        /**
    445         * Flips the Slice direction, after this operation, the slice begins at
    446         * previous end point, steps in the opposite direction, and finishes at the
    447         * previous start point.
    448         * <p>
    449         * Note : the stop value may not be preserved across two flips
    450         * </p>
    451         *
    452         * @return Flipped Slice.
    453         */
    454        public Slice flip() {
    455                if (length < 0) {
    456                        Integer tmp = start == null ? null : start - step;
    457                        start = stop == null ? null : getEnd();
    458                        stop = tmp;
    459                } else {
    460                        Integer tstart = start;
    461                        start = stop == null ? null : getEnd();
    462                        stop = tstart == null ? null : tstart - step;
    463                }
    464                step = -step;
    465
    466                return this;
    467        }
    468
    469        /**
    470         * Populates given start, stop, step arrays from given slice array
    471         *
    472         * @param slice
    473         *            Input array of Slices wanted to convert
    474         * @param shape
    475         *            Input array of Slices shapes
    476         * @param start
    477         *            Output array of Slices starts
    478         * @param stop
    479         *            Output array of Slices stops
    480         * @param step
    481         *            Output array of Slices steps
    482         */
    483        public static void convertFromSlice(final Slice[] slice, final int[] shape, final int[] start, final int[] stop,
    484                        final int[] step) {
    485                final int rank = shape.length;
    486                final int length = slice == null ? 0 : slice.length;
    487
    488                int i = 0;
    489                for (; i < length; i++) {
    490                        if (length > rank)
    491                                break;
    492
    493                        Slice s = slice[i];
    494                        if (s == null) {
    495                                start[i] = 0;
    496                                stop[i] = shape[i];
    497                                step[i] = 1;
    498                                continue;
    499                        }
    500                        int n;
    501                        if (s.start == null) {
    502                                start[i] = s.step > 0 ? 0 : shape[i] - 1;
    503                        } else {
    504                                n = s.start;
    505                                if (n < 0)
    506                                        n += shape[i];
    507                                if (n < 0 || n >= shape[i]) {
    508                                        throw new IllegalArgumentException(
    509                                                        String.format("Start is out of bounds: %d is not in [%d,%d)", n, s.start, shape[i]));
    510                                }
    511                                start[i] = n;
    512                        }
    513
    514                        if (s.stop == null) {
    515                                stop[i] = s.step > 0 ? shape[i] : -1;
    516                        } else {
    517                                n = s.stop;
    518                                if (n < 0)
    519                                        n += shape[i];
    520                                if (n < 0 || n > shape[i]) {
    521                                        throw new IllegalArgumentException(
    522                                                        String.format("Stop is out of bounds: %d is not in [%d,%d)", n, s.stop, shape[i]));
    523                                }
    524                                stop[i] = n;
    525                        }
    526
    527                        n = s.step;
    528                        if (n == 0) {
    529                                throw new IllegalArgumentException("Step cannot be zero");
    530                        }
    531                        if (n > 0) {
    532                                if (start[i] > stop[i])
    533                                        throw new IllegalArgumentException("Start must be less than stop for positive steps");
    534                        } else {
    535                                if (start[i] < stop[i])
    536                                        throw new IllegalArgumentException("Start must be greater than stop for negative steps");
    537                        }
    538                        step[i] = n;
    539                }
    540                for (; i < rank; i++) {
    541                        start[i] = 0;
    542                        stop[i] = shape[i];
    543                        step[i] = 1;
    544                }
    545        }
    546
    547        /**
    548         * Converts a set of integer arrays in a slice array
    549         *
    550         * @param start
    551         *            Array of Slices starts
    552         * @param stop
    553         *            Array of Slices stops
    554         * @param step
    555         *            Array of Slices steps
    556         * @return Slice array corresponding to the starts, stops and steps arrays
    557         *         entered.
    558         */
    559        public static Slice[] convertToSlice(final int[] start, final int[] stop, final int[] step) {
    560                int orank = start.length;
    561
    562                if (stop.length != orank || step.length != orank) {
    563                        throw new IllegalArgumentException("All arrays must be same length");
    564                }
    565
    566                Slice[] slice = new Slice[orank];
    567
    568                for (int j = 0; j < orank; j++) {
    569                        slice[j] = new Slice(start[j], stop[j], step[j]);
    570                }
    571
    572                return slice;
    573        }
    574
    575        /**
    576         * Converts string in a Slice array
    577         *
    578         * @param sliceString
    579         *            String of the Slice array
    580         * @return Slice array created from the given string
    581         */
    582        public static Slice[] convertFromString(String sliceString) {
    583
    584                String clean = sliceString.replace("[", "");
    585                clean = clean.replace("]", "");
    586
    587                String[] sub = clean.split(",");
    588
    589                Slice[] slices = new Slice[sub.length];
    590
    591                for (int i = 0; i < sub.length; i++) {
    592                        String s = sub[i];
    593
    594                        Slice slice = new Slice();
    595                        slices[i] = slice;
    596
    597                        int idx0 = s.indexOf(":");
    598
    599                        int n = 0;
    600                        if (idx0 == -1) {
    601                                n = Integer.parseInt(s);
    602                                slice.setStart(n);
    603                                slice.setStop(n + 1);
    604                                continue;
    605                        } else if (idx0 != 0) {
    606                                n = Integer.parseInt(s.substring(0, idx0));
    607                        }
    608                        slice.setStart(n);
    609
    610                        idx0++;
    611                        int idx1 = s.indexOf(":", idx0);
    612                        if (idx1 == -1) {
    613                                String t = s.substring(idx0).trim();
    614                                if (t.length() == 0)
    615                                        continue;
    616
    617                                slice.setStop(Integer.parseInt(t));
    618                                continue;
    619                        } else if (idx1 != idx0) {
    620                                slice.setStop(Integer.parseInt(s.substring(idx0, idx1)));
    621                        }
    622
    623                        String t = s.substring(idx1 + 1).trim();
    624                        if (t.length() > 0)
    625                                slice.setStep(Integer.parseInt(t));
    626                }
    627
    628                return slices;
    629        }
    630
    631        /**
    632         * Creates a string representing the slice taken from given shape
    633         *
    634         * @param shape
    635         *            Array of Slices shapes
    636         * @param start
    637         *            Array of Slices starts
    638         * @param stop
    639         *            Array of Slices stops
    640         * @param step
    641         *            Array of Slices steps
    642         * @return String representation of the Slice
    643         */
    644        public static String createString(final int[] shape, final int[] start, final int[] stop, final int[] step) {
    645                final int rank = shape.length;
    646                if (rank == 0) {
    647                        return "";
    648                }
    649                StringBuilder s = new StringBuilder();
    650                for (int i = 0; i < rank; i++) {
    651                        int l = shape[i];
    652                        int d = step == null ? 1 : step[i];
    653                        int b = start == null ? (d > 0 ? 0 : l - 1) : start[i];
    654                        int e = stop == null ? (d > 0 ? l : -1) : stop[i];
    655
    656                        appendSliceToString(s, l, b, e, d);
    657                        s.append(',');
    658                }
    659
    660                return s.substring(0, s.length() - 1);
    661        }
    662
    663        /**
    664         * Creates a string representation of slices.
    665         *
    666         * @param slices
    667         *            Wanted Slices to put inside of the string representation
    668         * @return Return the string representation of the Slices entered.
    669         */
    670        public static String createString(Slice... slices) {
    671                if (slices == null || slices.length == 0) {
    672                        return "";
    673                }
    674
    675                StringBuilder t = new StringBuilder();
    676                for (Slice s : slices) {
    677                        t.append(s != null ? s.toString() : ':');
    678                        t.append(',');
    679                }
    680
    681                return t.substring(0, t.length() - 1);
    682        }
    683}