array-view-ts

Форк
0
/
structs.ts 
276 строк · 7.4 Кб
1
import { IndexError, ValueError } from "./excpetions";
2
import { normalizeIndex } from "./utils";
3

4
/**
5
 * Represents a slice definition for selecting a range of elements.
6
 */
7
export class Slice {
8
  /**
9
   * The start index of the slice range.
10
   */
11
  public readonly start: number | undefined;
12
  /**
13
   * The end index of the slice range.
14
   */
15
  public readonly end: number | undefined;
16
  /**
17
   * The step size for selecting elements in the slice range.
18
   */
19
  public readonly step: number | undefined;
20

21
  /**
22
   * Converts a slice string or Slice object into a Slice instance.
23
   *
24
   * @param {string | Array<number | undefined> | Slice} s - The slice string/array or Slice object to convert.
25
   * @returns {Slice} The converted Slice instance.
26
   */
27
  public static toSlice(s: string | Array<number | undefined> | Slice): Slice {
28
    if (s instanceof Slice) {
29
      return s;
30
    }
31

32
    if (Array.isArray(s) && this.isSliceArray(s)) {
33
      return new Slice(...(s as Array<number | undefined>));
34
    }
35

36
    if (!this.isSliceString(s)) {
37
      throw new ValueError(`Invalid slice: "${String(s)}".`);
38
    }
39

40
    const slice = this.parseSliceString(s as string);
41

42
    return new Slice(...slice);
43
  }
44

45
  /**
46
   * Checks if the provided value is a Slice instance or a valid slice string.
47
   *
48
   * @param {unknown} s - The value to check.
49
   *
50
   * @returns {boolean} True if the value is a Slice instance or a valid slice string, false otherwise.
51
   */
52
  public static isSlice(s: unknown): boolean {
53
    return (s instanceof Slice) || this.isSliceString(s);
54
  }
55

56
  /**
57
   * Checks if the provided value is a valid slice string.
58
   *
59
   * @param {unknown} s - The value to check.
60
   *
61
   * @returns {boolean} True if the value is a valid slice string, false otherwise.
62
   */
63
  public static isSliceString(s: unknown): boolean {
64
    if (typeof s !== "string") {
65
      return false;
66
    }
67

68
    if (!Number.isNaN(Number(s))) {
69
      return false;
70
    }
71

72
    if (!s.match(/^-?[0-9]*:?-?[0-9]*:?-?[0-9]*$/)) {
73
      return false;
74
    }
75

76
    const slice = this.parseSliceString(s);
77

78
    return slice.length >= 1 && slice.length <= 3;
79
  }
80

81
  /**
82
   * Checks if the provided value is a valid slice array.
83
   *
84
   * @param {unknown} s - The value to check.
85
   *
86
   * @returns {boolean} True if the value is a valid slice array, false otherwise.
87
   */
88
  public static isSliceArray(s: unknown): boolean {
89
    if (!Array.isArray(s)) {
90
      return false;
91
    }
92

93
    if(!(s.length >= 0 && s.length <= 3)) {
94
      return false;
95
    }
96

97
    for (const item of s) {
98
      if (item !== undefined && !Number.isInteger(item)) {
99
        return false;
100
      }
101
    }
102

103
    return true;
104
  }
105

106
  /**
107
   * Creates a new Slice instance with optional start, end, and step values.
108
   *
109
   * @param {number} [start] - The start index of the slice range.
110
   * @param {number} [end] - The end index of the slice range.
111
   * @param {number} [step] - The step size for selecting elements in the slice range.
112
   */
113
  constructor(start?: number, end?: number, step?: number) {
114
    this.start = start;
115
    this.end = end;
116
    this.step = step;
117
  }
118

119
  /**
120
   * Normalizes the slice parameters based on the container length.
121
   *
122
   * @param {number} containerLength - The length of the container or array.
123
   *
124
   * @returns {NormalizedSlice} The normalized slice parameters.
125
   */
126
  public normalize(containerLength: number): NormalizedSlice {
127
    let step = this.step ?? 1;
128

129
    if (step > 0) {
130
      let start = this.start ?? 0;
131
      let end = this.end ?? containerLength;
132

133
      [start, end, step] = [Math.round(start), Math.round(end), Math.round(step)];
134

135
      start = normalizeIndex(start, containerLength, false);
136
      end = normalizeIndex(end, containerLength, false);
137

138
      if (start >= containerLength) {
139
        start = end = containerLength - 1;
140
      }
141

142
      start = this.squeezeInBounds(start, 0, containerLength - 1);
143
      end = this.squeezeInBounds(end, 0, containerLength);
144

145
      if (end < start) {
146
        end = start;
147
      }
148

149
      return new NormalizedSlice(start, end, step);
150
    } else if (step < 0) {
151
      let start = this.start ?? containerLength - 1;
152
      let end = this.end ?? -1;
153

154
      [start, end, step] = [Math.round(start), Math.round(end), Math.round(step)];
155

156
      start = normalizeIndex(start, containerLength, false);
157

158
      if (!(this.end === undefined)) {
159
        end = normalizeIndex(end, containerLength, false);
160
      }
161

162
      if (start < 0) {
163
        start = end = 0;
164
      }
165

166
      start = this.squeezeInBounds(start, 0, containerLength - 1);
167
      end = this.squeezeInBounds(end, -1, containerLength);
168

169
      if (end > start) {
170
        end = start;
171
      }
172

173
      return new NormalizedSlice(start, end, step);
174
    }
175

176
    throw new IndexError("Step cannot be 0.");
177
  }
178

179
  /**
180
   * Returns the string representation of the Slice.
181
   *
182
   * @returns {string} The string representation of the Slice.
183
   */
184
  public toString(): string {
185
    return `${this.start ?? ""}:${this.end ?? ""}:${this.step ?? ""}`;
186
  }
187

188
  /**
189
   * Parses a slice string into an array of start, end, and step values.
190
   *
191
   * @param {string} s - The slice string to parse.
192
   *
193
   * @returns {(number | undefined)[]} An array of parsed start, end, and step values.
194
   */
195
  private static parseSliceString(s: string): (number | undefined)[] {
196
    return s.split(":")
197
      .map(x => x.trim())
198
      .map(x => (x === "") ? undefined : parseInt(x));
199
  }
200

201
  /**
202
   * Constrains a value within a given range.
203
   *
204
   * @param {number} x - The value to constrain.
205
   * @param {number} min - The minimum allowed value.
206
   * @param {number} max - The maximum allowed value.
207
   *
208
   * @returns {number} The constrained value.
209
   */
210
  private squeezeInBounds(x: number, min: number, max: number): number {
211
    return Math.max(min, Math.min(max, x));
212
  }
213
}
214

