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) { 146 if (step > 0) { 147 if (stop > length) { 148 throw new IllegalArgumentException("Stop must be less than or equal to length"); 149 } 150 } 151 } 152 if (start != null) { 153 if (start > length) { 154 throw new IllegalArgumentException("Start must be less than or equal to length"); 155 } 156 } 157 this.length = length; 158 return this; 159 } 160 161 /** 162 * Returns {@code true} if the slice has a maximum size equal to the current 163 * size, else {@code false}. 164 * 165 * @return {@code true} if slice represents complete dimension, 166 * {@code false} in the other case. 167 */ 168 public boolean isSliceComplete() { 169 if (start == null && stop == null && (step == 1 || step == -1)) 170 return true; 171 if (length > 0) { 172 return getNumSteps() == length; 173 } 174 175 return true; 176 } 177 178 /** 179 * Returns the maximum value of the slice. 180 * 181 * @return Maximum value of the slice 182 */ 183 public int getLength() { 184 return length; 185 } 186 187 /** 188 * Returns the starting index of the slice. 189 * 190 * @return Start point of the slice 191 */ 192 public Integer getStart() { 193 return start; 194 } 195 196 /** 197 * Returns the stopping index of the slice. 198 * 199 * @return Stop point of the slice 200 */ 201 public Integer getStop() { 202 return stop; 203 } 204 205 /** 206 * Returns the step of the slice. 207 * 208 * @return Step of the slice 209 */ 210 public int getStep() { 211 return step; 212 } 213 214 /** 215 * Set the starting index of the slice. If the start point of the Slice is 216 * {@code null}, it will be set automatically to 0. 217 * 218 * @param start 219 * Starting index of the Slice, may be {@code null} 220 */ 221 public void setStart(Integer start) { 222 if (start != null && length > 0) { 223 if (step > 0) { 224 int end = stop == null ? length : stop; 225 if (start >= end) { 226 throw new IllegalArgumentException("Non-null start must be less than end"); 227 } 228 } else { 229 int end = stop == null ? -1 : stop; 230 if (start < end) { 231 throw new IllegalArgumentException("Non-null start must be greater than end for negative step"); 232 } 233 } 234 } 235 this.start = start; 236 } 237 238 /** 239 * Set the stopping index of the slice. If the stop point of the Slice is 240 * {@code null}, it will be set to the stop argument default to the end of 241 * the dimension that the slice is applied to. 242 * 243 * @param stop 244 * Stopping index of the Slice, may be {@code null} 245 */ 246 public void setStop(Integer stop) { 247 if (stop != null && length > 0) { 248 if (step > 0) { 249 int beg = start == null ? 0 : start; 250 if (stop < beg) { 251 throw new IllegalArgumentException("Non-null stop must be greater than or equal to beginning"); 252 } 253 } else { 254 int beg = start == null ? length - 1 : start; 255 if (stop >= beg) { 256 throw new IllegalArgumentException("Non-null stop must be less than beginning for negative step"); 257 } 258 } 259 if (stop > length) 260 stop = length; 261 } 262 this.stop = stop; 263 } 264 265 /** 266 * Move the start and end to an other index keeping the same step and the 267 * same gap between the two values 268 * 269 * @param beg 270 * New starting point 271 * @return Return {@code true} if the end was reached, {@code false} in the 272 * other case. 273 */ 274 public boolean setPosition(int beg) { 275 boolean end = false; 276 int len = getNumSteps(); 277 int max = getNumSteps(beg, length, step); 278 if (len > max) { 279 len = max; 280 end = true; 281 } 282 start = beg; 283 stop = start + (len - 1) * step + 1; 284 return end; 285 } 286 287 /** 288 * Returns the index of the n-th step inside of the slice 289 * 290 * @param n 291 * Wanted step index in the slice 292 * @return Return the index of the step inside of the Slice 293 */ 294 public int getPosition(int n) { 295 if (n < 0) 296 throw new IllegalArgumentException("Given n-th step should be non-negative"); 297 if (n >= getNumSteps()) 298 throw new IllegalArgumentException("N-th step exceeds extent of slice"); 299 int beg; 300 if (start == null) { 301 if (step < 0) { 302 if (length < 0) { 303 if (stop == null) { 304 throw new IllegalStateException("Length or stop should be set"); 305 } 306 beg = stop - 1; 307 } else { 308 beg = length - 1; 309 } 310 } else { 311 beg = 0; 312 } 313 } else { 314 beg = start; 315 } 316 return beg + step * n; 317 } 318 319 /** 320 * Set the step size inside of the Slice. If the the wanted step is set to 321 * {@code null}, it will be set by default to 1. 322 * 323 * @param step 324 * New wanted step, may be {@code null} 325 */ 326 public void setStep(int step) { 327 if (step == 0) { 328 throw new IllegalArgumentException("Step must not be zero"); 329 } 330 this.step = step; 331 } 332 333 /** 334 * Appends a String representation of the Slice comparable to the python 335 * representation. 336 * 337 * @param s 338 * String builder 339 * @param len 340 * Maximal length of the Slice, or -1 if not set 341 * @param beg 342 * Start index of the Slice 343 * @param end 344 * Stop index of the Slice 345 * @param del 346 * Step of the Slice 347 */ 348 public static void appendSliceToString(final StringBuilder s, final int len, final int beg, final int end, 349 final int del) { 350 int o = s.length(); 351 if (del > 0) { 352 if (beg != 0) 353 s.append(beg); 354 } else { 355 if (beg != len - 1) 356 s.append(beg); 357 } 358 359 int n = getNumSteps(beg, end, del); 360 if (n == 1) { 361 if (s.length() == o) { 362 s.append(beg); 363 } 364 return; 365 } 366 367 s.append(':'); 368 369 if (del > 0) { 370 if (end != len) 371 s.append(end); 372 } else { 373 if (end != -1) 374 s.append(end); 375 } 376 377 if (del != 1) { 378 s.append(':'); 379 s.append(del); 380 } 381 } 382 383 /** 384 * Returns a string construction of the slice with the python form. 385 * 386 * @return Constructed String. 387 */ 388 @Override 389 public String toString() { 390 StringBuilder s = new StringBuilder(); 391 appendSliceToString(s, length, start != null ? start : (step > 0 ? 0 : length - 1), 392 stop != null ? stop : (step > 0 ? length : -1), step); 393 return s.toString(); 394 } 395 396 /** 397 * Returns the number of steps inside of the Slice 398 * 399 * @return Number of steps inside of the Slice 400 */ 401 public int getNumSteps() { 402 if (length < 0) { 403 if (stop == null) 404 throw new IllegalStateException("Slice is underspecified - stop is null and length is negative"); 405 int beg = start == null ? (step > 0 ? 0 : stop - 1) : start; 406 if (step > 0 && stop <= beg) 407 return 0; 408 if (step < 0 && stop > beg) 409 return 0; 410 return getNumSteps(beg, stop, step); 411 } 412 int beg = start == null ? (step > 0 ? 0 : length - 1) : start; 413 int end = stop == null ? (step > 0 ? length : -1) : stop; 414 return getNumSteps(beg, end, step); 415 } 416 417 /** 418 * Returns the number of steps inside of the Slice from a point to an other 419 * point minus 1 because this is an exclusive index 420 * 421 * @param beg 422 * Starting point 423 * @param end 424 * (exclusive) Stopping point 425 * @return Numbers of steps between the 2 limits. 426 */ 427 public int getNumSteps(int beg, int end) { 428 return getNumSteps(beg, end, step); 429 } 430 431 private static int getNumSteps(int beg, int end, int step) { 432 int del = step > 0 ? 1 : -1; 433 return Math.max(0, (end - beg - del) / step + 1); 434 } 435 436 /** 437 * Returns the last value inside of Slice. 438 * 439 * @return Last value in the slice, it can be a lower value than the start 440 * if the step is going backward 441 */ 442 public int getEnd() { 443 int n = getNumSteps() - 1; 444 if (n < 0) 445 throw new IllegalStateException("End is not defined"); 446 447 return getPosition(n); 448 } 449 450 /** 451 * Flips the Slice direction, after this operation, the slice begins at 452 * previous end point, steps in the opposite direction, and finishes at the 453 * previous start point. 454 * <p> 455 * Note : the stop value may not be preserved across two flips 456 * </p> 457 * 458 * @return Flipped Slice. 459 */ 460 public Slice flip() { 461 if (length < 0) { 462 Integer tmp = start == null ? null : start - step; 463 start = stop == null ? null : getEnd(); 464 stop = tmp; 465 } else { 466 Integer tstart = start; 467 start = stop == null ? null : getEnd(); 468 stop = tstart == null ? null : tstart - step; 469 } 470 step = -step; 471 472 return this; 473 } 474 475 /** 476 * Populates given start, stop, step arrays from given slice array 477 * 478 * @param slice 479 * Input array of Slices wanted to convert 480 * @param shape 481 * Input array of Slices shapes 482 * @param start 483 * Output array of Slices starts 484 * @param stop 485 * Output array of Slices stops 486 * @param step 487 * Output array of Slices steps 488 */ 489 public static void convertFromSlice(final Slice[] slice, final int[] shape, final int[] start, final int[] stop, 490 final int[] step) { 491 final int rank = shape.length; 492 final int length = slice == null ? 0 : slice.length; 493 494 int i = 0; 495 for (; i < length; i++) { 496 if (length > rank) 497 break; 498 499 Slice s = slice[i]; 500 if (s == null) { 501 start[i] = 0; 502 stop[i] = shape[i]; 503 step[i] = 1; 504 continue; 505 } 506 int n; 507 if (s.start == null) { 508 start[i] = s.step > 0 ? 0 : shape[i] - 1; 509 } else { 510 n = s.start; 511 if (n < 0) 512 n += shape[i]; 513 if (n < 0 || n >= shape[i]) { 514 throw new IllegalArgumentException( 515 String.format("Start is out of bounds: %d is not in [%d,%d)", n, s.start, shape[i])); 516 } 517 start[i] = n; 518 } 519 520 if (s.stop == null) { 521 stop[i] = s.step > 0 ? shape[i] : -1; 522 } else { 523 n = s.stop; 524 if (n < 0) 525 n += shape[i]; 526 if (n < 0 || n > shape[i]) { 527 throw new IllegalArgumentException( 528 String.format("Stop is out of bounds: %d is not in [%d,%d)", n, s.stop, shape[i])); 529 } 530 stop[i] = n; 531 } 532 533 n = s.step; 534 if (n == 0) { 535 throw new IllegalArgumentException("Step cannot be zero"); 536 } 537 if (n > 0) { 538 if (start[i] > stop[i]) 539 throw new IllegalArgumentException("Start must be less than stop for positive steps"); 540 } else { 541 if (start[i] < stop[i]) 542 throw new IllegalArgumentException("Start must be greater than stop for negative steps"); 543 } 544 step[i] = n; 545 } 546 for (; i < rank; i++) { 547 start[i] = 0; 548 stop[i] = shape[i]; 549 step[i] = 1; 550 } 551 } 552 553 /** 554 * Converts a set of integer arrays in a slice array 555 * 556 * @param start 557 * Array of Slices starts 558 * @param stop 559 * Array of Slices stops 560 * @param step 561 * Array of Slices steps 562 * @return Slice array corresponding to the starts, stops and steps arrays 563 * entered. 564 */ 565 public static Slice[] convertToSlice(final int[] start, final int[] stop, final int[] step) { 566 int orank = start.length; 567 568 if (stop.length != orank || step.length != orank) { 569 throw new IllegalArgumentException("All arrays must be same length"); 570 } 571 572 Slice[] slice = new Slice[orank]; 573 574 for (int j = 0; j < orank; j++) { 575 slice[j] = new Slice(start[j], stop[j], step[j]); 576 } 577 578 return slice; 579 } 580 581 /** 582 * Converts string in a Slice array 583 * 584 * @param sliceString 585 * String of the Slice array 586 * @return Slice array created from the given string 587 */ 588 public static Slice[] convertFromString(String sliceString) { 589 590 String clean = sliceString.replace("[", "").replace("]", ""); 591 592 String[] sub = clean.split(","); 593 594 Slice[] slices = new Slice[sub.length]; 595 596 for (int i = 0; i < sub.length; i++) { 597 String s = sub[i]; 598 599 Slice slice = new Slice(); 600 slices[i] = slice; 601 602 int idx0 = s.indexOf(":"); 603 604 int n = 0; 605 if (idx0 == -1) { 606 n = Integer.parseInt(s); 607 slice.setStart(n); 608 slice.setStop(n + 1); 609 continue; 610 } else if (idx0 != 0) { 611 n = Integer.parseInt(s.substring(0, idx0)); 612 } 613 slice.setStart(n); 614 615 idx0++; 616 int idx1 = s.indexOf(":", idx0); 617 if (idx1 == -1) { 618 String t = s.substring(idx0).trim(); 619 if (t.length() == 0) 620 continue; 621 622 slice.setStop(Integer.parseInt(t)); 623 continue; 624 } else if (idx1 != idx0) { 625 slice.setStop(Integer.parseInt(s.substring(idx0, idx1))); 626 } 627 628 String t = s.substring(idx1 + 1).trim(); 629 if (t.length() > 0) 630 slice.setStep(Integer.parseInt(t)); 631 } 632 633 return slices; 634 } 635 636 /** 637 * Creates a string representing the slice taken from given shape 638 * 639 * @param shape 640 * Array of Slices shapes 641 * @param start 642 * Array of Slices starts 643 * @param stop 644 * Array of Slices stops 645 * @param step 646 * Array of Slices steps 647 * @return String representation of the Slice 648 */ 649 public static String createString(final int[] shape, final int[] start, final int[] stop, final int[] step) { 650 final int rank = shape.length; 651 if (rank == 0) { 652 return ""; 653 } 654 StringBuilder s = new StringBuilder(); 655 for (int i = 0; i < rank; i++) { 656 int l = shape[i]; 657 int d = step == null ? 1 : step[i]; 658 int b = start == null ? (d > 0 ? 0 : l - 1) : start[i]; 659 int e = stop == null ? (d > 0 ? l : -1) : stop[i]; 660 661 appendSliceToString(s, l, b, e, d); 662 s.append(','); 663 } 664 665 return s.substring(0, s.length() - 1); 666 } 667 668 /** 669 * Creates a string representation of slices. 670 * 671 * @param slices 672 * Wanted Slices to put inside of the string representation 673 * @return Return the string representation of the Slices entered. 674 */ 675 public static String createString(Slice... slices) { 676 if (slices == null || slices.length == 0) { 677 return ""; 678 } 679 680 StringBuilder t = new StringBuilder(); 681 for (Slice s : slices) { 682 t.append(s != null ? s.toString() : ':'); 683 t.append(','); 684 } 685 686 return t.substring(0, t.length() - 1); 687 } 688}