Вот я снова сошел с ума и переосмыслил сумму Римана. До этой статьи я опубликовал Интегральное исчисление суммы Римана с помощью функционального JS. И все же у меня все еще есть эта жажда найти альтернативы, которые могли бы быть лучше, чем предыдущая. Вы должны были знать, что сумма Римана - это просто арифматический способ решения задачи интегрирования исчисления или, говоря простым языком, нахождения общей площади под (или над) кривой. Сумма Римана делает это, создавая столько прямоугольников, что вся площадь под кривой покрывается, и суммирует площади всех этих тонких прямоугольников.

Если у вас есть обсессивно-компульсивное расстройство или вкус к совершенству, вы бы возненавидели крошечную выпуклость каждого из этих прямоугольников, которая делала диаграмму графически неприятной и математически неточной. Вы хотели бы поместить другую форму, которая может лучше вписаться в эти фрагменты, может быть, вы думаете о сложенном прямоугольнике и треугольнике по вертикали или (в причудливом термине) о трапеции.

Хотя количество срезов минимально, интуитивно они лучше подходят для вычисления площади под этой кривой. Как же так? Поскольку вершины каждого среза различаются по высоте, таким образом, достигается лучшее приближение разной высоты в каждой точке, сделанной кривой. Делает ли это сумму трапеций лучше, чем сумму Римана в целом, возможно, так.

Я хочу провести аналогию здесь. Представьте себе Рэмбо, окруженного врагами, и он должен изрешетить каждого противника пулями, с двумя пулеметами в руках, грузовиком, полным магазинов, дождь из пуль, как водопад, было бы ежу понятно. Точность здесь больше не проблема, если вы можете напугать взвод и сказать им, кто главный, даже деревья не пощадили. Можно ли сказать, что Рэмбо удалось? Да. Но какой ценой?

Теперь входит Мусаси, самурай-ветеран, отставной от бесчисленных войн. Может, он и не так силен, как десятилетия назад, но совершенство владения мечом никогда не покидало его. Когда его сосед по дереву попросил, чтобы его деревянную доску обрезали по точной линии, Мусаси кивнул. На одном дыхании катана пролетает быстрее, чем могут уследить глаза его соседа, осколки упали и оставили каждый из них идеально разрезанным, без мусора. «Мусаси-сан, как такой опытный человек, как вы, может быть на этой ферме?». Вставив свой меч, Мусаси улыбнулся и произнес: «Лучше быть самураем на ферме, чем быть фермером в зоне боевых действий».

Вот и все, я просто утоляю жажду писать фантастику, ничего страшного. И вернемся к исчислению. Теперь давайте запишем метод, прежде чем мы приступим к кодированию:

  1. Решите, сколько кусочков будет
  2. Найдите высоту вершины трапеции как для левой, так и для правой стороны.
  3. Найдите разницу между левой и правой сторонами этой трапеции.
  4. С этой разницей рассчитайте площадь треугольника
  5. Вычислите площадь прямоугольника под ним
  6. Суммируйте все разрезанные трапеции и представьте результат

Теперь, имея немного знаний в Javascript (и много проб и ошибок), могу я представить вам этот фрагмент:

let withAs = (obj, cb) => cb(obj),
makeArray = n => [...Array().keys()],
sum = arr => arr.reduce((r, i) => r + i),

trapezoidSum = o => withAs(
  (o.end - o.start) / o.slice,
  width => sum(
    makeArray(o.slice).map(i => [
      o.func(o.start + i * width),
      o.func(o.start + i * width + width)
    ]).map(i => sum([
      Math.abs(i[1] - i[0]) * width / 2,
      i[0] * width
    ]))
  )
)

withAs — это функция обратного вызова, позволяющая передавать объект без объявления переменных. makeArray — это функция для создания массива определенной длины, заполненного его порядковым номером. sum работает, как следует из названия, с массивом в качестве параметра. Функция trapezoidSum работает в соответствии с описанным ранее процессом.

trapezoidSum({
  start: 1, end: 5, slice: 4,
  func: x => Math.pow(x, 2)
}) // got 42, should be 41.33

