Skip to content

Commit

Permalink
initial attempt at pattern-de-duplication based on subsequence detection
Browse files Browse the repository at this point in the history
  • Loading branch information
miles-grant-ibigroup committed Oct 12, 2023
1 parent ea10904 commit 4244d41
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 2 deletions.
19 changes: 18 additions & 1 deletion __tests__/util/state.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
/* globals describe, expect, it */

import '../test-utils/mock-window-url'
import { queryIsValid } from '../../lib/util/state'
import { isValidSubsequence, queryIsValid } from '../../lib/util/state'

describe('util > state', () => {
describe('isValidSubsequence', () => {
it('should handle edge cases correctly', () => {
expect(isValidSubsequence([0], [0])).toBe(true)
expect(isValidSubsequence([0], [1])).toBe(false)
expect(isValidSubsequence([], [])).toBe(true)
expect(isValidSubsequence([], [9])).toBe(false)
expect(isValidSubsequence([9], [])).toBe(true)
expect(isValidSubsequence([9], [9, 9])).toBe(false)
expect(isValidSubsequence([9, 9, 9], [9, 9])).toBe(true)
})
it('should handle normal cases correctly', () => {
expect(isValidSubsequence([1, 2, 3, 4, 5], [5, 6, 3])).toBe(false)
expect(isValidSubsequence([1, 2, 3, 4, 5], [2, 3, 4])).toBe(true)
expect(isValidSubsequence([1, 2, 4, 4, 3], [2, 3, 4])).toBe(false)
expect(isValidSubsequence([1, 2, 3, 4, 5], [1, 3, 4])).toBe(false)
})
})
describe('queryIsValid', () => {
const fakeFromLocation = {
lat: 12,
Expand Down
29 changes: 28 additions & 1 deletion lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { generateModeSettingValues } from '../util/api'
import {
getActiveItineraries,
getActiveItinerary,
isValidSubsequence,
queryIsValid
} from '../util/state'
import { ItineraryView } from '../util/ui'
Expand Down Expand Up @@ -651,7 +652,33 @@ export const findRoute = (params) =>

const newRoute = clone(route)
const routePatterns = {}
newRoute.patterns.forEach((pattern) => {

// Sort patterns by length to make algorithm below more efficient
const patternsSortedByLength = newRoute.patterns.sort(
(a, b) => a.stops.length - b.stops.length
)

// Remove all patterns that are subsets of larger patterns
const filteredPatterns = patternsSortedByLength
// Start with the largest for performance
.reverse()
.filter((pattern) => {
// Compare to all other patterns TODO: make this beat O(n^2)
return !patternsSortedByLength.find((p) => {
// Don't compare against ourself
if (p.id === pattern.id) return false

// If our pattern is longer, it's not a subset
if (p.stops.length < pattern.stops.length) return false

return isValidSubsequence(
p.stops.map((s) => s.id),
pattern.stops.map((s) => s.id)
)
})
})

filteredPatterns.forEach((pattern) => {
const patternStops = pattern.stops.map((stop) => {
const color =
stop.routes?.length > 0 &&
Expand Down
21 changes: 21 additions & 0 deletions lib/util/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,24 @@ export function getOperatorAndRoute(routeObject, transitOperators, intl) {
)
)
}

/**
* Helper method returns true if an array is a subsequence of another.
*
* More efficient than comparing strings as we don't need to look at the entire
* array.
*/
export function isValidSubsequence(array, sequence) {
// Find starting point
let i = 0
let j = 0
while (array[i] !== sequence[j] && i < array.length) {
i = i + 1
}
// We've found the starting point, now we test to see if the rest of the sequence is matched
while (array[i] === sequence[j] && i < array.length && j < sequence.length) {
i = i + 1
j = j + 1
}
return j === sequence.length
}

0 comments on commit 4244d41

Please sign in to comment.