From a24450a6290773ef4581209443e5c67123013a2f Mon Sep 17 00:00:00 2001
From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com>
Date: Wed, 11 Oct 2023 11:33:02 +0530
Subject: [PATCH 1/4] feat: add determinant algorithm (#1438)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: add determinant calculating algorithm

* test: add self-tests for determinant algorithm

* chore: add wikipedia info link

* fix: change initialization to zero

* fix: add error throw and general code improvements

* fix: add error try and catch

* fix: seperate the test loops of error cases

* clean up a bit

---------

Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com>
---
 Maths/Determinant.js           | 78 ++++++++++++++++++++++++++++++++++
 Maths/test/Determinant.test.js | 63 +++++++++++++++++++++++++++
 2 files changed, 141 insertions(+)
 create mode 100644 Maths/Determinant.js
 create mode 100644 Maths/test/Determinant.test.js

diff --git a/Maths/Determinant.js b/Maths/Determinant.js
new file mode 100644
index 0000000000..d218a6ee98
--- /dev/null
+++ b/Maths/Determinant.js
@@ -0,0 +1,78 @@
+/**
+ * Given a square matrix, find its determinant using Laplace Expansion.
+ * Time Complexity : O(n!)
+ *
+ * For more info: https://en.wikipedia.org/wiki/Determinant
+ *
+ * @param {number[[]]} matrix - Two dimensional array of integers.
+ * @returns {number} - An integer equal to the determinant.
+ *
+ * @example
+ * const squareMatrix = [
+ *                          [2,3,4,6],
+ *                          [5,8,9,0],
+ *                          [7,4,3,9],
+ *                          [4,0,2,1]
+ *                      ];
+ *
+ * const result = determinant(squareMatrix);
+ * // The function should return 858 as the resultant determinant.
+ */
+
+const subMatrix = (matrix, i, j) => {
+  let matrixSize = matrix[0].length
+  if (matrixSize === 1) {
+    return matrix[0][0]
+  }
+  let subMatrix = []
+  for (let x = 0; x < matrixSize; x++) {
+    if (x === i) {
+      continue
+    }
+    subMatrix.push([])
+    for (let y = 0; y < matrixSize; y++) {
+      if (y === j) {
+        continue
+      }
+      subMatrix[subMatrix.length - 1].push(matrix[x][y])
+    }
+  }
+  return subMatrix
+}
+
+const isMatrixSquare = (matrix) => {
+  let numRows = matrix.length
+  for (let i = 0; i < numRows; i++) {
+    if (numRows !== matrix[i].length) {
+      return false
+    }
+  }
+  return true
+}
+
+const determinant = (matrix) => {
+  if (
+    !Array.isArray(matrix) ||
+    matrix.length === 0 ||
+    !Array.isArray(matrix[0])
+  ) {
+    throw new Error('Input is not a valid 2D matrix.')
+  }
+  if (!isMatrixSquare(matrix)) {
+    throw new Error('Square matrix is required.')
+  }
+  let numCols = matrix[0].length
+  if (numCols === 1) {
+    return matrix[0][0]
+  }
+  let result = 0
+  let setIndex = 0
+  for (let i = 0; i < numCols; i++) {
+    result +=
+      Math.pow(-1, i) *
+      matrix[setIndex][i] *
+      determinant(subMatrix(matrix, setIndex, i))
+  }
+  return result
+}
+export { determinant }
diff --git a/Maths/test/Determinant.test.js b/Maths/test/Determinant.test.js
new file mode 100644
index 0000000000..df9d34df83
--- /dev/null
+++ b/Maths/test/Determinant.test.js
@@ -0,0 +1,63 @@
+import { expect } from 'vitest'
+import { determinant } from '../Determinant'
+describe('Determinant', () => {
+  test.each([
+    [
+      [
+        [8, 1, 6],
+        [1, 2, 3],
+        [4, 7, 5]
+      ],
+      -87
+    ],
+    [
+      [
+        [2, 1, 0, 2],
+        [1, 2, 4, 3],
+        [0, 4, 7, 5],
+        [4, 7, 9, 8]
+      ],
+      25
+    ],
+    [
+      [
+        [5, 9],
+        [3, 7]
+      ],
+      8
+    ],
+    [
+      [
+        [7, 5, 1, 4, 3],
+        [6, 8, 7, 9, 6],
+        [9, 8, 0, 4, 7],
+        [0, 3, 4, 7, 9],
+        [3, 6, 2, 8, 8]
+      ],
+      2476
+    ],
+    [[[23]], 23]
+  ])(
+    'Should return the determinant of the square matrix.',
+    (matrix, expected) => {
+      expect(determinant(matrix)).toEqual(expected)
+    }
+  )
+
+  test.each([
+    [
+      [
+        [1, 6],
+        [1, 2, 3],
+        [4, 7, 5]
+      ],
+      'Square matrix is required.'
+    ],
+    [[1, 3, 2, [5, 8, 6], 3], 'Input is not a valid 2D matrix.']
+  ])(
+    'Should return the error message.',
+    (matrix, expected) => {
+      expect(() => determinant(matrix)).toThrowError(expected)
+    }
+  )
+})

From 3823eded0a57884a177a6d7df837d927a9b108ba Mon Sep 17 00:00:00 2001
From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com>
Date: Wed, 11 Oct 2023 11:34:33 +0530
Subject: [PATCH 2/4] feat: add row echelon matrix algorithm (#1454)

* feat: add row echelon matrix algorithm

* test: add self-tests for row echelon algorithm

* fix: replace rounding with float tolerance

* chore: use correct style

* fix: use error tolerance and segregate testcases

* chore: add necessary explaining comments
---
 Maths/RowEchelon.js           | 150 ++++++++++++++++++++++++++++++++++
 Maths/test/RowEchelon.test.js |  89 ++++++++++++++++++++
 2 files changed, 239 insertions(+)
 create mode 100644 Maths/RowEchelon.js
 create mode 100644 Maths/test/RowEchelon.test.js

diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js
new file mode 100644
index 0000000000..c773bb80a9
--- /dev/null
+++ b/Maths/RowEchelon.js
@@ -0,0 +1,150 @@
+/**
+ * Given a two dimensional matrix, find its row echelon form.
+ *
+ * For more info: https://en.wikipedia.org/wiki/Row_echelon_form
+ *
+ * @param {number[[]]} matrix - Two dimensional array of rational numbers.
+ * @returns {number[[]]} - Two dimensional array of rational numbers (row echelon form).
+ *
+ * @example
+ * const matrix = [
+ *                          [2,3,4,5,7],
+ *                          [9,8,4,0,9],
+ *                          [5,7,4,3,9],
+ *                          [3,4,0,2,1]
+ *                ]
+ *
+ * const result = rowEchelon(matrix)
+ *
+ * // The function returns the corresponding row echelon form:
+ * // result:
+ * //             [
+ * //                       [1,  1.5,  2,        2.5,      3.5],
+ * //                       [0,  1,    2.54545,  4.09091,  4.09091],
+ * //                       [0,  0,    1,        1.57692,  1.36539],
+ * //                       [0,  0,    0,        1,        -0.25]
+ * //             ]
+ */
+
+// Set a tolerance value for floating-point comparisons
+const tolerance = 0.000001
+
+// Check if all the rows have same length of elements
+const isMatrixValid = (matrix) => {
+  let numRows = matrix.length
+  let numCols = matrix[0].length
+  for (let i = 0; i < numRows; i++) {
+    if (numCols !== matrix[i].length) {
+      return false
+    }
+  }
+
+  // Check for input other than a 2D matrix
+  if (
+    !Array.isArray(matrix) ||
+    matrix.length === 0 ||
+    !Array.isArray(matrix[0])
+  ) {
+    return false
+  }
+  return true
+}
+
+const checkNonZero = (currentRow, currentCol, matrix) => {
+  let numRows = matrix.length
+  for (let i = currentRow; i < numRows; i++) {
+    // Checks if the current element is not very near to zero.
+    if (!isTolerant(0, matrix[i][currentCol], tolerance)) {
+      return true
+    }
+  }
+  return false
+}
+
+const swapRows = (currentRow, withRow, matrix) => {
+  let numCols = matrix[0].length
+  let tempValue = 0
+  for (let j = 0; j < numCols; j++) {
+    tempValue = matrix[currentRow][j]
+    matrix[currentRow][j] = matrix[withRow][j]
+    matrix[withRow][j] = tempValue
+  }
+}
+
+// Select a pivot element in the current column to facilitate row operations.
+// Pivot element is the first non-zero element found from the current row
+// down to the last row.
+const selectPivot = (currentRow, currentCol, matrix) => {
+  let numRows = matrix.length
+  for (let i = currentRow; i < numRows; i++) {
+    if (matrix[i][currentCol] !== 0) {
+      swapRows(currentRow, i, matrix)
+      return
+    }
+  }
+}
+
+// Multiply each element of the given row with a factor.
+const scalarMultiplication = (currentRow, factor, matrix) => {
+  let numCols = matrix[0].length
+  for (let j = 0; j < numCols; j++) {
+    matrix[currentRow][j] *= factor
+  }
+}
+
+// Subtract one row from another row
+const subtractRow = (currentRow, fromRow, matrix) => {
+  let numCols = matrix[0].length
+  for (let j = 0; j < numCols; j++) {
+    matrix[fromRow][j] -= matrix[currentRow][j]
+  }
+}
+
+// Check if two numbers are equal within a given tolerance
+const isTolerant = (a, b, tolerance) => {
+  const absoluteDifference = Math.abs(a - b)
+  return absoluteDifference <= tolerance
+}
+
+const rowEchelon = (matrix) => {
+  // Check if the input matrix is valid; if not, throw an error.
+  if (!isMatrixValid(matrix)) {
+    throw new Error('Input is not a valid 2D matrix.')
+  }
+
+  let numRows = matrix.length
+  let numCols = matrix[0].length
+  let result = matrix
+
+  // Iterate through the rows (i) and columns (j) of the matrix.
+  for (let i = 0, j = 0; i < numRows && j < numCols; ) {
+    // If the current column has all zero elements below the current row,
+    // move to the next column.
+    if (!checkNonZero(i, j, result)) {
+      j++
+      continue
+    }
+
+    // Select a pivot element and normalize the current row.
+    selectPivot(i, j, result)
+    let factor = 1 / result[i][j]
+    scalarMultiplication(i, factor, result)
+
+    // Make elements below the pivot element zero by performing
+    // row operations on subsequent rows.
+    for (let x = i + 1; x < numRows; x++) {
+      factor = result[x][j]
+      if (isTolerant(0, factor, tolerance)) {
+        continue
+      }
+      scalarMultiplication(i, factor, result)
+      subtractRow(i, x, result)
+      factor = 1 / factor
+      scalarMultiplication(i, factor, result)
+    }
+    i++
+  }
+  return result
+}
+
+export { rowEchelon }
diff --git a/Maths/test/RowEchelon.test.js b/Maths/test/RowEchelon.test.js
new file mode 100644
index 0000000000..5575bc6d39
--- /dev/null
+++ b/Maths/test/RowEchelon.test.js
@@ -0,0 +1,89 @@
+import { rowEchelon } from '../RowEchelon'
+describe('Determinant', () => {
+  const tolerance = 0.000001
+  test.each([
+    [
+      [
+        [8, 1, 3, 5],
+        [4, 6, 8, 2],
+        [3, 5, 6, 8]
+      ],
+      [
+        [1, 0.125, 0.375, 0.625],
+        [0, 1, 1.18182, -0.09091],
+        [0, 0, 1, -11.0769]
+      ]
+    ],
+    [
+      [
+        [6, 8, 1, 3, 5],
+        [1, 4, 6, 8, 2],
+        [0, 3, 5, 6, 8],
+        [2, 5, 9, 7, 8],
+        [5, 5, 7, 0, 1]
+      ],
+      [
+        [1, 1.33333, 0.16667, 0.5, 0.83333],
+        [0, 1, 2.1875, 2.8125, 0.4375],
+        [0, 0, 1, 1.56, -4.28003],
+        [0, 0, 0, 1, -3.3595],
+        [0, 0, 0, 0, 1]
+      ]
+    ],
+    [
+      [
+        [1, 3, 5],
+        [6, 8, 2],
+        [5, 6, 8],
+        [7, 9, 9],
+        [5, 0, 6]
+      ],
+      [
+        [1, 3, 5],
+        [0, 1, 2.8],
+        [0, 0, 1],
+        [0, 0, 0],
+        [0, 0, 0]
+      ]
+    ],
+    [
+      [
+        [0, 7, 8, 1, 3, 5],
+        [0, 6, 4, 6, 8, 2],
+        [0, 7, 3, 5, 6, 8],
+        [6, 8, 1, 0, 0, 4],
+        [3, 3, 5, 7, 3, 1],
+        [1, 2, 1, 0, 9, 7],
+        [8, 8, 0, 2, 3, 1]
+      ],
+      [
+        [1, 1.33333, 0.16667, 0, 0, 0.66667],
+        [0, 1, 0.66667, 1, 1.33333, 0.33333],
+        [0, 0, 1, 1.2, 1.99999, -3.4],
+        [0, 0, 0, 1, 1.3, -1.4],
+        [0, 0, 0, 0, 1, -2.32854],
+        [0, 0, 0, 0, 0, 1],
+        [0, 0, 0, 0, 0, 0]
+      ]
+    ]
+  ])('Should return the matrix in row echelon form.', (matrix, expected) => {
+    for (let i = 0; i < matrix.length; i++) {
+      for (let j = 0; j < matrix[i].length; j++) {
+        expect(rowEchelon(matrix)[i][j]).toBeCloseTo(expected[i][j], tolerance)
+      }
+    }
+  })
+
+  test.each([
+    [
+      [
+        [8, 1, 3, 5],
+        [4, 6, 8, 2, 7],
+        [3, 5, 6, 8]
+      ],
+      'Input is not a valid 2D matrix.'
+    ]
+  ])('Should return the error message.', (matrix, expected) => {
+    expect(() => rowEchelon(matrix)).toThrowError(expected)
+  })
+})

From ca761d87b6f507560305c27ba7dd0f1168f6ded7 Mon Sep 17 00:00:00 2001
From: Nay Zaw Min Naing <nayzawminnaing@gmail.com>
Date: Wed, 11 Oct 2023 12:35:09 +0630
Subject: [PATCH 3/4] feat: add MergeTwoSortedLinkedLIsts algorithms (#1442)

* feat: add mergeTwoSortedLinkedLIsts algorithms

* remove class and unnecessary function

change the function params and return value from Node to LinkedList.
---
 .../Linked-List/MergeTwoSortedLinkedLists.js  | 45 +++++++++++++++++++
 .../test/MergeTwoSortedLinkedLists.test.js    | 39 ++++++++++++++++
 2 files changed, 84 insertions(+)
 create mode 100644 Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js
 create mode 100644 Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js

diff --git a/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js b/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js
new file mode 100644
index 0000000000..df11c0ac2c
--- /dev/null
+++ b/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js
@@ -0,0 +1,45 @@
+import { LinkedList } from './SinglyLinkedList.js'
+/**
+ * A LinkedList-based solution for merging two sorted linked lists into one sorted list.
+ *
+ * @param {LinkedList} list1 - The the first sorted linked list.
+ * @param {LinkedList} list2 - The second sorted linked list.
+ * @returns {LinkedList} - The merged sorted linked list.
+ *
+ * @example
+ * const list1 = new LinkedList([1,2,4]);
+ *
+ * const list2 = new LinkedList([1,3,4]);
+ *
+ * const result = mergeLinkedLists(list1, list2);
+ * // Returns the merged linked list representing 1 -> 1 -> 2 -> 3 -> 4 -> 4
+ */
+
+function mergeLinkedLists(list1, list2) {
+  const mergedList = new LinkedList()
+
+  let current1 = list1.headNode
+  let current2 = list2.headNode
+
+  while (current1 || current2) {
+    if (!current1) {
+      mergedList.addLast(current2.data)
+      current2 = current2.next
+    } else if (!current2) {
+      mergedList.addLast(current1.data)
+      current1 = current1.next
+    } else {
+      if (current1.data < current2.data) {
+        mergedList.addLast(current1.data)
+        current1 = current1.next
+      } else {
+        mergedList.addLast(current2.data)
+        current2 = current2.next
+      }
+    }
+  }
+
+  return mergedList
+}
+
+export { mergeLinkedLists }
diff --git a/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js b/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js
new file mode 100644
index 0000000000..bc5bea953e
--- /dev/null
+++ b/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js
@@ -0,0 +1,39 @@
+import { expect } from 'vitest'
+import { mergeLinkedLists } from '../MergeTwoSortedLinkedLists.js'
+import { LinkedList } from '../SinglyLinkedList.js'
+
+describe('MergeTwoSortedLinkedLists', () => {
+  it('Merges two sorted linked lists', () => {
+    const list1 = new LinkedList([1, 2, 4])
+
+    const list2 = new LinkedList([1, 3, 4])
+
+    const expectedResult = new LinkedList([1, 1, 2, 3, 4, 4])
+
+    const result = mergeLinkedLists(list1, list2)
+
+    expect(result).toEqual(expectedResult)
+  })
+
+  it('Merges two empty linked lists', () => {
+    const list1 = new LinkedList()
+    const list2 = new LinkedList()
+
+    const expectedResult = new LinkedList()
+
+    const result = mergeLinkedLists(list1, list2)
+
+    expect(result).toEqual(expectedResult)
+  })
+
+  it('Merges one empty linked list with a non-empty one', () => {
+    const list1 = new LinkedList()
+    const list2 = new LinkedList([1])
+
+    const expectedResult = new LinkedList([1])
+
+    const result = mergeLinkedLists(list1, list2)
+
+    expect(result).toEqual(expectedResult)
+  })
+})

From ce86248b1e1d747866d74b0fbf518c81de55f40d Mon Sep 17 00:00:00 2001
From: Piyush Katyal <109459034+piyushk77@users.noreply.github.com>
Date: Wed, 11 Oct 2023 11:45:47 +0530
Subject: [PATCH 4/4] feat: add RGB to HSL color format conversion algorithm
 (#1475)

* feat: add RGB to HSL color format conversion algorithm

* test: add self-tests for rgb to hsl conversion algorithm

* fix: change function code to concise format

* fix: use throw and segregate the test cases

* chore: clean up the test format

* chore: use correct styling
---
 Conversions/RgbHslConversion.js           | 85 +++++++++++++++++++++++
 Conversions/test/RgbHslConversion.test.js | 43 ++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 Conversions/RgbHslConversion.js
 create mode 100644 Conversions/test/RgbHslConversion.test.js

diff --git a/Conversions/RgbHslConversion.js b/Conversions/RgbHslConversion.js
new file mode 100644
index 0000000000..7e014f1318
--- /dev/null
+++ b/Conversions/RgbHslConversion.js
@@ -0,0 +1,85 @@
+/**
+ * Given a color in RGB format, convert it to HSL format.
+ *
+ * For more info: https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
+ *
+ * @param {number[]} colorRgb - One dimensional array of integers (RGB color format).
+ * @returns {number[]} - One dimensional array of integers (HSL color format).
+ *
+ * @example
+ * const colorRgb = [24, 98, 118]
+ *
+ * const result = rgbToHsl(colorRgb)
+ *
+ * // The function returns the corresponding color in HSL format:
+ * // result = [193, 66, 28]
+ */
+
+const checkRgbFormat = (colorRgb) => colorRgb.every((c) => c >= 0 && c <= 255)
+
+const rgbToHsl = (colorRgb) => {
+  if (!checkRgbFormat(colorRgb)) {
+    throw new Error('Input is not a valid RGB color.')
+  }
+
+  let colorHsl = colorRgb
+
+  let red = Math.round(colorRgb[0])
+  let green = Math.round(colorRgb[1])
+  let blue = Math.round(colorRgb[2])
+
+  const limit = 255
+
+  colorHsl[0] = red / limit
+  colorHsl[1] = green / limit
+  colorHsl[2] = blue / limit
+
+  let minValue = Math.min(...colorHsl)
+  let maxValue = Math.max(...colorHsl)
+
+  let channel = 0
+
+  if (maxValue === colorHsl[1]) {
+    channel = 1
+  } else if (maxValue === colorHsl[2]) {
+    channel = 2
+  }
+
+  let luminance = (minValue + maxValue) / 2
+
+  let saturation = 0
+
+  if (minValue !== maxValue) {
+    if (luminance <= 0.5) {
+      saturation = (maxValue - minValue) / (maxValue + minValue)
+    } else {
+      saturation = (maxValue - minValue) / (2 - maxValue - minValue)
+    }
+  }
+
+  let hue = 0
+
+  if (saturation !== 0) {
+    if (channel === 0) {
+      hue = (colorHsl[1] - colorHsl[2]) / (maxValue - minValue)
+    } else if (channel === 1) {
+      hue = 2 + (colorHsl[2] - colorHsl[0]) / (maxValue - minValue)
+    } else {
+      hue = 4 + (colorHsl[0] - colorHsl[1]) / (maxValue - minValue)
+    }
+  }
+
+  hue *= 60
+
+  if (hue < 0) {
+    hue += 360
+  }
+
+  colorHsl[0] = Math.round(hue)
+  colorHsl[1] = Math.round(saturation * 100)
+  colorHsl[2] = Math.round(luminance * 100)
+
+  return colorHsl
+}
+
+export { rgbToHsl }
diff --git a/Conversions/test/RgbHslConversion.test.js b/Conversions/test/RgbHslConversion.test.js
new file mode 100644
index 0000000000..5dec4835cd
--- /dev/null
+++ b/Conversions/test/RgbHslConversion.test.js
@@ -0,0 +1,43 @@
+import { rgbToHsl } from '../RgbHslConversion'
+describe('RgbHslConversion', () => {
+  test.each([
+    [
+      [215, 19, 180],
+      [311, 84, 46]
+    ],
+    [
+      [21, 190, 18],
+      [119, 83, 41]
+    ],
+    [
+      [80, 100, 160],
+      [225, 33, 47]
+    ],
+    [
+      [80, 1, 16],
+      [349, 98, 16]
+    ],
+    [
+      [8, 20, 0],
+      [96, 100, 4]
+    ],
+    [
+      [0, 0, 0],
+      [0, 0, 0]
+    ],
+    [
+      [255, 255, 255],
+      [0, 0, 100]
+    ]
+  ])('Should return the color in HSL format.', (colorRgb, expected) => {
+    expect(rgbToHsl(colorRgb)).toEqual(expected)
+  })
+
+  test.each([
+    [[256, 180, 9], 'Input is not a valid RGB color.'],
+    [[-90, 46, 8], 'Input is not a valid RGB color.'],
+    [[1, 39, 900], 'Input is not a valid RGB color.']
+  ])('Should return the error message.', (colorRgb, expected) => {
+    expect(() => rgbToHsl(colorRgb)).toThrowError(expected)
+  })
+})