trapezoidSum({
  start: 1, end: 5, slice: 50,
  func: x => Math.pow(x, 2)
}) // finally gets 41.33

Если вы вспомните результат суммы Римана, который я написал в предыдущей статье, вы увидите, что 4 среза левостороннего прямоугольника против уравнения f = x² из x = {1, 5} дадут вам 30 точек площади, что отчаянно неточным (хотя средняя сторона дает лучший результат). Соответствующий результат должен быть 41,33, как и при реальном методе интегрирования (антидеривации). Но с trapezoidSum нам удалось очень близко подойти к этому числу всего за 4 среза, но требуется больше срезов, чтобы они были на равных с результатом реального метода интегрирования (но все же сравнительно более эффективным, чем 100 срезов, которые мы сделали с помощью суммы Римана).

Я сделал функцию trapezoidSum достаточно лаконичной, чтобы ее могли прочитать другие программисты. Но если вы все же найдете в нем легкую борьбу, вот объяснение:

// How It Works
trapezoidSum = o => withAs(
  // make some slices
  (o.end - o.start) / o.slice,
  width => sum(
    makeArray(o.slice).map(i => [
      // find height for..
      o.func(o.start + i * width), // left side
      o.func(o.start + i * width + width) // right side
    ]).map(i => sum([
      // calculate the triangle
      Math.abs(i[1] - i[0]) * width / 2,
      // calculate the rectangle below it
      i[0] * width
    ])) // sum both of them
  ) // and sum every slices
)

Испытание старостью

— Подожди, Мусаси-сан! Какие-то злоумышленники пришли в нашу деревню! Не могли бы вы справиться с ними?»

problems = [
  { // a
    start: -1, end: 2, slice: 50,
    func: x => 3 * Math.pow(x, 2)
  }, // gets 9.0054, while IC gets 9

  { // b
    start: 1, end: 8, slice: 50,
    func: x => Math.pow(x, 1/3)
  }, // gets 11.2495, while IC gets 11.25

  { // d
    start: 0, end: 2, slice: 50,
    func: x => 3*Math.pow(x, 3) - 2*x + 5
  }, // gets 18.0048, while IC gets 18

  { // f
    start: Math.PI/6, end: Math.PI/2, slice: 50,
    func: x => Math.pow(Math.sin(x), 2) * Math.cos(x)
  }, // gets 0.2916, while IC gets 7/24 or 0.2916

  { // h
    start: 0, end: 1, slice: 50,
    func: x => x / Math.pow(Math.pow(x, 2) + 1, 2)
  }, // gets 0.2499, while IC gets 1/4 or 0.25

  { // i
    start: 0, end: 1, slice: 50,
    func: x => withAs(
      Math.pow(Math.E, x),
      exp => exp / (exp + 1)
    )
  }, // gets 0.6201, while IC gets ln(e+1)-ln(2) or 0.6201

  { // q
    start: 1, end: Math.E,
    slice: 50, func: Math.log
  }, // gets 0.9999, while IC gets 1

  { // r
    start: 1, end: Math.E, slice: 50,
    func: x => Math.pow(x, 3) * Math.log(x)
  }, // gets 10.3024, while IC gets 10.2996

  { // z
    start: 0, end: Math.PI/4, slice: 50,
    func: x => (
      1 + Math.pow(Math.sin(x), 2)
    ) / Math.pow(Math.cos(x), 2)
  } // gets 1.2147, while IC gets 2-pi/4 or 1.2146
]

problems.map(trapezoidSum) // or I'd say MushashiSum

С 50 срезами (а не 100) для каждой проблемы (или врага) нам удалось так точно приблизиться к правильному ответу, который дает реальный метод интеграции. Это доказывает, что trapezoidSum может достичь того, что делает сумма Римана, используя, возможно, более эффективный метод.

— Спасибо, Мусаси-сан, теперь мы можем позаботиться о себе. Но куда вы направляетесь? Вам нужен отдых. А мы еще не устроили для вас банкет». Мусаси собирает вещи «Не время бездельничать, и я не имею права заслужить вашу благодарность. Мир забеспокоился, я иду в самое сердце войны». Поехалиооо :D