Skip to content

Instantly share code, notes, and snippets.

@renoirb
Last active February 7, 2024 04:55
Show Gist options
  • Save renoirb/a73b7309a935c3d5da86a95afed7651b to your computer and use it in GitHub Desktop.
Save renoirb/a73b7309a935c3d5da86a95afed7651b to your computer and use it in GitHub Desktop.
Université de Sherbrooke INF721 Théorie de Mesure

Ce qui suit a été du code qui a été écrit durant un examen et j'illustre comment j'ai pu utiliser ce qui est fourni.

Notez que j'inscrit ici (ce Gist sur GitHub aujourd'hui en Février 2024) ce qui était dans mes fichiers que je n'ai pas touché depuis plus de 2 ans (Mai 2021!).

Durant examen chapitre sur la Théorie de la Mesure

La question citait des points de données en liste que j'ai assigné dans DATA_KLOC, DATA_MOD, DATA_FD directement de l'exercice.

Exemple de listes de références ou l'on peux obtenir la *moyenne(, la médiane, et ce qui est entre deux membres de listes

  • calculateAverage(...)
  • getMedian(...)
  • getRangeBetween(<N1>, <N2>)
import { getRangeBetween, calculateAverage, getMedian } from './utils.mjs'

// Listes de nombres mentionnés en exercice pour lesquels nous voulons extraire la médiane, ...
const DATA_KLOC = [
  10, 23, 26, 31, 31, 40, 47, 52, 54, 67, 70, 75, 83, 83, 100, 110, 200,
]
const DATA_MOD = [
  15, 43, 61, 10, 43, 57, 58, 65, 50, 60, 50, 96, 51, 61, 32, 78, 48,
]
const DATA_FD = [
  36, 22, 15, 33, 15, 13, 22, 16, 15, 18, 10, 34, 16, 18, 12, 20, 21,
]

calculateAverage(DATA_MOD) //?
getMedian(DATA_FD) //?

getRangeBetween(126, 129) //?

Analyzing A Systematic Review on Software Cost Estimation in Agile Software Development

Ce que j'ai utilisé pour calculer des références de papiers et savoir lesquels sont communs entre chacun.

J'analysais un papier sur les techniques d'evaluation de l'effort.