215
/**
216
 * Represents a normalized slice definition with start, end, and step values.
217
 */
218
export class NormalizedSlice extends Slice {
219
  /**
220
   * The start index of the normalized slice.
221
   */
222
  public readonly start: number;
223
  /**
224
   * The end index of the normalized slice.
225
   */
226
  public readonly end: number;
227
  /**
228
   * The step size for selecting elements in the normalized slice.
229
   */
230
  public readonly step: number;
231

232
  /**
233
   * Creates a new NormalizedSlice instance with start, end, and step values.
234
   *
235
   * @param {number} start - The start index of the normalized slice.
236
   * @param {number} end - The end index of the normalized slice.
237
   * @param {number} step - The step size for selecting elements in the normalized slice.
238
   */
239
  constructor(start: number, end: number, step: number) {
240
    super();
241
    this.start = start;
242
    this.end = end;
243
    this.step = step;
244
  }
245

246
  /**
247
   * Returns the length of the normalized slice.
248
   *
249
   * @type {number}
250
   */
251
  get length(): number {
252
    return Math.ceil(Math.abs(((this.end - this.start) / this.step)));
253
  }
254

255
  /**
256
   * Converts the provided index to the actual index based on the normalized slice parameters.
257
   *
258
   * @param {number} i - The index to convert.
259
   *
260
   * @returns {number} The converted index value.
261
   */
262
  public convertIndex(i: number): number {
263
    return this.start + normalizeIndex(i, this.length, false) * this.step;
264
  }
265

266
  /**
267
   * Generate an iterator for iterating over the elements in the normalized slice range.
268
   *
269
   * @returns {IterableIterator<number>} An iterator for the normalized slice range.
270
   */
271
  public* toRange(): IterableIterator<number> {
272
    for (let i = 0; i < this.length; ++i) {
273
      yield this.convertIndex(i);
274
    }
275
  }
276
}
277

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.