diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..1770b6e --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +const config = { + clearMocks: true, + collectCoverage: true, +} + +module.exports = config \ No newline at end of file diff --git a/package.json b/package.json index 05234f0..4c02ea8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dev": "react-scripts start", "build": "react-scripts build", "test": "jest", + "failing-test": "jest --onlyFailures", "eject": "react-scripts eject" }, "dependencies": { diff --git a/src/components/pages/App.js b/src/components/pages/App.js index 1ed6c89..926c603 100644 --- a/src/components/pages/App.js +++ b/src/components/pages/App.js @@ -1,5 +1,6 @@ import React, { useState } from 'react' import { display, pipe, KWICv2 } from '../../utils/helpers' +import { KWICv3 } from '../../utils/OO_KWIC' import '../../styles/globals.css' function App() { @@ -11,6 +12,30 @@ function App() { const handleResetOutput = () => setOutputText('') const handleKWIC = () => setOutputText(pipe(display)(KWICv2(inputText.trim().split('\n').filter(line => line.trim() !== '')).result)) + const handleKWICOOP = () => { + const processedInput = inputText.trim().split('\n').filter(line => line.trim() !== '') + setOutputText(pipe(display)(KWICv3(processedInput))) + } + + const handleKWICPerf = () => { + const processedInput = inputText.trim().split('\n').filter(line => line.trim() !== '') + const startTimeFP = performance.now() + const FPPipesAndFilters = pipe(display)(KWICv2(processedInput).result) + const endTimeFP = performance.now() + + const startTimeOOP = performance.now() + const OOPPipesAndFilters = pipe(display)(KWICv3(processedInput)) + const endTimeOOP = performance.now() + + const FPPerf = endTimeFP - startTimeFP + const OOPPerf = endTimeOOP - startTimeOOP + + console.log(`FP Pipes and Filter performance: ${FPPerf.toFixed(4)} ms`) + console.log(`OOP Pipes and Filter performance: ${OOPPerf.toFixed(4)} ms`) + console.log(`${FPPerf < OOPPerf ? 'FP Pipes and filter was faster' : 'OOP Pipes and filter was faster'}`) + + setOutputText(pipe(display)(KWICv3(processedInput))) + } return (
@@ -29,7 +54,7 @@ function App() {
diff --git a/src/utils/OO_KWIC.js b/src/utils/OO_KWIC.js new file mode 100644 index 0000000..0e4d963 --- /dev/null +++ b/src/utils/OO_KWIC.js @@ -0,0 +1,282 @@ +// This will have the OO / shared data solution quarantined +import { NOISE_WORDS } from "./helpers" + +export class Input { + // constructor(lineStorage) + // lineStorage class is data + // read() method: read data lines from input medium + // store(?) method: store data lines by calling the setchar of Line Storage + constructor(lineStorage) { this.lineStorage = lineStorage } + + // read data lines from input medium + read(lines) { lines.forEach(lineText => this.lineStorage.setLine(lineText.trim().split(/\s+/))) } +} + +export class LineStorage { + /* +  Create, access, and possibly delete characters, + words, and lines +  Actual representations and processing + algorithms are hidden + */ + // unknown if has constructor + // Characters is data + // setChar(l,w,c,d): used by Input module. Causes cth char in the wth word of the lth line to be d + // getChar(l,w,c): returns int repr cth char in wth word of ith line, else blank if out of range + // word(l): returns # of words in line l + + // Notes: if setchar(l,w,c,d) then getchar(l,w,c) = d + + constructor(lines) { + if (!lines) { this.lines = [] } + else { this.lines = lines } + } + + setLine(lineText) { this.lines.push(lineText) } + + setChar(lineIndex, wordIndex, charIndex, char) { + if (this.lines[lineIndex] && this.lines[lineIndex][wordIndex] && this.lines[lineIndex][wordIndex][charIndex]) { + const wordArray = this.lines[lineIndex][wordIndex].split('') // Convert string to array of characters + wordArray[charIndex] = char // Modify the character at charIndex + this.lines[lineIndex][wordIndex] = wordArray.join('') // Join array back into a string + } + } + + getChar(lineIndex, wordIndex, charIndex) { + if (this.lines[lineIndex] && this.lines[lineIndex][wordIndex]) { + return this.lines[lineIndex][wordIndex][charIndex] || '' + } + return '' + } + + wordCount(lineIndex) { + return this.lines[lineIndex].length + } +} + +export class CircularShift { + /* +  Creates virtual lines of the circular shifts of the stored + lines +  Provides routines to access individual characters and + words in the shifted lines + */ + // Char and index is data + // setup(): get a title(s) using char and word of Circular Shift, used to construct circular shift + // cs-setchar(s,w,c,e): causes c-th char in the wth word of the sth circular shift to be e, used to construct circular shift + // cs-getchar(s,w,c): returns the cth char in the wth word in the sth circular shift, used by Alphabetizer to reconstruct the circular shifts of the lines + // cs-word(?): ?? Not defined, but example given + + /* + Examples given: + + cs-setchar(1,1,1, “P”) + cs-setchar(1,3,5, “e”) + cs-setchar(2,1,1, “a”) + cs-setchar(2,3,3, “p”) + cs-setchar(3,2,2, “i”) + cs-getchar(1,1,1) = “P” + cs-getchar(1,3,5) = “e” + cs-getchar(2,1,1) = “a” + cs-getchar(2,3,3) = “p” + cs-getchar(3,2,2) = “i” + cs-word(1) = 3 + */ + constructor(lineStorage) { + this.lineStorage = lineStorage + this.shifts = [] + } + + setup() { + for (let i = 0; i < this.lineStorage.lines.length; i++) { + const line = this.lineStorage.lines[i] + for (let j = 0; j < line.length; j++) { + const shift = [] + for (let k = 0; k < line.length; k++) { + const wordIndex = (j + k) % line.length + shift.push(line[wordIndex]) + } + this.shifts.push(shift) + } + } + } + + setChar(shiftIndex, wordIndex, charIndex, char) { + if (this.shifts[shiftIndex] && this.shifts[shiftIndex][wordIndex]) { + const wordArray = this.shifts[shiftIndex][wordIndex].split('') + wordArray[charIndex] = char + this.shifts[shiftIndex][wordIndex] = wordArray.join('') + } + } + + getChar(shiftIndex, wordIndex, charIndex) { + if (this.shifts[shiftIndex] && this.shifts[shiftIndex][wordIndex]) { + return this.shifts[shiftIndex][wordIndex][charIndex] || '' + } + return '' + } + + wordCount(shiftIndex) { + return this.shifts[shiftIndex].length + } +} + +export class Alphabetizer { + /* +  Creates alphabetized lines of the circular shifts + using cs-getchar and cs-word +  Provides routines to access shifted lines in + alphabetical order + */ + // Char and index is data + // alph(?) : use Circular_Shift.cs-getchar and Circular_Shift.cs-word to get shifted lines and create alphabetized lines + // i-th(?) : returns the index of the circular shift which comes i-th in the ordering + constructor(circularShift) { + this.circularShift = circularShift; + this.alphabetizedLines = []; + } + + customSort(a, b) { + const charPairs = [...Array.from({ length: Math.min(a.length, b.length) }, (_, i) => [a[i], b[i]])] + const index = charPairs.findIndex(([charA, charB]) => charA !== charB) + if (index !== -1) { + const [charA, charB] = charPairs[index] + // Check if both characters are letters + if (/[a-zA-Z]/.test(charA) && /[a-zA-Z]/.test(charB)) { + return charA.localeCompare(charB) + } else { + // If both characters are not letters, compare based on ASCII value + return charA.charCodeAt(0) - charB.charCodeAt(0) + } + } + // If characters are equal, or one is a prefix of the other, compare their lengths + return a.length - b.length + } + + alph() { + // Sort circular shifts alphabetically + this.alphabetizedLines = this.circularShift.slice().sort(this.customSort) + } + + // skipped ith index thing, it is redundant +} + +class Output { + /* + calls Alphabetizer.i-th to + produce 1st , 2nd, ..., KWIC index + */ + constructor(alphabetizer) { + this.alphabetizer = alphabetizer + } + + generateKWICIndex() { + for (let i = 0; i < this.alphabetizer.alphabetizedLines.length; i++) { + return this.alphabetizer.alphabetizedLines + } + } +} + +export class MasterControl { + /* +  Input +  Circular Shift +  Alphabetizer +  Output + */ + constructor(input, circularShift, alphabetizer, output) { + this.input = input + this.circularShift = circularShift + this.alphabetizer = alphabetizer + this.output = output + } + + // Orchestrates the flow of data and operations + orchestrate(lines) { + this.input.read(lines) + this.circularShift.setup() + this.alphabetizer.alph() + this.output.generateKWICIndex() + } +} + + +export const KWICv3 = inputLines => { + // Instantiate the necessary objects + const lineStorage = new LineStorage() + const input_ = new Input(lineStorage) + input_.read(inputLines) + + const circularShift = new CircularShift(lineStorage) + circularShift.setup() + const alphabetizer = new Alphabetizer(circularShift.shifts) + alphabetizer.alph() + + const sortedLines = alphabetizer.alphabetizedLines.map(innerList => innerList.join(' ')) + + // Manually filter noise words, I am sick of OOP over-engineering + const filterNoiseWords = (lines, noiseWords = NOISE_WORDS) => lines.filter(line => !noiseWords[line.trim().split(' ')[0].toLowerCase()]) + + const filteredOutput = filterNoiseWords(sortedLines) + + // Retrieve the KWIC index from the Output object + return filteredOutput +} + + +/* + +Non-functional requirements + +Modifiability + Changes in processing algorithms, e.g. + Line shifting + One at a time as it is read + All after they are read + On demand when the alphabetization requires +a new set of shifted lines + batch alphabetizer vs incremental alphabetizer + Changes in data representation, e.g. + Storing characters, words and lines in 1-D/2-D +array/linked list, compressed/uncompressed + Explicitly/implicitly (as pairs of index and offset) + Core storage/secondary storage +Not affect others +Not affect others +25 ++Non-Functional Requirements + Enhanceability + Enhancement to system function, e.g., + eliminate noise words (a, an, the, and, etc.) + The user deletes lines from the original or shifted lines + Performance + Space and time + Reusability + To what extent can the components serve as reusable +entities + better supported than Shared Data, as modules make +fewer assumptions about the others with which they +interact, e.g., + Circular_shift is not dependent on the data representation +in Input as in Shared Data + Alphabetizer can assume Circular_shift returns all lines in +full +Little changes +can be poorer than Shared Data, +due to duplication and reconstruction +26 ++Properties of Solution 3 +Module interfaces are abstract + Hide data representations + Could be array+indices, as before + Lines could be stored explicitly + Hide internal algorithm used to process that data + Require users to follow a protocol for correct use + Initialization + Error handling + Allows work to begin on modules before data +representations are designed + Could result in same executable code as shared +data + +*/ diff --git a/src/utils/OO_KWIC.test.js b/src/utils/OO_KWIC.test.js new file mode 100644 index 0000000..8f9d759 --- /dev/null +++ b/src/utils/OO_KWIC.test.js @@ -0,0 +1,457 @@ +import { + Input, + LineStorage, + CircularShift, + Alphabetizer, + KWICv3, +} from './OO_KWIC.js' + +// Define the Input class test suite +describe('Input class', () => { + // Define the LineStorage instance + let lineStorage + + // Initialize a new LineStorage instance before each test case + beforeEach(() => { + lineStorage = new LineStorage() + }) + + // Define the test cases + const testCases = [ + // Happy path + { + description: "Input with one line", + input: ["First Line"], + expectedOutput: { + lineStorageLines: [["First", "Line"]] + } + }, + { + description: "Input with multiple lines", + input: ["First Line", "Second Line this is"], + expectedOutput: { + lineStorageLines: [["First", "Line"], ["Second", "Line", "this", "is"]] + } + }, + + // Bad path + { + description: "Input with one empty line", + input: [""], + expectedOutput: { + lineStorageLines: [[""]] + } + }, + { + description: "Input with multiple empty lines", + input: ["", "", ""], + expectedOutput: { + lineStorageLines: [[""], [""], [""]] + } + }, + { + description: "Input with leading and trailing whitespace", + input: [" First Line ", "Second Line "], + expectedOutput: { + lineStorageLines: [["First", "Line"], ["Second", "Line"]] + } + }, + { + description: "Input with lines containing special characters", + input: ["!@#$%^&*()", "()-_=+{}[]|:'\"<>,.?/"], + expectedOutput: { + lineStorageLines: [["!@#$%^&*()"], ["()-_=+{}[]|:'\"<>,.?/"]] + } + }, + { + description: "Input with lines containing numbers", + input: ["12345", "9876", "0"], + expectedOutput: { + lineStorageLines: [["12345"], ["9876"], ["0"]] + } + }, + { + description: "Input with lines containing only whitespace", + input: [" ", " \t ", "\t\t"], + expectedOutput: { + lineStorageLines: [[""], [""], [""]] + } + }, + ] + + // Iterate over each test case + testCases.forEach((testCase, index) => { + // Define the test + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Create a new Input instance with the LineStorage instance + const input = new Input(lineStorage) + + // Execute the read method with the input data + input.read(testCase.input) + + // Verify the output by comparing the LineStorage lines with the expected output + expect(lineStorage.lines).toEqual(testCase.expectedOutput.lineStorageLines) + }) + }) +}) + +describe('LineStorage', () => { + // Define the LineStorage instance + let lineStorage + + // Initialize a new LineStorage instance before each test case + beforeEach(() => { + lineStorage = new LineStorage() + }) + + // Test the setLine method + describe('setLine method', () => { + // Define test cases for the setLine method + const setLineTestCases = [ + { + description: 'should add a new line to the storage', + lineText: 'Test', + expectedLines: ['Test'] + }, + { + description: 'should add a new line to the storage', + lineText: ['Test', 'another'], + expectedLines: [['Test', 'another']] + }, + ] + + setLineTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lineText, expectedLines } = testCase + + // Act + lineStorage.setLine(lineText) + + // Assert + expect(lineStorage.lines).toEqual(expectedLines) + }) + }) + }) + + // Test the setChar method + describe('setChar method', () => { + // Define test cases for the setChar method + const setCharTestCases = [ + { + description: 'should set the character at the specified position in the line', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + lineIndex: 0, + wordIndex: 1, + charIndex: 1, + char: 'b', + expectedLines: [['Pipe', 'abd', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']] + }, + // Add more test cases as needed + ] + setCharTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, lineIndex, wordIndex, charIndex, char, expectedLines } = testCase + + // Act + lines.forEach(line => lineStorage.setLine(line)) // ex: [firstline, secondline,...] + lineStorage.setChar(lineIndex, wordIndex, charIndex, char) + + // Assert + expect(lineStorage.lines).toEqual(expectedLines) + }) + }) + }) + + // Test the getChar method + describe('getChar method', () => { + // Define test cases for the getChar method + const getCharTestCases = [ + { + description: 'should return the character at the specified position in the line', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + lineIndex: 0, + wordIndex: 1, + charIndex: 1, + expectedChar: 'n' + }, + // Add more test cases as needed + ] + + getCharTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, lineIndex, wordIndex, charIndex, expectedChar } = testCase + + // Act + lines.forEach(line => lineStorage.setLine(line)) // ex: [firstline, secondline,...] + const result = lineStorage.getChar(lineIndex, wordIndex, charIndex) + + // Assert + expect(result).toBe(expectedChar) + }) + }) + }) + + // Test the wordCount method + describe('wordCount method', () => { + // Define test cases for the wordCount method + const wordCountTestCases = [ + { + description: 'should return the number of words in the specified line', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + lineIndex: 0, + expectedWordCount: 3 + }, + // Add more test cases as needed + ] + + wordCountTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, lineIndex, expectedWordCount } = testCase + + // Act + lines.forEach(line => lineStorage.setLine(line)) // ex: [firstline, secondline,...] + const result = lineStorage.wordCount(lineIndex) + + // Assert + expect(result).toBe(expectedWordCount) + }) + }) + }) + +}) + +describe('CircularShift', () => { + // Test the setup method + describe('setup method', () => { + // Define test cases for the setup method + const setupTestCases = [ + { + description: 'should generate correct circular shifts for each line', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + expectedShifts: [ + ['Pipe', 'and', 'Filter'], ['and', 'Filter', 'Pipe'], ['Filter', 'Pipe', 'and'], + + ['Software', 'Architecture', 'in', 'Practice'], ['Architecture', 'in', 'Practice', 'Software'], + ['in', 'Practice', 'Software', 'Architecture'], ['Practice', 'Software', 'Architecture', 'in'] + ] + }, + ] + + setupTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, expectedShifts } = testCase + + const lineStorage = new LineStorage(lines) + const circularShift = new CircularShift(lineStorage) + + // Act: CircularShift setup has already been called in beforeEach + circularShift.setup() + + // Assert + expect(circularShift.shifts).toEqual(expectedShifts) + }) + }) + }) + + // Test the setChar method + describe('setChar method', () => { + // Define test cases for the setChar method + const setCharTestCases = [ + { + description: 'should set the character at the specified position in the circular shift', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + shiftIndex: 0, + wordIndex: 1, + charIndex: 1, + char: 'b', + expectedModifiedShifts: [ + ['Pipe', 'abd', 'Filter'], ['and', 'Filter', 'Pipe'], ['Filter', 'Pipe', 'and'], + ['Software', 'Architecture', 'in', 'Practice'], ['Architecture', 'in', 'Practice', 'Software'], + ['in', 'Practice', 'Software', 'Architecture'], ['Practice', 'Software', 'Architecture', 'in'] + ] + }, + // Add more test cases as needed + ] + + setCharTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, shiftIndex, wordIndex, charIndex, char, expectedModifiedShifts } = testCase + const lineStorage = new LineStorage(lines) + const circularShift = new CircularShift(lineStorage) + circularShift.setup() + + // Act + circularShift.setChar(shiftIndex, wordIndex, charIndex, char) + + // Assert + expect(circularShift.shifts).toEqual(expectedModifiedShifts) + }) + }) + }) + + // Test the getChar method + describe('getChar method', () => { + // Define test cases for the getChar method + const getCharTestCases = [ + { + description: 'should return the character at the specified position in the circular shift', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + shiftIndex: 0, + wordIndex: 1, + charIndex: 1, + expectedChar: 'n' + }, + // Add more test cases as needed + ] + + getCharTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, shiftIndex, wordIndex, charIndex, expectedChar } = testCase + const lineStorage = new LineStorage(lines) + const circularShift = new CircularShift(lineStorage) + circularShift.setup() + + // Act + const result = circularShift.getChar(shiftIndex, wordIndex, charIndex) + + // Assert + expect(result).toBe(expectedChar) + }) + }) + }) + + // Test the wordCount method + describe('wordCount method', () => { + // Define test cases for the wordCount method + const wordCountTestCases = [ + { + description: 'should return the number of words in the specified circular shift', + lines: [['Pipe', 'and', 'Filter'], ['Software', 'Architecture', 'in', 'Practice']], + shiftIndex: 0, + expectedWordCount: 3 + }, + // Add more test cases as needed + ] + + wordCountTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, shiftIndex, expectedWordCount } = testCase + const lineStorage = new LineStorage(lines) + const circularShift = new CircularShift(lineStorage) + circularShift.setup() + + // Act + const result = circularShift.wordCount(shiftIndex) + + // Assert + expect(result).toBe(expectedWordCount) + }) + }) + }) + +}) + +describe('Alphabetizer', () => { + // Test the alph method + describe('alph method', () => { + // Define test cases for the alph method + const alphTestCases = [ + { + description: 'should alphabetize circular shifts correctly', + lines: [ + ['Pipe', 'and', 'Filter'], + ['and', 'Filter', 'Pipe'], + ['Filter', 'Pipe', 'and'], + ['Software', 'Architecture', 'in', 'Practice'], + ['Architecture', 'in', 'Practice', 'Software'], + ['in', 'Practice', 'Software', 'Architecture'], + ['Practice', 'Software', 'Architecture', 'in'] + ], + expectedAlphabetizedLines: [ + ['and', 'Filter', 'Pipe'], + ['Architecture', 'in', 'Practice', 'Software'], + ['Filter', 'Pipe', 'and'], + ['in', 'Practice', 'Software', 'Architecture'], + ['Pipe', 'and', 'Filter'], + ['Practice', 'Software', 'Architecture', 'in'], + ['Software', 'Architecture', 'in', 'Practice'] + ] + }, + { + description: 'should alphabetize circular shifts correctly for standard simple input', + lines: [ + ['Z'], + ['z'], + ['Y'], + ['y'], + ['c'], + ['C'], + ], + expectedAlphabetizedLines: [ + ['c'], + ['C'], + ['y'], + ['Y'], + ['z'], + ['Z'], + ] + } + ] + + alphTestCases.forEach((testCase, index) => { + it(`Test Case ${index + 1}: ${testCase.description}`, () => { + // Arrange + const { lines, expectedAlphabetizedLines } = testCase + const alphabetizer = new Alphabetizer(lines) + + // Act: Call the alph method + alphabetizer.alph() + + // Assert: Check if the alphabetized lines match the expected result + expect(alphabetizer.alphabetizedLines).toEqual(expectedAlphabetizedLines) + }) + }) + }) + +}) + +describe('KWICV3', () => { + const testCases = [ + // Test cases for valid input + { + input: ['The Quick Brown Fox', 'second line'], + expected: [ + "Brown Fox The Quick", + "Fox The Quick Brown", + "line second", + "Quick Brown Fox The", + "second line", + ], + }, + { + input: ['Pipes and filters', 'Software Architecture and Design'], + expected: [ + "Architecture and Design Software", + "Design Software Architecture and", + "filters Pipes and", + "Pipes and filters", + "Software Architecture and Design", + ] + }, // Test case based on Professor's email + ] + testCases.forEach(testCase => { + it(`should return ${JSON.stringify(testCase.expected)} for input '${JSON.stringify(testCase.input)}'`, () => { + const input = testCase.input + const result = KWICv3(input) + expect(result).toEqual(testCase.expected) + }) + }) +}) \ No newline at end of file diff --git a/src/utils/helpers.js b/src/utils/helpers.js index abe70d5..2a4db0b 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -53,11 +53,14 @@ export const KWIC = lines => pipe( sortLines, // takes a list of lines, removes duplicate lines, then sorts them line-by-line and character-by-character, and returns a result )(lines) -// ---- KWIC Pipeline as per instructions (Version 2, A5) +// ---- KWIC Pipeline as per instructions (Version 2, A5) (Pipe and Filter with extra filterNoiseWords) export const KWICv2 = lines => pipe( processInput, // verifies input is correct and returns result convertLines, // converts lines to set and remove extra whitespaces and empty lines (removes duplicates, extra whitespaces, and empty lines) allCircularShiftsAllLines, // makes a list of list containing all the circular shifts for each line sortLines, // takes a list of lines, removes duplicate lines, then sorts them line-by-line and character-by-character, and returns a result filterNoiseWords, // No line prefix of (lower/upper case): “a”, “an”, “the”, “and”, “or”, “of”, “to”, “be”, “is”, “in”, “out”, “by”, “as”, “at”, “off” -)(lines) \ No newline at end of file +)(lines) + +// ---- KWIC Pipeline as per instructions (Version 3, A7) (Shared Data and OOP) +// TODO: Import from OO file \ No newline at end of file