Je ne me rappelle plus des techniques (ça fait 2.5 ans que j'ai mis de coté ces fichiers), mais c'était des techniques de régression linéaire (LR, Linear Regression) qui étaient populaires.

Le papier listait les références (chaque nombre étant un numéro d'article en référence) et je voulais savoir quel papier était en commun.

Ces calculs étaient alors que j'analysais le papier (DOI: 10.25103/jestr.104.08):

S. Bilgaiyan, S. Sagnika, S. Mishra and D. Madhabananda, "A Systematic Review on Software Cost
Estimation in Agile Software Development," Journal of Engineering Science and Technology Review,
vol. 10, no. 4, pp. 51-64, 08 2017.

Code:

const table3Papers = new Map()

// Table 3. Accuracy parameters used by different estimation mechanisms

// NN: Neural Network, une méthode d'estimation qui utilise le machine learning 
// Papers:                [109-123]
// Accuracy parameters:   MRE, MMRE, PRED(X), MAPE, MdMRE, R2, MSE, MMER
table3Papers.set('NN', [...getRangeBetween(109, 123)].sort()) // 15
// EJ: Expert Judgement, une méthode d'estimation (la façon que l'on fait depuis toujours lorsqu'on a des experts!)
// Papiers:               [124, 126-129]
// Accuracy parameters:   MRE, MdMRE
table3Papers.set('EJ', [...getRangeBetween(126, 129), 124].sort()) // 5
// PP/Da: Planning Poker / Disaggregation des mécanismes d'estimation 
// Papiers:               [124, 130-132]
// Accuracy parameters:   MRE, BRE
table3Papers.set('PPDA', [...getRangeBetween(130, 132), 124].sort()) // 4

// UCP: Use Case Point
// Papiers:               [111, 125, 133, 134]
// Accuracy parameters:   MRE, MMRE, MMER, PRED(X), R2, MSE
table3Papers.set('UCP', [111, 125, 133, 134]) // 4

// MUCP: Modified Use Case Point
// Papiers: [133, 134, 138, 139]
// Methods: MRE, MMRE, MdMRE, PRED(X)
table3Papers.set('MUCP', [133, 134, 138, 139]) // 4

// LR: Linear Regression
// Papiers:             [135, 136]
// Accuracy parameters: MRE, MMRE, MMER, PRED(X), MdMRE
table3Papers.set('LR', [135, 136]) // 2

// Wd: Wideband Delphi
// Papiers:             [124, 132]
// Accuracy parameters: MRE
table3Papers.set('WD', [124, 132]) // 2

// BUTD: Buttom UP, Top Down
// Papiers:             [129, 137]
// Accuracy parameters: MRE
table3Papers.set('BUTD', [129, 137]) // 2

const all = []
for (const [k, c] of table3Papers.entries()) {
  all.push(...c)
}

const unique = new Set(all)

const shared = new Map()
for (const [k, c] of table3Papers.entries()) {
  shared.set(k, c)
}
/**!
* Utilitaires de calculs pour la théorie de mesure
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
/**
* Configuration for Object.defineProperty accessiblity to be read only.
*
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
*/
export const DEFINE_PROPERTY_DESCRIPTOR_DEFAULT = {
writable: false,
configurable: false,
enumerable: true,
}
export const fromMapToArray = (input) => {
if (Reflect.has(input, 'clear')) {
return [...input]
}
const message = `Unexpected error, we expected a Map`
throw new TypeError(message)
}
/**!
* Utilitaires de calculs pour la théorie de mesure
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
export const REGEX_CAPTURE_FLOATING_NUMBER = /^(?<left>\d+)\.(?<right>\d+)$/
export const isFloat = (value) => {
if (
typeof value === 'number' &&
!Number.isNaN(value) &&
!Number.isInteger(value)
) {
return true
}
return false
}
export const isNumberNotFloat = (entry) => {
return isFloat(entry) === false
}
export const assertIsNumberNotFloat = (entry) => {
if (!isNumberNotFloat(entry)) {
const message = `Expected an integer and we got "${entry}"`
throw new TypeError(message)
}
return entry
}
export const assertIsNumberNotZero = (entry) => {
assertIsNumberNotFloat(entry)
if (entry < 1) {
const message = `Expected a non-zero number`
throw new TypeError(message)
}
}
export const takeNumberAsFloating = (entry) => {
const isFloating = isFloat(entry)
if (!isFloating) {
// Cannot do anything more, because 1.0 === 1
// at least making doubly sure it is a number
assertIsNumberNotFloat(entry)
}
return entry
}
export const assertIsFloating = (entry) => {
/**
* Cannot really do much, even (entry).toPrecision(2) will not guarantee
*
* Anything between the following is pointless
*/
/* ================== BEGIN ================== *
const stringified = (entry).toPrecision(10)
const outcome = stringified.indexOf('.') // .match(REGEX_CAPTURE_FLOATING_NUMBER) ?? [entry, 0, 0]
console.log(`assertIsFloating(${entry})`, { entry, stringified, outcome })
return outcome !== -1
* ================== END ================== */
takeNumberAsFloating(entry)
}
export class NumberFloatingItem {
#value = 0
#left = []
#right = []
#sign = ''
toString() {
const left = this.#left.join('')
const right = this.#right.join('')
const digits = [left, right].join('.')
return `${this.#sign}${digits}`
}
get decimalSize() {
return this.#right.length
}
constructor(value) {
const valueLen = value.toPrecision().length
console.log(`NumberFloatingItem(${value})`, { valueLen })
const { dot = 0, coefficients, sign } = splitNumber(value)
const left = []
const right = []
if (dot !== -1) {
left.push(...coefficients.slice(0, dot))
right.push(...coefficients.slice(dot))
} else {
// Because zeroes are removed in splitNumber, we need to re-parse and ensure they're there.
const coefficients2 = String(value)
.split('')
.map((i) => +i)
left.push(...coefficients2)
right.push(0)
}
if (left.length + right.length !== coefficients.length) {
const message = `Something went wrong, we could not parse the value: "${value}", we should have ${left.length} digits on the left side of the dot, and ${right.length} on the right.`
throw new Error(message)
}
this.#value = value
this.#left = left
this.#right = right
this.#sign = sign
}
}
/**
* Split a number into sign, coefficients, and exponent
* @param {number | string} value
* @return {SplitValue}
* Returns an object containing sign, coefficients, and exponent
*
* Thanks: https://github.com/josdejong/mathjs/blob/69fed3f1/src/utils/number.js
*/
export const splitNumber = (value) => {
// parse the input value
const match = String(value)
.toLowerCase()
.match(/^(-?)(\d+\.?\d*)(e([+-]?\d+))?$/)
if (!match) {
throw new SyntaxError('Invalid number ' + value)
}
const sign = match[1]
const digits = match[2]
let exponent = parseFloat(match[4] || '0')
const dot = digits.indexOf('.')
exponent += dot !== -1 ? dot - 1 : digits.length - 1
const coefficients = digits
.replace('.', '') // remove the dot (must be removed before removing leading zeros)
.replace(/^0*/, function (zeros) {
// remove leading zeros, add their count to the exponent
exponent -= zeros.length
return ''
})
.replace(/0*$/, '') // remove trailing zeros
.split('')
.map(function (d) {
return parseInt(d)
})
if (coefficients.length === 0) {
coefficients.push(0)
exponent++
}
return { sign, coefficients, exponent, dot }
}
export const isNumberFloating = (entry) => {
const [integerSideStr, decimalSideStr] = String(entry).split('.')
const integerSide = Number.parseInt(integerSideStr)
const decimalSide = Number.parseInt(decimalSideStr)
console.log(`isNumberFloating(${entry})`, { integerSide, decimalSide })
if (isNumberNotFloat(entry) && Number.isNaN(integerSide) === false) {
return true
}
return false
}
export const assertIsNumberCollection = (entries) => {
if (entries.length < 1) {
const message = `Unexpected error, we expected a non zero length list of numbers`
throw new TypeError(message)
}
const [firstInEntries] = entries
const shouldAllBeFloating = isNumberFloating(firstInEntries)
? true
: isNumberNotFloat(firstInEntries) === false
const matches = entries.filter((e) => {
const iterCheck = isNumberFloating(e)
let outcome = false
let outcome2 = iterCheck !== shouldAllBeFloating || !isNumberNotFloat(e)
if (iterCheck !== shouldAllBeFloating) {
outcome = true
} else if (!isNumberNotFloat(e)) {
outcome = true
}
console.log('assertIsNumberCollection 1 entries.filter', {
e,
iterCheck,
outcome,
outcome2,
})
return outcome
})
console.log('assertIsNumberCollection 2', { matches })
if (matches.length !== 0) {
const listOf = `list of ${
shouldAllBeFloating ? 'decimal' : 'integer'
} (e.g. '${firstInEntries}')`
const [firstWrong] = matches
const andAlso =
typeof firstWrong !== 'number'
? ` which was expected to be a number, and we got type ${typeof firstWrong}`
: ''
const message = `Unexpected error, we expected a ${listOf} and we got '${firstWrong}'${andAlso}`
throw new TypeError(message)
}
return entries
}
/**!
* Utilisation des utilitaires de calculs
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
import { RayleighCurvePredictionModel } from './utils-defect-prediction-rayleigh.mjs'
import { DefectsCollection, DefectsItem } from './utils-defect-model.mjs'
// Nombre d'erreurs fournies dans l'exercice
const DATA_SET_LAIRD_P125_TUPLES = [
// Week number, Detected, estimated,
[1, 20],
[2, 41],
[3, 48],
[4, 52],
[5, 62],
[6, 59],
[7, 52],
[8, 44],
[9, 33],
]
{
let incr = 1
const K = 491
const tm = 5
const model = new RayleighCurvePredictionModel(K, tm)
model.F(incr) //?
model.f(incr++) //?
// 36.26
model.F(incr) //?
model.f(incr++) //?
// 49.21
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
model.F(incr) //?
model.f(incr++) //?
}
// Using arrays instead
{
const K = 491
const tm = 5
const model = new RayleighCurvePredictionModel(K, tm)
const collection = new DefectsCollection()
for (const [t, found] of DATA_SET_LAIRD_P125_TUPLES) {
const weekDefects = new DefectsItem(found)
// console.log('DATA_SET_LAIRD_P125_TUPLES', { t, found });
collection.setFor(t, weekDefects)
}
for (const i of [...collection]) {
console.log('wat', i)
}
}
/**!
* Utilitaires de calculs pour la théorie de mesure
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
import {
assertIsNumberNotFloat,
assertIsNumberNotZero,
} from './number-checking.mjs'
import {
DEFINE_PROPERTY_DESCRIPTOR_DEFAULT,
fromMapToArray,
} from './internals.mjs'
// Lorsque modèe statique Barry Boehm and Capers Jones
export const DRE = (e, d) => e / (e + d)
export const calculateSumOfFound = (listOfDefectForWeek) =>
Array.from(listOfDefectForWeek ?? [])
// .map((i) => { console.log('calculateSumOfFound', i); return i })
.filter((i) => Reflect.has(i, 'found'))
.map((i) => i.found)
.reduce((found, cumulation) => found + cumulation, 0)
export class AbstractCurvePredictionModel {
K = 0
tm = 1
constructor(_K, _tm) {
assertIsNumberNotZero(_K)
assertIsNumberNotZero(_tm)
Object.defineProperty(this, 'K', {
...DEFINE_PROPERTY_DESCRIPTOR_DEFAULT,
value: _K,
})
Object.defineProperty(this, 'tm', {
...DEFINE_PROPERTY_DESCRIPTOR_DEFAULT,
value: _tm,
})
}
/**
* f(t) — Lowercase f
*/
getDefectsPredictionFor(t) {
return super.getDefectsPredictionFor(t)
}
/**
* F(t) — Uppercase F
*/
getCumulativeDefectsPredictionFor(t) {
return super.getCumulativeDefectsPredictionFor(t)
}
f = this.getDefectsPredictionFor.bind(this)
F = this.getCumulativeDefectsPredictionFor.bind(this)
}
/**
* Item de tableau cumulatif.
*/
export class DefectsItem {
/**
* Nombre de défectuosités trouvées.
*/
found = 0
constructor(_found = 0) {
assertIsNumberNotFloat(_found)
this.found = _found
Object.defineProperty(this, 'found', {
...DEFINE_PROPERTY_DESCRIPTOR_DEFAULT,
value: _found,
})
}
cloneWithCumulated(_cumulated) {
assertIsNumberNotFloat(_cumulated)
const copy = new DefectsItem(this.found)
Object.defineProperty(copy, 'cumulated', {
...DEFINE_PROPERTY_DESCRIPTOR_DEFAULT,
value: _cumulated,
})
return copy
}
}
export class DefectForWeekPrediction {
prediction = 0
cumulativePrediction = 0
constructor(prediction = 0, cumulativePrediction = 0) {
this.prediction = prediction
this.cumulativePrediction = cumulativePrediction
}
}
/**
* Calculate at t, using predictive model of f(t) and F(t)
*/
export const calculateLowerAndCapitalEffUsingModelAtTee = (model, t) => {
if (Reflect.has(model, 'f') && Reflect.has(model, 'f')) {
try {
const prediction = _rayleighModel.f(t)
const predictionCumulative = _rayleighModel?.F(t)
return new DefectForWeekPrediction(prediction, predictionCumulative)
} catch {
// Do nothing it should throw anyway
}
}
const message = `Invalid argument, we expected first argument to be a Model that has two methods, lowercase f, and UPPERCASE F`
throw new TypeError(message)
}
export class DefectsCollection {
#items = new Map()
setFor(t, defectObj) {
assertIsNumberNotFloat(t)
if (this.#items.has(t)) {
const atTee = this.#items.get(t)
const message = `Unexpected error, cannot update at t = ${t} already set ${atTee}, updating data not yet implemented`
throw new Error(message)
}
const found = defectObj.found
const item = new DefectsItem(found)
this.#items.set(t, item)
}
*[Symbol.iterator]() {
const iterator = this.#items[Symbol.iterator]()
for (const [i, item] of iterator) {
const items = fromMapToArray(this.#items)
.filter(([index, item]) => (index <= i ? item : false))
.map(([_, item]) => item)
const cumulative = calculateSumOfFound(items)
const itemCopy = item.cloneWithCumulated(cumulative)
console.log('DefectsCollection iterator', {
i,
item,
itemCopy,
cumulative,
})
yield itemCopy
}
}
}
/**!
* Utilisation des utilitaires de calculs
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
import { assertIsNumberNotFloat } from './number-checking.mjs'
import { AbstractCurvePredictionModel } from './utils-defect-model.mjs'
/**
* Prédiction lorsque courbe suit le modèle Exponentiel.
*/
export class ExponentialCurvePredictionModel extends AbstractCurvePredictionModel {
/**
* f(t) of Exponential Model — Lowercase f
*
* TODO
*/
getDefectsPredictionFor(t) {
assertIsNumberNotFloat(t)
throw new Error('TODO getDefectsPredictionFor')
}
/**
* F(t) of Exponential Model — Uppercase F
*
* TODO
*/
getCumulativeDefectsPredictionFor(t) {
assertIsNumberNotFloat(t)
throw new Error('TODO getCumulativeDefectsPredictionFor')
}
}
/**!
* Utilisation des utilitaires de calculs
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
import { assertIsNumberNotFloat } from './number-checking.mjs'
import { AbstractCurvePredictionModel } from './utils-defect-model.mjs'
/**
* Prédiction lorsque courbe suit le modèle de Rayleigh.
*/
export class RayleighCurvePredictionModel extends AbstractCurvePredictionModel {
/**
* f(t) of Rayleigh Model — Lowercase f
*
* Excel:
* =C28*(A12/(C25*C25))*EXP(-(A12*A12)/(2*(C25*C25)))
*
* Excel formula, credit to Evariste Valery Bevo Wandji
*
* Where:
* - C28 was for the cell where K was calculated
* - A12 was for the cell of the curent line's t index number ("peak", or t at max m)
* - C25 was for the cell where we got the t index where there were most issues
*
* Attempt to rewrite as ECMAScript (pseudo for now):
*
* f(t) = K*(t/(i*i))*EXP(-(t*t)/(2*(i*i)))
*
* Where:
* - i stands for the A12
*/
getDefectsPredictionFor(t) {
assertIsNumberNotFloat(t)
return (
this.K *
(t / Math.pow(this.tm, 2)) *
Math.exp(-(t * t) / (2 * Math.pow(this.tm, 2)))
)
}
/**
* F(t) of Rayleigh Model — Uppercase F
*
* Excel:
* =C28*(1-EXP(-(A12*A12)/(2*(C25*C25))))
*
* Excel formula, credit to Evariste Valery Bevo Wandji
*
* That is as if we did in algebra:
*
* F(4) = f(1) + f(2) + f(3) + f(4)
*/
getCumulativeDefectsPredictionFor(t) {
assertIsNumberNotFloat(t)
return this.K * (1 - Math.exp(-Math.pow(t, 2) / (2 * Math.pow(this.tm, 2))))
}
}
/**!
* Utilitaires de calculs pour la théorie de mesure
*
* Durant cours INF721 a l'Université de Sherbrooke durant session 2021.
*
* Renoir Boulanger <renoir.boulanger@usherbrooke.ca>
*/
import { assertIsNumberCollection } from './number-checking.mjs'
export const sortAscFn = (a, b) => {
if (typeof a !== 'number' && typeof b !== 'number') {
const message = `We expected two numbers`
throw new TypeError(message)
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
// a is less than b by some ordering criterion
if (a < b) {
return -1
}
// a is greater than b by the ordering criterion
if (a > b) {
return 1
}
// a must be equal to b
return a === b
}
export const calculateAverage = (entries) => {
assertIsNumberCollection(entries)
const sum = entries.reduce((previousValue, currentValue) => {
return previousValue + currentValue
}, 0)
const out = sum / entries.length
return out
}
export const getMedian = (entries) => {
assertIsNumberCollection(entries)
let out = 0
const sorted = entries.slice().sort(sortAscFn)
const midpoints = []
const middleground = sorted.length / 2
// const isEven = Number.isInteger(middleground)
// console.log('getMedian', { lenght: sorted.length, middleground, isEven })
if (Number.isInteger(middleground)) {
out = sorted[middleground]
} else {
const position = +String(middleground).split('.')[0]
midpoints.push(sorted[position])
midpoints.push(sorted[position + 1])
out
}
const median = midpoints.length == 1 ? midpoints[0] : midpoints.reduce()
return {
median: out,
midpoints,
entries: sorted,
}
}
export const getRangeBetween = (start, end) => {
assertIsNumberCollection([start, end])
if (end < start) {
const message = `Invalid range number, start ${start} must be smaller than ${end}`
throw new Error(message)
}
return Array(end - start + 1)
.fill()
.map((_, idx) => start + idx)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment