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}