NivaarExam PrepOfficial exam papers ↗

25-Comp-A4 Program Design and Data Structures · December 2014

Question 8 of 9: File I/O — Replicating and Interleaving a Sound Stream

Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)

Notes on this paper

Paper format. 98-Comp-A4 Program Design and Data Structures, December 2014 — 3 hours, closed book, no calculator permitted. Nine questions, each of equal weight (20 marks); candidates answer any six, so a complete paper is 120 marks. Pseudocode or any high-level language is accepted, and the examiner's note states explicitly that marking emphasises the operation of the program, not syntactic details. All nine questions are answered below, because the whole set is the more useful revision resource. Answers are given in C or C++ as the question dictates; each is compilable as written, but a clear, correctly reasoned pseudocode answer would earn the same marks.

Reference texts for this subject.

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, 4th ed. — tree traversals (ch. 12), partitioning (ch. 7), asymptotic analysis (ch. 3).
  • Weiss, Data Structures and Algorithm Analysis in C, 2nd ed. — linked lists (ch. 3), binary search trees (ch. 4).
  • Deitel & Deitel, C++ How to Program, 10th ed. — class design and operator overloading (ch. 9–10), file streams (ch. 14).
  • Kernighan & Ritchie, The C Programming Language, 2nd ed. — character I/O idioms (ch. 1, 7), pointers and structures (ch. 5–6).
  • Stroustrup, The C++ Programming Language, 4th ed. — value semantics and const-correctness (ch. 16–18).

The Computer Engineering citation list is built around architecture and networking texts (Patterson & Hennessy, Tanenbaum, Mano); this subject is programming and data structures, so the works above are cited instead.

Question 8: File I/O — Replicating and Interleaving a Sound Stream (20 marks)

Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.

Given. A six-value example stream, 17 15 91 68 52 84, and the coded stream it must produce, 17 15 17 91 15 17 68 91 15 52 68 91 84 52 68 84 52 84; a replication factor of three; an input of unknown length read from a file; and an explicit instruction to be as efficient in time and space as possible.

Find. The rule that generates the printed coded sequence, and a program implementing it that reads and writes files without knowing the sequence length in advance.

