Skip to content

Commit

Permalink
Added OOP version with performance comparison.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nellak2017 committed Apr 1, 2024
1 parent b020a61 commit 9e4c16e
Show file tree
Hide file tree
Showing 6 changed files with 777 additions and 3 deletions.
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const config = {
clearMocks: true,
collectCoverage: true,
}

module.exports = config
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dev": "react-scripts start",
"build": "react-scripts build",
"test": "jest",
"failing-test": "jest --onlyFailures",
"eject": "react-scripts eject"
},
"dependencies": {
Expand Down
27 changes: 26 additions & 1 deletion src/components/pages/App.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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 (
<div className="flex justify-center items-center h-screen bg-blue-100 overflow-y-auto p-16">
<div className="flex flex-col space-y-4">
Expand All @@ -29,7 +54,7 @@ function App() {
<div className='flex items-center justify-center'>
<button
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
onClick={handleKWIC}
onClick={handleKWICPerf}
>
Compute
</button>
Expand Down
282 changes: 282 additions & 0 deletions src/utils/OO_KWIC.js
Original file line number Diff line number Diff line change
@@ -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
*/
Loading

0 comments on commit 9e4c16e

Please sign in to comment.