-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProblem6.cs
665 lines (504 loc) · 21.7 KB
/
Problem6.cs
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
using DataStructuresAndAlgos;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
namespace Assignment5
{
class Problem6
{
public static void RunTests()
{
// Can't cast from int to char because truncation?
// unchecked would work, but doing a proper conversion
// with the Convert class is better
var testCases = new List<TestCase>
{
new TestCase
{
InputAsString = "sstnhtn",
CorrectNREAfterEachRead = new int[]{ 's', -1, 't', 't', 't', 'n', 'h' },
},
new TestCase
{
InputAsString = "aabc",
CorrectNREAfterEachRead = new int[]{ 'a', -1, 'b', 'b' },
},
new TestCase
{
InputAsString = "ccaba",
CorrectNREAfterEachRead = new int[]{ 'c', -1, 'a', 'b', 'b' },
},
new TestCase
{
InputAsString = String.Empty,
CorrectNREAfterEachRead = new int[]{ -1 },
},
};
string intro =
"==============\n" +
"= Problem #6 =\n" +
"==============\n" +
"\n" +
"Given an input stream of n characters consisting only of " +
"small case alphabets the task is to find the first non " +
"repeating character each time a character is inserted to " +
"the stream. If no non repeating element is found print -1.";
Console.WriteLine(intro);
int testOopsCount = 0;
for (var i = 0; i < testCases.Count; ++i)
{
Console.WriteLine($"\nTest #{i + 1}:");
Console.WriteLine($"For the stream: {testCases[i].InputAsString}");
//Console.WriteLine($"The correct result is: {testCases[i].CorrectNREAfterEachRead}");
PrintFirstNRE_ForVideo(testCases[i].InputAsStream);
//string resultMessage;
//if (Enumerable.SequenceEqual(testCaseResult, testCases[i].CorrectNREAfterEachRead))
//{
// resultMessage = "SUCCESS";
//}
//else
//{
// ++testOopsCount;
// resultMessage = "OOPS";
//}
//Console.WriteLine($"{resultMessage}!");
//Console.WriteLine($"Your answer is: {testCaseResult}");
}
//var testCount = testCases.Count;
//var testSuccessCount = testCount - testOopsCount;
//Console.WriteLine($"\n\nOut of {testCount} tests total,\n");
//Console.WriteLine($"{testSuccessCount}/{testCount} tests succeeded, and");
//Console.WriteLine($"{testOopsCount}/{testCount} tests oopsed.\n");
//if (testOopsCount == 0)
//{
// Console.WriteLine($"YAY! All tests succeeded! :D\n");
//}
}
const int NO_NRE = -1;
const int LETTER_COUNT = 'z' - 'a';
public static void PrintFirstNRE_ForVideo(StreamReader sr)
{
if (sr == null)
throw new ArgumentNullException("Parameter StreamReader sr is null.");
if (sr.EndOfStream == true)
Console.WriteLine(
$"No letters found in stream.\n" +
$"No non-repeating letters. {NO_NRE}");
var letterTracker = new OrderedLetter_ForVideo[LETTER_COUNT];
for (var i = 0; i < LETTER_COUNT; ++i)
letterTracker[i] = new OrderedLetter_ForVideo();
while (sr.EndOfStream == false)
{
var currLetter = sr.Read();
ValidateLetter(currLetter);
Console.WriteLine(
$"Reading '{Convert.ToChar(currLetter)}' from stream.");
letterTracker[currLetter - 'a'].LogAsRead();
var currFirstNRE = FindCurrentFirstNRE_ForVideo(letterTracker);
if (currFirstNRE == NO_NRE)
Console.WriteLine($"No non-repeating letters. {NO_NRE}");
else
Console.WriteLine($"First non-repeating letter so far is: {Convert.ToChar(currFirstNRE)}");
}
}
public static void ValidateLetter(int letter)
{
if (letter < 'a' || letter > 'z')
throw new ArgumentException(
"Stream may contain only letters between 'a' and 'z', inclusive.");
}
private class OrderedLetter_ForVideo
{
public int FirstSeenOrder { get; private set; }
public bool DoesRepeat { get; private set; }
public OrderedLetter_ForVideo()
{
FirstSeenOrder = NOT_SEEN;
DoesRepeat = false;
}
private const int NOT_SEEN = -2;
private static int nextSeenOrderValue = 0;
public void LogAsRead()
{
if (FirstSeenOrder < 0)
FirstSeenOrder = nextSeenOrderValue++;
else
DoesRepeat = true;
}
}
private static int FindCurrentFirstNRE_ForVideo(OrderedLetter_ForVideo[] letterTracker)
{
var allCurrentNREQuery = letterTracker
.Where(ol => ol.FirstSeenOrder >= 0)
.Where(ol => ol.DoesRepeat == false);
if (allCurrentNREQuery.Count() == 0)
return NO_NRE;
var newFirstNRE = allCurrentNREQuery
.OrderBy(ol => ol.FirstSeenOrder)
.First();
for (var i = 0; i < LETTER_COUNT; ++i)
{
if (ReferenceEquals(letterTracker[i], newFirstNRE))
return 'a' + i;
}
throw new ArgumentException(
"An element in letterTracker met the query criteria," +
"but that element isn't in letterTracker? Wat.");
}
private class OrderedLetter
{
private static int nextSeenOrderValue = 0;
private const int NOT_SEEN = -2;
public OrderedLetter()
{
FirstSeenOrder = NOT_SEEN;
DoesRepeat = false;
}
// EDIT: Not returning true/false anymore, current version
// logic handles it seperately
// Returns true if this is a new sighting of the letter
// Returns false if letter has been seen before
public void LogAsRead()
{
if (FirstSeenOrder < 0)
{
FirstSeenOrder = nextSeenOrderValue++;
//if (nextSeenOrderValue > ('z' - 'a' + 1))
// throw new ArgumentOutOfRangeException(
// "Somehow we've seen more letters then there exist letters.");
}
else
{
DoesRepeat = true;
}
}
public int FirstSeenOrder { get; private set; }
public bool DoesRepeat { get; private set; }
}
private static int FindCurrentFirstNRE(OrderedLetter[] letterTracker)
{
var allCurrentNREQuery = letterTracker
.Where(ol => ol.FirstSeenOrder >= 0)
.Where(ol => ol.DoesRepeat == false);
// I think Count only does a single traversal to check and count the elements, afaik
if (allCurrentNREQuery.Count() == 0)
{
return NO_NRE;
}
// I think that this uses lazy evaluation in that OrderBy remains only as a
// stored instruction until a LINQ "execute" command is added
// it only needs to walk the array once and find the element
// that meets the other criteria with the smallest FirstSeenOrder
// Doesn't need to copy the array or do a full sort, etc.
var newFirstNRE = allCurrentNREQuery
.OrderBy(ol => ol.FirstSeenOrder)
.First();
// That is, First does the least amount of work necessary to figure
// out what is the first element of the sequence
// Reverse lookup to figure out what letter the found newFirstNRE
// corresponds to.
// An alternative to this approach is for each OrderedLetter to also
// store what letter it reprents, which seems silly and can fall out
// of sync. Or we could use use a dictionary instead of an array
// to store the OrderedLetter objects (which is overkill)
// Then we could iterate on the kvps and have access to the keys that way.
for (var i = 0; i < LETTER_COUNT; ++i)
{
if (ReferenceEquals(letterTracker[i], newFirstNRE))
return 'a' + i;
}
throw new ArgumentException(
"An element in letterTracker met the query criteria," +
"but that element isn't in letterTracker? Wat.");
}
public static List<int> PrintFirstNRE(StreamReader sr)
{
if (sr == null)
throw new ArgumentNullException("Parameter StreamReader sr is null.");
//// Debug to see what we got in that thar stream
//while (sr.EndOfStream == false)
//{
// Console.WriteLine(Convert.ToChar(sr.Read()));
//}
var letterTracker = new OrderedLetter[LETTER_COUNT];
for (var i = 0; i < LETTER_COUNT; ++i)
{
letterTracker[i] = new OrderedLetter();
}
//var letters = new LetterTracker();
// "cannot assign to letter because it is a foreach iteration variable"
//foreach (var letter in letters)
//{
// letter = new orderedletter();
//}
var firstNREList = new List<int>();
// No. If stream is empty this will fail:
//var currFirstNRE = sr.Read();
// Either use a single Read() inside the end check of the while loop
// Or add protection out here
// (Fixed with the former)
//ValidateLetter(currFirstNRE);
//letters.SeeLetter(currFirstNRE);
//firstNREList.Add(currFirstNRE);
while (sr.EndOfStream == false)
{
var currLetter = sr.Read();
Console.WriteLine($"Reading '{Convert.ToChar(currLetter)}' from stream.");
ValidateLetter(currLetter);
letterTracker[currLetter - 'a'].LogAsRead();
var currFirstNRE = FindCurrentFirstNRE(letterTracker);
firstNREList.Add(currFirstNRE);
if (currFirstNRE == NO_NRE)
{
Console.WriteLine($"No non-repeating letters. {NO_NRE}");
}
else
{
Console.WriteLine($"First non-repeating letter so far is: {Convert.ToChar(currFirstNRE)}");
}
}
// Fixup for if there were no letters in the stream at all
if (firstNREList.Count == 0)
{
Console.WriteLine(
$"No letters found in stream.\n" +
$"No non-repeating letters. {NO_NRE}");
firstNREList.Add(NO_NRE);
}
return firstNREList;
}
private class LetterTracker
{
const int LETTER_COUNT = 'z' - 'a';
private readonly OrderedLetter[] orderedLetters;
public LetterTracker()
{
orderedLetters = new OrderedLetter[LETTER_COUNT];
for (var i = 0; i < LETTER_COUNT; ++i)
{
orderedLetters[i] = new OrderedLetter();
}
}
public int GetNRE()
{
var firstNREQuery = orderedLetters
.Where(ol => ol.HaveSeen == true)
.Where(ol => ol.DoesRepeat == false)
.OrderBy(ol => ol.FirstSeenOrder);
if (firstNREQuery.Count() == 0)
{
return NO_NRE;
}
var newNRE = firstNREQuery.First();
// Reverse lookup to figure out what letter newNRE is
// An alternative to this is for each OrderedLetter to know what letter
// it represents. I like this way better.
for (var i = 0; i < orderedLetters.Length; ++i)
{
if (ReferenceEquals(orderedLetters[i], newNRE))
return 'a' + i;
}
throw new ArgumentOutOfRangeException(
"An element in orderedLetters met the query criteria," +
"but that element isn't in orderedLetters? Wat.");
}
// Do we need HaveSeen?
// Maybe we only need to know if we've seen it when seeing it
// with SeeLetter
public bool HaveSeen(int letter)
{
ValidateLetter(letter);
return orderedLetters[letter - 'a'].HaveSeen;
// Add 'a' before returning letter value back to the user
// subtract 'a' after getting letter value from the user,
// before using it
}
// Returns true if this is a new sighting of the letter
public bool SeeLetter(int letter)
{
ValidateLetter(letter);
return orderedLetters[letter - 'a'].SeeThisLetter();
}
public bool DoesRepeat(int letter)
{
ValidateLetter(letter);
return orderedLetters[letter - 'a'].DoesRepeat;
}
private class OrderedLetter
{
private static int nextSeenOrderValue = 0;
const int NOT_SEEN = -2;
public OrderedLetter()
{
FirstSeenOrder = NOT_SEEN;
DoesRepeat = false;
}
// Returns true if this is a new sighting of the letter
// Returns false if letter has been seen before
public bool SeeThisLetter()
{
if (HaveSeen == false)
{
FirstSeenOrder = nextSeenOrderValue++;
if (nextSeenOrderValue > ('z' - 'a' + 1))
throw new ArgumentOutOfRangeException(
"Somehow we've seen more letters then there exist letters.");
// New sighting of this letter
return true;
}
else
{
if (DoesRepeat == false)
{
DoesRepeat = true;
}
// Not first sighting of this letter
return false;
}
}
public int FirstSeenOrder { get; private set; }
// Maybe don't really need have seen...only do this once
public bool HaveSeen
{
get
{
return FirstSeenOrder >= 0 ? true : false;
}
}
public bool DoesRepeat { get; private set; }
}
}
// "Prints" first non-repeating element from the stream
// to the result List<int> for each letter in the stream
public static List<int> PrintFirstNRE_DoubleClassApproach(StreamReader sr)
{
if (sr == null)
throw new ArgumentNullException("Parameter StreamReader sr is null.");
//// Debug to see what we got in that thar stream
//while (sr.EndOfStream == false)
//{
// Console.WriteLine(Convert.ToChar(sr.Read()));
//}
var letters = new LetterTracker();
var firstNREList = new List<int>();
// No. If stream is empty this will fail
// Either use a single Read() inside the end check of the while loop
// Or add protection out here
var currFirstNRE = sr.Read();
ValidateLetter(currFirstNRE);
letters.SeeLetter(currFirstNRE);
firstNREList.Add(currFirstNRE);
while (sr.EndOfStream == false)
{
var currLetter = sr.Read();
ValidateLetter(currLetter);
letters.SeeLetter(currLetter);
if (currFirstNRE == NO_NRE || currLetter == currFirstNRE)
{
currFirstNRE = letters.GetNRE();
firstNREList.Add(currFirstNRE);
}
else // Same first NRE as last loop
{
firstNREList.Add(currFirstNRE);
}
// Could add a bailout logic feature for the LetterTracker class
// to whittle the count of remaining NRE down to zero
// so doen't have to look for an NRE if we know that there aren't any
// if currLetter != firstNRE, we're done:
// firstNRE can only change when currLetter == firstNRE
// the sighting of currLetter has been logged
// That is, currLetter's sighting order (if this is the first sighting)
// also flagging currLetter as repeating if this is 2nd or later sighting
// The LetterTracker keeps track of all of this
}
return firstNREList;
}
//public static char[] FirstNonRepeatingCharPerChar(StreamReader sr)
//{
// char NO_NRE = Convert.ToChar(-1);
// if (sr == null)
// throw new ArgumentNullException("Parameter StreamReader sr is null.");
// var firstNRE = Convert.ToChar(sr.Read());
// // Older class type with no generic version (keys and values are object)
// // Key type is char
// // Value type is SeenChar, which contains bool DoesRepeat
// var seenChars = new OrderedDictionary();
// seenChars[firstNRE] = new SeenChar
// {
// DoesRepeat = false,
// };
// while (sr.EndOfStream == false)
// {
// var curr = Convert.ToChar(sr.Read());
// if (seenChars.Contains(curr))
// { //Second sighting of this char in the stream
// var currChar = ((SeenChar)seenChars[curr]);
// if ( currChar.DoesRepeat == false )
// { // And just became a repeat
// currChar.DoesRepeat = true;
// if (curr == firstNRE)
// {
// // curr is now repeating, so must find a new NRE
// foreach (var entry in seenChars)
// {
// // Does this give us the value only, or a kvp?
// var seenChar = ((SeenChar)((DictionaryEntry)entry).Value);
// }
// }
// else
// {
// }
// // Wait. This stream is guaranteed to only consist of lowercase letters
// // Don't use a freaking (non-generic) OrderedDictionary
// // Maybe this solution for if the things in the stream could be
// // or represent arbitrary objects
// // just do an array of (26) bools instead
// }
// }
// }
// throw new NotImplementedException();
//}
private class TestCase
{
public string InputAsString { get; set; }
public StreamReader InputAsStream
{
get
{
return new StreamReader(InputAsString.ToStream());
}
}
public int[] CorrectNREAfterEachRead { get; set; }
}
}
// Borrowed static string utility class
// used for testing only
// https://stackoverflow.com/a/38399723/13587176
public static class StringExtensions
{
public static Stream ToStream(this string s)
{
return s.ToStream(Encoding.UTF8);
}
public static Stream ToStream(this string s, Encoding encoding)
{
return new MemoryStream(encoding.GetBytes(s ?? ""));
}
//// Huh. I think that we don't actually have to reverse the string
//// before converting it into a stream
//// A basic solution that isn't robust in the general case:
//// https://stackoverflow.com/questions/228038/best-way-to-reverse-a-string
//public static string Reverse(this string s)
//{
// var charArray = s.ToCharArray();
// Array.Reverse(charArray);
// return new string(charArray);
// // not: return charArray.ToString();
//}
}
}