Approach. Recover the interleaving rule by indexing the given output against the input, recognise it as a sliding window of the three most recent values, and implement it with a three-element ring buffer plus a short flush after end of file.

  1. Recover the rule from the example, rather than guessing it. Label the inputs $v_{0}\dots v_{5}$ and rewrite the printed coded sequence as source indices: $$0,\;1,0,\;2,1,0,\;3,2,1,\;4,3,2,\;5,4,3,\;5,4,\;5$$ The structure is now unmistakable. The output is a concatenation of groups; the group at step $k$ lists $v_{k},\,v_{k-1},\,v_{k-2}$, and any index outside $0 \le i < n$ is simply skipped. $$\boxed{\text{group } k = \big(v_{k},\,v_{k-1},\,v_{k-2}\big), \quad k = 0,1,\dots,n+1, \text{ in-range indices only}}$$
  2. Check the rule reproduces the printed output and the counts. The groups run 1, 2, 3, 3, 3, 3, 2, 1 in size — ramping up while the window fills and down while it drains — for a total of $$1+2+3+3+3+3+2+1 = 18 = 3 \times 6$$ so every value is written exactly three times, as replication demands. Applying the rule to 17 15 91 68 52 84 regenerates the question's coded sequence character for character.
  3. Confirm the rule achieves what interleaving is for. The three copies of a value are never adjacent: in the coded stream, 91 sits at positions 3, 7 and 11 and 68 at 6, 10 and 14 — four slots apart — while values near the two ends are still at least two apart. A localised scratch that destroys three consecutive stored numbers can therefore take at most one copy of any value, which is precisely the protection the plain triplicated form fails to give.
  4. Choose the data structure the rule implies. Group $k$ needs only $v_{k}$, $v_{k-1}$ and $v_{k-2}$, so at no point are more than three values required. A three-element array indexed by $\texttt{cnt} \bmod 3$ — a ring buffer — holds exactly those, overwriting each value only once it can never be needed again. Space is therefore $O(1)$, independent of the sequence length, which answers the question's demand for space efficiency and its warning that the length is unknown until end of file.
  5. Handle the tail. When the input ends, the window still holds the last values and two shorter groups remain to be emitted — the $k = n$ and $k = n+1$ groups, whose leading indices have run past the data. The guard idx < cnt drops those, and cnt - idx <= COPIES keeps the emission to values still resident in the buffer.
    #include <stdio.h>
    
    #define COPIES 3
    
    int main(void)
    {
        char inName[FILENAME_MAX], outName[FILENAME_MAX];
        FILE *in, *out;
        int buf[COPIES];        /* the ONLY storage: three values, whatever n is */
        long cnt = 0;           /* how many values have been read so far */
        long k, idx;
        int v, j;
    
        if (scanf("%s %s", inName, outName) != 2) {
            fprintf(stderr, "expected an input and an output file name\n");
            return 2;
        }
        in  = fopen(inName,  "r");
        out = fopen(outName, "w");
        if (in == NULL || out == NULL) {
            perror("fopen");
            if (in)  fclose(in);
            if (out) fclose(out);
            return 2;
        }
    
        /* One value at a time. After reading v_k, emit the diagonal that ends at
           it: v_k, v_(k-1), v_(k-2), skipping indices that do not exist yet. */
        while (fscanf(in, "%d", &v) == 1) {
            buf[cnt % COPIES] = v;
            cnt++;
            for (j = 0; j < COPIES; j++) {
                idx = cnt - 1 - j;
                if (idx >= 0)
                    fprintf(out, "%d ", (int)buf[idx % COPIES]);
            }
        }
    
        /* Flush: two further diagonals whose leading index has run past the end of
           the data. The (cnt - idx <= COPIES) guard keeps us to values that are
           still resident in the ring buffer. */
        for (k = cnt; k <= cnt + COPIES - 2; k++)
            for (j = 0; j < COPIES; j++) {
                idx = k - j;
                if (idx >= 0 && idx < cnt && cnt - idx <= COPIES)
                    fprintf(out, "%d ", (int)buf[idx % COPIES]);
            }
    
        fprintf(out, "\n");
        fclose(in);
        fclose(out);
        return 0;
    }
  6. Confirm the cost. Each input value is read once and written three times, so the program runs in $\Theta(n)$ time — optimal, since the output alone has length $3n$ — using three integers of working storage regardless of $n$.
input17v015v191v268v352v484v5the 3-slot window holds only the three most recent values, and slides one step per readoutput17k=015 17k=191 15 17k=268 91 15k=352 68 91k=484 52 68k=584 52k=684k=7group sizes ramp 1, 2, 3, …, 3, 2, 1 — total is exactly 3 × (number of input values)
How the coded stream is produced. Only three values are ever held. On reading vₕ the program emits the descending run ending at it; after end of file two shorter runs are flushed. Group sizes 1, 2, 3, 3, 3, 3, 2, 1 total 18 — exactly three copies of each of the six inputs.
Question 8 — results
ItemResult
Interleaving rulegroup $k$ emits $v_{k}, v_{k-1}, v_{k-2}$, in-range indices only, for $k = 0 \dots n+1$
Input17 15 91 68 52 84
Coded output17 15 17 91 15 17 68 91 15 52 68 91 84 52 68 84 52 84
Output length18 values, that is $3n$
Group sizes1, 2, 3, 3, 3, 3, 2, 1
Copies of each value3, never in adjacent slots (4 apart in the steady state)
Working storage3 integers — $O(1)$, independent of the sequence length
Time$\Theta(n)$, which is optimal for an output of size $3n$