exercices/4e/4S11.js

import Exercice from '../Exercice.js'
import { context } from '../../modules/context.js'
import { listeQuestionsToContenu, randint, choice, prenom, tirerLesDes, listeDeNotes, joursParMois, unMoisDeTemperature, nomDuMois, stringNombre, texNombre, texteGras, lampeMessage, combinaisonListes, calcul } from '../../modules/outils.js'

import { setReponse } from '../../modules/gestionInteractif.js'
import { ajouteChampTexteMathLive } from '../../modules/interactif/questionMathLive.js'
export const titre = 'Déterminer des médianes'
export const interactifReady = true
export const interactifType = 'mathLive'
export const amcReady = true // pour définir que l'exercice est exportable à AMC
export const amcType = 'AMCNum'

export const dateDeModifImportante = '28/10/2021'

/**
 * Calculs de médianes dans des séries statistiques
* @author Sébastien Lozano forked de Jean-Claude Lhote
* Référence 4S11
* Date initiale 2021-01-12
* Ajout de l'alternance entre effectif total pair et impair le 18/08/2021 : Guilllaume Valmont
*/
export const uuid = '7c068'
export const ref = '4S11'
export default function DeterminerDesMedianes () {
  Exercice.call(this) // Héritage de la classe Exercice()
  this.nbQuestions = 1
  this.spacing = 1
  this.spacingCorr = 1.5
  this.nbCols = 1
  this.nbColsCorr = 1
  this.sup = 1
  this.listePackages = 'bclogo'

  this.nouvelleVersion = function () {
    this.listeQuestions = [] // Liste de questions
    this.listeCorrections = [] // Liste de questions corrigées
    this.autoCorrection = []

    this.sup = parseInt(this.sup)

    const listePairOuImpair = combinaisonListes(['pair', 'impair'], this.nbQuestions)

    for (let i = 0, temperatures, nombreTemperatures, nombreNotes, notes, nombreDes, nombreFaces, nombreTirages, indexValeur, tirages, texte, texteCorr, cpt = 0; i < this.nbQuestions && cpt < 50;) {
      const underbraceMediane = (nbVal) => {
        let sortie
        if (nbVal % 2 === 0) { // nb pair de valeurs
          sortie = `$\\underbrace{1^e\\hspace{0.25cm}2^e ... ${nbVal / 2 - 1}^e}_{${nbVal / 2 - 1}\\; valeurs} \\hspace{0.25cm} ${nbVal / 2}^e \\hspace{0.25cm} ${nbVal / 2 + 1}^e \\hspace{0.25cm} \\underbrace{${nbVal / 2 + 2}^e ... ${nbVal}^e}_{${nbVal / 2 - 1}\\; valeurs}$`
        } else { // nb impair de valeurs
          sortie = `$\\underbrace{1^e\\hspace{0.25cm}2^e ... ${(nbVal - 1) / 2}^e}_{${(nbVal - 1) / 2}\\; valeurs} \\hspace{0.25cm} ${(nbVal - 1) / 2 + 1}^e \\hspace{0.25cm} \\underbrace{${(nbVal - 1) / 2 + 2}^e ... ${nbVal}^e}_{${(nbVal - 1) / 2}\\; valeurs}$`
        }
        return sortie
      }
      const desTabEffCumul = (tirages, effCumulBool) => {
        let sortie
        if (!effCumulBool) {
          sortie = ''
          if (tirages.length > 12) {
            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs 1/2
            for (let j = 0; j <= Math.round(tirages.length / 2); j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = 0; j < Math.round(tirages.length / 2); j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = 0; j < Math.round(tirages.length / 2); j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline\\end{array}$<br><br>'

            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs 2/2
            for (let j = Math.round(tirages.length / 2); j <= tirages.length; j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = Math.round(tirages.length / 2); j < tirages.length; j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = Math.round(tirages.length / 2); j < tirages.length; j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline\\end{array}$'
          } else {
            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs en un seul morceau
            for (let j = 0; j <= tirages.length; j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = 0; j < tirages.length; j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = 0; j < tirages.length; j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline\\end{array}$'
          };
        } else {
          sortie = ''
          if (tirages.length > 12) {
            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs 1/2
            for (let j = 0; j <= Math.round(tirages.length / 2); j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = 0; j < Math.round(tirages.length / 2); j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = 0; j < Math.round(tirages.length / 2); j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions cumulées}'
            for (let j = 0; j < Math.round(tirages.length / 2); j++) {
              let cumul = 0
              for (let k = 0; k <= j; k++) {
                cumul += tirages[k][1]
              }
              sortie += '&' + cumul// tirages[j][1];
            }
            sortie += '\\\\\\hline\\end{array}$<br><br>'

            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs 2/2
            for (let j = Math.round(tirages.length / 2); j <= tirages.length; j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = Math.round(tirages.length / 2); j < tirages.length; j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = Math.round(tirages.length / 2); j < tirages.length; j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions cumulées}'
            for (let j = Math.round(tirages.length / 2); j < tirages.length; j++) {
              let cumul = 0
              for (let k = 0; k <= j; k++) {
                cumul += tirages[k][1]
              }
              sortie += '&' + cumul// tirages[j][1];
            }
            sortie += '\\\\\\hline\\end{array}$'
          } else {
            sortie += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // construction du tableau des effectifs en un seul morceau
            for (let j = 0; j <= tirages.length; j++) { sortie += '|c' }
            sortie += '}\\hline  \\text{Scores}'
            for (let j = 0; j < tirages.length; j++) { sortie += '&' + tirages[j][0] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions}'
            for (let j = 0; j < tirages.length; j++) { sortie += '&' + tirages[j][1] }
            sortie += '\\\\\\hline \\text{Nombre d\'apparitions cumulées}'
            for (let j = 0; j < tirages.length; j++) {
              let cumul = 0
              for (let k = 0; k <= j; k++) {
                cumul += tirages[k][1]
              }
              sortie += '&' + cumul// tirages[j][1];
            }
            sortie += '\\\\\\hline\\end{array}$'
          };
        };
        return sortie
      }
      let repInteractive
      if (this.sup === 1) { // ici on lance des dés
        const solidName = (nbCot) => {
          switch (nbCot) {
            case 4:
              return 'tétraèdre'
            case 6:
              return 'hexaèdre'
            case 8:
              return 'octaèdre'
            case 10:
              return 'decaèdre'
            default:
              return 'cas non prévu'
          };
        }
        nombreDes = randint(1, 2)
        nombreFaces = choice([4, 6, 8, 10])
        if (listePairOuImpair[i] === 'pair') {
          nombreTirages = choice([50, 100, 200, 500, 1000, 2000])
        } else {
          nombreTirages = choice([49, 99, 199, 299, 999, 1999])
        }
        tirages = tirerLesDes(nombreTirages, nombreFaces, nombreDes) // on récupère une série rangée dans l'ordre croissant avec les effectifs correspondants
        do { indexValeur = randint(0, tirages.length - 1) }
        while (tirages[indexValeur][1] === 0) // on choisit au hasard l'index d'une valeur dont l'effectif est différent de 0.
        if (nombreDes > 1) {
          texte = `On a réalisé $${nombreTirages}$ lancers de $${nombreDes}$ dés à $${nombreFaces}$ faces.<br>`
        } else {
          texte = `On a réalisé $${nombreTirages}$ lancers d'un dé à $${nombreFaces}$ faces.<br>`
        }
        texte += lampeMessage({
          titre: 'Vocabulaire',
          texte: `Le solide qui correspond à ce type de dé s'appelle ${texteGras(solidName(nombreFaces))}.`,
          couleur: 'nombres'
        }) + '<br>'
        texte += 'Les résultats sont inscrits dans le tableau ci-dessous :<br><br>'
        texte += desTabEffCumul(tirages, false) + '<br>'

        texteCorr = `On a réalisé $${nombreTirages}$ lancers en tout.<br>`
        if (nombreTirages % 2 === 0) {
          texteCorr += `Le nombre de lancers est pair, les scores sont rangés dans l'ordre croissant.<br>
                    Les deux valeurs centrales sont la $${nombreTirages / 2}^{e}$ et la $${nombreTirages / 2 + 1}^{e}$ valeur.<br>
                    En effet, ${underbraceMediane(nombreTirages)} <br>
                    Une médiane est donc un score compris entre le $${nombreTirages / 2}^{e}$ et le $${nombreTirages / 2 + 1}^{e}$ score.<br>
                    On peut ajouter une ligne avec les effectifs cumulés pour trouver ces deux valeurs.<br><br>
                    ${desTabEffCumul(tirages, true)}<br><br>
                    `
          // on récupère le score des deux lancers médians
          const scoresMedians = []
          // compteur
          let cpt = 0
          // Pour cumuler les effectifs, tirages est un tableau 2D qui contient les couples [score,effectif]
          let effCumulCroiss = tirages[0][1]
          // On récupère le premier score médian
          while (effCumulCroiss < nombreTirages / 2) {
            cpt += 1
            effCumulCroiss += tirages[cpt][1]
          };
          scoresMedians.push(tirages[cpt][0])
          // On récupère le second score médian
          cpt = 0
          effCumulCroiss = tirages[0][1]
          while (effCumulCroiss < nombreTirages / 2 + 1) {
            cpt += 1
            effCumulCroiss += tirages[cpt][1]
          };
          scoresMedians.push(tirages[cpt][0])
          let medianeCorr // pour la correction statique
          scoresMedians[0] === scoresMedians[1] ? medianeCorr = scoresMedians[0] : medianeCorr = (scoresMedians[0] + scoresMedians[1]) / 2
          texteCorr += `D'où ${texteGras(`le score médian : ${stringNombre(medianeCorr)}`)}<br>`
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${(nombreTirages) / 2}$ lancers dont le score est inférieur ou égal à  $${texNombre(medianeCorr)}$ et $${(nombreTirages) / 2}$ lancers dont le score est supérieur ou égal à  $${texNombre(medianeCorr)}$.`,
            couleur: 'nombres'
          })
          scoresMedians[0] === scoresMedians[1] ? repInteractive = scoresMedians[0] : repInteractive = scoresMedians
        } else { // Le nombre de lancers est impair ici
          texteCorr += `Le nombre de lancers est impair, les scores sont rangés dans l'ordre croissant.<br>
          La valeur centrale est la $${(nombreTirages - 1) / 2 + 1}^{e}$ valeur.<br>
          En effet, ${underbraceMediane(nombreTirages)} <br>
          Une médiane est donc le $${(nombreTirages - 1) / 2 + 1}^{e}$ score.<br>
          On peut ajouter une ligne avec les effectifs cumulés pour trouver cette valeur.<br><br>
          ${desTabEffCumul(tirages, true)}<br><br>`
          // on récupère le score des deux lancers médians
          const scoresMedians = []
          // compteur
          let cpt = 0
          // Pour cumuler les effectifs, tirages est un tableau 2D qui contient les couples [score,effectif]
          let effCumulCroiss = tirages[0][1]
          // On récupère le premier score médian
          while (effCumulCroiss <= nombreTirages / 2) {
            cpt += 1
            effCumulCroiss += tirages[cpt][1]
          };
          scoresMedians.push(tirages[cpt][0])
          texteCorr += `D'où ${texteGras(`le score médian : ${stringNombre(scoresMedians[0])}`)}<br>`
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${(nombreTirages - 1) / 2}$ lancers dont le score est inférieur ou égal à  $${texNombre(scoresMedians[0])}$ et $${(nombreTirages - 1) / 2}$ lancers dont le score est supérieur ou égal à  $${texNombre(scoresMedians[0])}$.`,
            couleur: 'nombres'
          })
          repInteractive = scoresMedians[0]
        }
      } else if (this.sup === 2) { // ici on trie des notes
        if (listePairOuImpair[i] === 'pair') {
          nombreNotes = choice([8, 10, 12])
        } else {
          nombreNotes = choice([7, 9, 11])
        }
        notes = listeDeNotes(nombreNotes, randint(0, 7), randint(13, 20)) // on récupère une liste de notes (série brute)
        indexValeur = randint(0, notes.length - 1) // on choisi une des notes au hasard
        texte = `${prenom()} a obtenu ces notes ce trimestre-ci en mathématiques :<br>`
        texte += `$${notes[0]}$`
        for (let j = 1; j < nombreNotes - 1; j++) { texte += `; $${notes[j]}$ ` } // On liste les notes (série brute)
        texte += `et $${notes[nombreNotes - 1]}$.`
        const notesRangees = notes.sort((a, b) => a - b)
        let mediane
        if (notes.length % 2 === 0) { // attention les indices commencent à 0 !
          notesRangees[notes.length / 2 - 1] === notesRangees[notes.length / 2] ? mediane = notesRangees[notes.length / 2 - 1] : mediane = [notesRangees[notes.length / 2 - 1], notesRangees[notes.length / 2]]
        } else {
          mediane = notesRangees[(notes.length - 1) / 2]
        }
        texteCorr = `Il y a $${notes.length}$ notes en tout. `
        if (notes.length % 2 === 0) {
          texteCorr += 'Le nombre de notes est pair.<br>'
        } else {
          texteCorr += 'Le nombre de notes est impair.<br>'
        };
        texteCorr += `Il faut par exemple ranger les notes dans l'ordre croissant : <br> $${notes[0]}$`
        for (let j = 1; j < nombreNotes - 1; j++) { texteCorr += `; $${notes[j]}$ ` } // On liste les notes (série brute)
        texteCorr += `et $${notes[nombreNotes - 1]}$.<br>`

        if (notes.length % 2 === 0) {
          texteCorr += `Les notes centrales sont la $${notes.length / 2}^{e}$ et la $${notes.length / 2 + 1}^{e}$.<br>
          En effet, ${underbraceMediane(notes.length)}<br>
          Une médiane est donc une note comprise entre la $${notes.length / 2}^{e}$ et la $${notes.length / 2 + 1}^{e}$ note, lorsque ces notes sont rangées.<br>`
        } else {
          texteCorr += `La note centrale est donc la $${(notes.length + 1) / 2}^{e}$.<br>
          En effet, ${underbraceMediane(notes.length)}<br>
          Une médiane est donc la $${(notes.length + 1) / 2}^{e}$ note, lorsque ces notes sont rangées.<br>`
        };
        let medianeCorr // pour la correction statique
        Array.isArray(mediane) ? medianeCorr = (mediane[0] + mediane[1]) / 2 : medianeCorr = mediane
        texteCorr += `D'où ${texteGras(`la note médiane : ${stringNombre(medianeCorr)}`)}<br>`
        if (notes.length % 2 === 0) {
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${notes.length / 2}$ notes inférieures ou égales à  $${texNombre(medianeCorr)}$ et $${notes.length / 2}$ notes supérieures ou égales à  $${texNombre(medianeCorr)}$.`,
            couleur: 'nombres'
          })
        } else {
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${(notes.length - 1) / 2}$ notes inférieures ou égales à  $${texNombre(medianeCorr)}$ et $${(notes.length - 1) / 2}$ notes supérieures ou égales à  $${texNombre(medianeCorr)}$.`,
            couleur: 'nombres'
          })
        }
        repInteractive = mediane
      } else { // ici on relève des températures
        const annee = randint(1980, 2019)
        let listeMois
        if (listePairOuImpair[i] === 'pair') {
          listeMois = [4, 6, 9, 11]
        } else {
          listeMois = [1, 3, 5, 7, 8, 10, 12]
        }
        if ((((annee % 4 === 0) && (annee % 100 !== 0)) || (annee % 400 === 0)) && (listePairOuImpair[i] === 'impair')) { // Si l'année est bissextile et qu'on veut une liste impair
          listeMois.push(2)
        } else if (!(((annee % 4 === 0) && (annee % 100 !== 0)) || (annee % 400 === 0)) && (listePairOuImpair[i] === 'pair')) { // Si l'année n'est pas bissextile et qu'on veut une liste paire
          listeMois.push(2)
        }
        const mois = listeMois[randint(0, listeMois.length - 1)]
        const temperaturesDeBase = [3, 5, 9, 13, 19, 24, 26, 25, 23, 18, 10, 5]
        nombreTemperatures = joursParMois(mois, annee)
        temperatures = unMoisDeTemperature(temperaturesDeBase[mois - 1], mois, annee) // on récupère une série de température correspondant à 1 mois d'une année (série brute)
        indexValeur = randint(0, temperatures.length - 1) // on choisi l'index d'une valeur au hasard
        texte = `En ${nomDuMois(mois)} ${annee}, à ${choice(['Moscou', 'Berlin', 'Paris', 'Bruxelles', 'Rome', 'Belgrade'])}, on a relevé les températures suivantes<br>`
        texte += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // On construit le tableau des températures
        texte += '|c'
        for (let j = 0; j < Math.round(temperatures.length / 2); j++) { texte += '|c' }
        texte += '}\\hline  \\text{Jour}'
        for (let j = 0; j < Math.round(temperatures.length / 2); j++) { texte += '&' + texNombre(j + 1) }
        texte += '\\\\\\hline \\text{Température en}  ^\\circ\\text{C}'
        for (let j = 0; j < Math.round(temperatures.length / 2); j++) { texte += '&' + temperatures[j] }
        texte += '\\\\\\hline\\end{array}$<br><br>'
        texte += '$\\def\\arraystretch{1.5}\\begin{array}{|c' // On construit le tableau des températures
        texte += '|c'
        for (let j = Math.round(temperatures.length / 2); j < temperatures.length; j++) { texte += '|c' }
        texte += '}\\hline  \\text{Jour}'
        for (let j = Math.round(temperatures.length / 2); j < temperatures.length; j++) { texte += '&' + texNombre(j + 1) }
        texte += '\\\\\\hline \\text{Température en}  ^\\circ\\text{C}'
        for (let j = Math.round(temperatures.length / 2); j < temperatures.length; j++) { texte += '&' + temperatures[j] }
        texte += '\\\\\\hline\\end{array}$'
        texteCorr = ''
        const temperaturesRangees = temperatures.sort((a, b) => a - b)
        let mediane
        if (temperatures.length % 2 === 0) { // attention les indices commencent à 0 !
          temperaturesRangees[temperatures.length / 2 - 1] === temperaturesRangees[temperatures.length / 2] ? mediane = temperaturesRangees[temperatures.length / 2 - 1] : mediane = [temperaturesRangees[temperatures.length / 2 - 1], temperaturesRangees[temperatures.length / 2]]
        } else {
          mediane = temperaturesRangees[(temperatures.length - 1) / 2]
        };
        texteCorr = `Il y a $${temperatures.length}$ températures relevées en tout. `
        if (temperatures.length % 2 === 0) {
          texteCorr += 'Le nombre de temperatures est pair.<br>'
        } else {
          texteCorr += 'Le nombre de temperatures est impair.<br>'
        };
        texteCorr += `Il faut par exemple ranger les temperatures dans l'ordre croissant : <br> $${temperatures[0]}$`
        for (let j = 1; j < nombreTemperatures - 1; j++) { texteCorr += `; $${temperatures[j]}$ ` } // On liste les temperatures (série brute)
        texteCorr += `et $${temperatures[nombreTemperatures - 1]}$.<br>`

        if (temperatures.length % 2 === 0) {
          texteCorr += `Les temperatures centrales sont la $${temperatures.length / 2}^{e}$ et la $${temperatures.length / 2 + 1}^{e}$.<br>
          En effet, ${underbraceMediane(temperatures.length)}<br>
          Une médiane est donc une temperature comprise entre la $${temperatures.length / 2}^{e}$ et la $${temperatures.length / 2 + 1}^{e}$ temperature, lorsque ces temperatures sont rangées.<br>`
        } else {
          texteCorr += `La temperature centrale est donc la $${(temperatures.length + 1) / 2}^{e}$.<br>
          En effet, ${underbraceMediane(temperatures.length)}<br>
          Une médiane est donc la $${(temperatures.length + 1) / 2}^{e}$ temperature, lorsque ces temperatures sont rangées.<br>`
        };
        let medianeCorr // pour la correction statique
        Array.isArray(mediane) ? medianeCorr = (mediane[0] + mediane[1]) / 2 : medianeCorr = mediane
        texteCorr += `D'où ${texteGras(`une temperature médiane : ${stringNombre(medianeCorr)}`)}<br>`
        if (temperatures.length % 2 === 0) {
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${temperatures.length / 2}$ temperatures inférieures ou égales à  $${texNombre(medianeCorr)}$ et $${temperatures.length / 2}$ temperatures supérieures ou égales à  $${texNombre(medianeCorr)}$.`,
            couleur: 'nombres'
          })
        } else {
          texteCorr += lampeMessage({
            titre: 'Interprétation',
            texte: `Ìl y a bien $${(temperatures.length - 1) / 2}$ temperatures inférieures ou égales à  $${texNombre(medianeCorr)}$ et $${(temperatures.length - 1) / 2}$ temperatures supérieures ou égales à  $${texNombre(medianeCorr)}$.`,
            couleur: 'nombres'
          })
        }
        repInteractive = mediane
      }

      // On factorise la question
      (this.interactif && !context.isAmc) ? texte += '<br><br>Déterminer une médiane de cette série : ' : texte += '<br>Déterminer une médiane de cette série.'

      if (Array.isArray(repInteractive)) {
        setReponse(this, i, repInteractive, { decimals: 1, milieuIntervalle: calcul((repInteractive[0] + repInteractive[1]) / 2), approx: 'intervalleStrict', formatInteractif: 'intervalleStrict' })
      } else {
        setReponse(this, i, repInteractive)
      }
      if (this.interactif && !context.isAmc) {
        texte += ajouteChampTexteMathLive(this, i, 'largeur20 inline')
      }
      if (this.listeQuestions.indexOf(texte) === -1) { // Si la question n'a jamais été posée, on en créé une autre
        this.listeQuestions.push(texte)
        this.listeCorrections.push(texteCorr)
        i++
      }
      cpt++
    }
    listeQuestionsToContenu(this)
  }
  this.besoinFormulaireNumerique = ['Type de séries', 3, ' 1 : Lancers de dés \n 2 : Liste de notes\n 3 : Un mois de températures']
}