aboutsummaryrefslogtreecommitdiff
path: root/challenge-131/iangoodnight/javascript/README.md
blob: 855573cd91102c47de614df2ef3262dcdcc4a584 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# [Perl Weekly Challenge - 131] _JavaScript Edition_

Five weeks into the Perl Weekly Challenge, my Perl is getting a lot sharper, but
I still have a lot of fun coming up with JavaScript solutions.  Generally
speaking, I try to make my code as declarative and self-documenting as possible,
however, the challenges this week are pretty straightforward, so I allowed
myself some room for solutions that I just dig aesthetically in my JavaScript
solutions this week.

# Task 1 > Consecutive Arrays

You are given a sorted list of unique positive integers.

Write a script to return lists of arrays where the arrays are consecutive
integers.

**Example 1:**

```javascript
const input = [1, 2, 3, 6, 7, 8, 9];

const output =[[1, 2, 3], [6, 7, 8, 9]];

```

**Example 2:**

```javascript
const input = [11, 12, 14, 17, 18, 19];

const output = [[2], [4], [6], [8]];

```

**Example 3:**

```javascript
const input = [2, 4, 6, 8];

const output = [[2], [4], [6], [8]];

```

**Example 4:**

```javascript
const input = [1, 2, 3, 4, 5];

const output = [[1, 2, 3, 4, 5]];

```

### Solution

When I am working with an array as input, my first question is, am I using
`map`, `filter`, `reduce`, or `forEach`, as these are functional operations
that,  between the four of them, are generally non-destructive and solve most use
cases.  Generally speaking, I am using a map if my output is meant to be an
`Array` the same length as my input, I am using `map`.  If my output is supposed
to be a shorter array, I'm probably going to reach for `filter`.  If my
operation calls for or causes side effects, `forEach` is probably the right
choice.  But, for anything else, I reach for `reduce`.  The general idea with
the `reduce` method to reduce an array to a single value like a `sum` or a `max`
value.  Really though, you can use reduce to return anything else that takes an
array as input.  Here, we use a `reduce` function to reduce our array to an
array of arrays as described in the examples above.

```javascript
// Task 1 Solution
function reduceToConsecutive(input = []) {
  return input.reduce((reduced, element) => {
    // If no arrays in `reduced`, initialize our first set and return.
    if (reduced.length === 0) return [[element]];
    // Otherwise, pop off the last set for inspection.
    const lastSet = reduced.pop();
    // Take a copy of the last value.
    const [ last ] = lastSet.slice(-1);
    // if the last value is equal to one less than our current value, push it
    // to the last set and push our last set back into our `reduced` array.
    if (last === element - 1) {
      lastSet.push(element);
      reduced.push(lastSet);
    } else {
      // Else, push our unaltered last set, and our new set back to the array.
      reduced.push(lastSet, [element]);
    }
    return reduced;
  }, []);
}
```

The comments, though a little gratuitous, should make our operations clear.

### ch-1.js

Running `./ch-1.js` tests our solution against the sample test cases found at
`../test_cases/ch-1/`.  Optionally, `ch-1.js` can be run with a path argument
pointing to a test file or directory of test files (i.e., `./ch-1.js ./test`).
Our test files must be comprised of lines alternating between inputs and 
expected results formatted as shown in the examples above (i.e., `(1, 2, 4)` and
`([1, 2], 4)`). It looks a little funny in JavaScript, but this is the _Perl_
Weekly Challenge Club, after all.  Lines beginning with `#` and blank lines will
be ignored.  Lines are parsed with a helper function, `eval_input`.

```javascript
function evalInput(input = '') {
  if (input.indexOf('[') === -1) {
    // No inner sets, input string, split and return
    return input
      .replace(/\(|\)/g, '')
      .split(/\s*,\s*/)
      .map((element) => parseInt(element)
    );
  }
  // Else, answer string, parse and return
  return [...input.matchAll(/\[([^\]]*)\]/g)].map(match =>
    match[1].split(/\s*,\s*/).map(el => parseInt(el))
  );
}

```

This function converts our Perlish input into arrays we can read into our
solution.

#### Output

Running `ch-1.js` with no arguments (assuming the existence of our sample
test directory) outputs:

```
../test_cases/ch-1/case-1.txt
=============================

Input: (1, 2, 3, 6, 7, 8, 9)
Expected: ([1, 2, 3], [6, 7, 8, 9])
Result: ([1, 2, 3], [6, 7, 8, 9])
Passed ⚐


../test_cases/ch-1/case-2.txt
=============================

Input: (11, 12, 14, 17, 18, 19)
Expected: ([11, 12], [14], [17, 18, 19])
Result: ([11, 12], [14], [17, 18, 19])
Passed ⚐


../test_cases/ch-1/case-3.txt
=============================

Input: (2, 4, 6, 8)
Expected: ([2], [4], [6], [8])
Result: ([2], [4], [6], [8])
Passed ⚐


../test_cases/ch-1/case-4.txt
=============================

Input: (1, 2, 3, 4, 5)
Expected: ([1, 2, 3, 4, 5])
Result: ([1, 2, 3, 4, 5])
Passed ⚐


../test_cases/ch-1/case-5.txt
=============================

Input: (-1, 0, 1, 11, 101, 102, 103)
Expected: ([-1, 0, 1], [11], [101, 102, 103])
Result: ([-1, 0, 1], [11], [101, 102, 103])
Passed ⚐

Input: (1, 2, 3, 7, 9)
Expected: ([1, 2, 3], [7], [9])
Result: ([1, 2, 3], [7], [9])
Passed ⚐
 
```

Our output shows all our tests as passing, indicating either that our solution 
is correct or our testing methods are flawed.

## Task 2 > Find Pairs

You are given a string of delimiter pairs and a string to search.

Write a script to return two strings, the first with any characters matching
the "opening character" set, the second with any matching the "closing 
character" set.

**Example 1:**

```
Input:

Delimiter pairs: ""[]()
Search String: "I like (parens) and the Apple ][+" they said.

Output:

  "(["
  "])"

```

**Example 2:**

```
Input:

Delimiter pairs: **//<>
Search String: /* This is a comment (in some languages) * / <could be a tag>

Output:

/**/<
/**/>

```

### Solution

From the examples provided, it looks like we are testing for _any_ instance of
our opening or closing delimiter, regardless of whether or not they are
fulfilling the function of opening or closing.  Specifically, matching tags like
`*`, `'`, or `"` will show up in both lines of output.  This makes for a simpler
solution, as we will not have to differentiate based on the actual function our
delimiters are serving (opening or closing).  We will partition our opening and
closing delimiters (again reaching for that flexible `reduce` function) assuming
that they are listed in pairs as shown in the example input (i.e.: `[]{}""`),
and then test our search string character-by-character via another `reduce`
function looking for matches.

```javascript
// Task 2 Solution
function findPairs(delimiters = '', string = '') {
  const [openSet, closeSet] = [...delimiters].reduce(
    ([open, close], el, idx) => {
      if (idx % 2) return [open, [...close, el]];
      return [[...open, el], close];
    },
    [[], []]
  );
  return [...string].reduce(
    ([open, close], el) => {
      if (openSet.includes(el)) open.push(el);
      if (closeSet.includes(el)) close.push(el);
      return [open, close];
    },
    [[], []]
  );
}

```

### `ch-2.js`

Generally speaking, when approaching these challenges, I have been consistently
writing a solution, creating a directory of test cases, and writing my
submission framed as a series of tests run against the solution.  So, in the
solution for the first task, running the file `ch-1.js` automatically looks for
that directory and those test cases unless it is run with a path argument
pointing to some other local test.  That choice was mainly just an excuse to
have some fun parsing different types of input files.  For `ch-2.js`, however,
the prospect of coming up with a bunch of delimiter tests cases seemed
dangerously boring.  So, instead, `ch-2.js` is written as an interactive CLI.
Running `./ch-2.js` starts our utility.  Sample output is shown below.

#### Output

```
$ ./ch-2.js
Welcome to delimiter search (type 'exit' or Ctrl+c to quit).
Please provide delimiter string (ie: ''[]{}<>**): ""[]()
Please provide search string: "I like (parens) and the Apple ][+" they said.
Results:
"(["
")]"
Welcome to delimiter search (type 'exit' or Ctrl+c to quit).
Please provide delimiter string (ie: ''[]{}<>**): **//<>
Please provide search string: /* This is a comment (in some languages) * / <could be a tag>
Results:
/**/<
/**/>
Welcome to delimiter search (type 'exit' or Ctrl+c to quit).
Please provide delimiter string (ie: ''[]{}<>**): exit"
```

## Coming Soon

Hopefully, by next week, I will be set up to start posting some of these on my
own, as of yet non-existent, blog.  It is my hope to explore programming in
general, using the PWC as an excuse to explore more `JavaScript`, `Perl`, and
hopefully some `Ruby`, `Bash`, and `Powershell` as well.  Stay tuned.


[Perl Weekly Challenge - 131]: https://theweeklychallenge.org/blog/perl-weekly-challenge-131/