From 0224afbc424fa5c1446f98b3472a8c5dac582d99 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Thu, 12 Apr 2018 11:53:26 +0300 Subject: [PATCH] Add BubbleSort. --- README.md | 2 + src/algorithms/sorting/Sort.js | 34 ++++++++++++++ src/algorithms/sorting/SortTester.js | 37 +++++++++++++++ src/algorithms/sorting/__test__/Sort.test.js | 12 +++++ .../sorting/bubble-sort/BubbleSort.js | 23 ++++++++++ src/algorithms/sorting/bubble-sort/README.md | 8 ++++ .../bubble-sort/__test__/bubbleSort.test.js | 45 +++++++++++++++++++ 7 files changed, 161 insertions(+) create mode 100644 src/algorithms/sorting/Sort.js create mode 100644 src/algorithms/sorting/SortTester.js create mode 100644 src/algorithms/sorting/__test__/Sort.test.js create mode 100644 src/algorithms/sorting/bubble-sort/BubbleSort.js create mode 100644 src/algorithms/sorting/bubble-sort/README.md create mode 100644 src/algorithms/sorting/bubble-sort/__test__/bubbleSort.test.js diff --git a/README.md b/README.md index 688d1ff1..ceaa4d07 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ * Graph * [Depth-First Search (DFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/depth-first-search) * [Breadth-First Search (BFS)](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/breadth-first-search) +* Sorting + * [Bubble Sort](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/bubble-sort) ## Running Tests diff --git a/src/algorithms/sorting/Sort.js b/src/algorithms/sorting/Sort.js new file mode 100644 index 00000000..4d969acc --- /dev/null +++ b/src/algorithms/sorting/Sort.js @@ -0,0 +1,34 @@ +import Comparator from '../../utils/comparator/Comparator'; + +/** + * @typedef {Object} SorterCallbacks + * @property {function(a: *, b: *)} compareCallback - If provided then all elements comparisons + * will be done through this callback. + * @property {function(a: *)} visitingCallback - If provided it will be called each time the sorting + * function is visiting the next element. + */ + +export default class Sort { + constructor(rawCallbacks) { + this.callbacks = Sort.initSortingCallbacks(rawCallbacks); + this.comparator = new Comparator(this.callbacks.compareCallback); + } + + /** + * @param {SorterCallbacks} rawCallbacks + * @returns {SorterCallbacks} + */ + static initSortingCallbacks(rawCallbacks) { + const callbacks = rawCallbacks || {}; + const stubCallback = () => {}; + + callbacks.compareCallback = callbacks.compareCallback || undefined; + callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; + + return callbacks; + } + + sort() { + throw new Error('sort method must be implemented'); + } +} diff --git a/src/algorithms/sorting/SortTester.js b/src/algorithms/sorting/SortTester.js new file mode 100644 index 00000000..d7d10111 --- /dev/null +++ b/src/algorithms/sorting/SortTester.js @@ -0,0 +1,37 @@ +export const sortedArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; +export const notSortedArray = [10, 5, 30, -1, 0, 0, 1, 2, -3, 2]; +export const equalArray = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + +export class SortTester { + static testSort(SortingClass) { + const sorter = new SortingClass(); + + expect(sorter.sort([])).toEqual([]); + expect(sorter.sort([1])).toEqual([1]); + expect(sorter.sort([1, 2])).toEqual([1, 2]); + expect(sorter.sort([2, 1])).toEqual([1, 2]); + expect(sorter.sort([1, 1, 1])).toEqual([1, 1, 1]); + expect(sorter.sort(sortedArray)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + expect(sorter.sort(notSortedArray)).toEqual([-3, -1, 0, 0, 1, 2, 2, 5, 10, 30]); + expect(sorter.sort(equalArray)).toEqual([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + } + + static testSortWithCustomComparator(SortingClass) { + const callbacks = { + compareCallback: (a, b) => { + if (a.length === b.length) { + return 0; + } + + return a.length < b.length ? -1 : 1; + }, + }; + + const sorter = new SortingClass(callbacks); + + expect(sorter.sort([''])).toEqual(['']); + expect(sorter.sort(['a'])).toEqual(['a']); + expect(sorter.sort(['aa', 'a'])).toEqual(['a', 'aa']); + expect(sorter.sort(['bb', 'aa', 'c'])).toEqual(['c', 'bb', 'aa']); + } +} diff --git a/src/algorithms/sorting/__test__/Sort.test.js b/src/algorithms/sorting/__test__/Sort.test.js new file mode 100644 index 00000000..989f3e77 --- /dev/null +++ b/src/algorithms/sorting/__test__/Sort.test.js @@ -0,0 +1,12 @@ +import Sort from '../Sort'; + +describe('Sort', () => { + it('should throw an error when trying to call Sort.sort() method directly', () => { + function doForbiddenSort() { + const sorter = new Sort(); + sorter.sort(); + } + + expect(doForbiddenSort).toThrow(); + }); +}); diff --git a/src/algorithms/sorting/bubble-sort/BubbleSort.js b/src/algorithms/sorting/bubble-sort/BubbleSort.js new file mode 100644 index 00000000..58f7d134 --- /dev/null +++ b/src/algorithms/sorting/bubble-sort/BubbleSort.js @@ -0,0 +1,23 @@ +import Sort from '../Sort'; + +export default class BubbleSort extends Sort { + sort(initialArray) { + const array = initialArray; + + for (let i = 0; i < array.length; i += 1) { + for (let j = 0; j < array.length - 1; j += 1) { + // Call visiting callback. + this.callbacks.visitingCallback(array[j]); + + // Swap elements if they are in wrong order. + if (this.comparator.lessThen(array[j + 1], array[j])) { + const tmp = array[j + 1]; + array[j + 1] = array[j]; + array[j] = tmp; + } + } + } + + return array; + } +} diff --git a/src/algorithms/sorting/bubble-sort/README.md b/src/algorithms/sorting/bubble-sort/README.md new file mode 100644 index 00000000..3c004b7e --- /dev/null +++ b/src/algorithms/sorting/bubble-sort/README.md @@ -0,0 +1,8 @@ +# Bubble Sort + +Bubble sort, sometimes referred to as sinking sort, is a +simple sorting algorithm that repeatedly steps through +the list to be sorted, compares each pair of adjacent +items and swaps them if they are in the wrong order. +The pass through the list is repeated until no swaps +are needed, which indicates that the list is sorted. diff --git a/src/algorithms/sorting/bubble-sort/__test__/bubbleSort.test.js b/src/algorithms/sorting/bubble-sort/__test__/bubbleSort.test.js new file mode 100644 index 00000000..0c02694e --- /dev/null +++ b/src/algorithms/sorting/bubble-sort/__test__/bubbleSort.test.js @@ -0,0 +1,45 @@ +import BubbleSort from '../BubbleSort'; +import { equalArray, notSortedArray, sortedArray, SortTester } from '../../SortTester'; + +describe('bubbleSort', () => { + it('should sort array', () => { + SortTester.testSort(BubbleSort); + }); + + it('should sort array with custom comparator', () => { + SortTester.testSortWithCustomComparator(BubbleSort); + }); + + it('should visit sorted array element specified number of times', () => { + const visitingCallback = jest.fn(); + + const callbacks = { visitingCallback }; + const sorter = new BubbleSort(callbacks); + + sorter.sort(sortedArray); + + expect(visitingCallback).toHaveBeenCalledTimes(90); + }); + + it('should visit not-sorted array element specified number of times', () => { + const visitingCallback = jest.fn(); + + const callbacks = { visitingCallback }; + const sorter = new BubbleSort(callbacks); + + sorter.sort(notSortedArray); + + expect(visitingCallback).toHaveBeenCalledTimes(90); + }); + + it('should visit equal array element specified number of times', () => { + const visitingCallback = jest.fn(); + + const callbacks = { visitingCallback }; + const sorter = new BubbleSort(callbacks); + + sorter.sort(equalArray); + + expect(visitingCallback).toHaveBeenCalledTimes(90); + }); +});