Pull to refresh
2
0

Frontend developer

Send message

Что вы подразумеваете под "парсингJSX шаблонов на клиенте"?

Да, действительно, хорошее решение. Я не против такого решения.
Могу отметить три различия: поддержка браузеров, производительность и читаемость.


Поддержка браузеров:


Object.keys + for-of: 94%
Object.values+.flat(): 91%


Производительность на входных данных из статьи на Node v12:
Внимание: бенчмарк на единственных входных данных на единственной версии интерпретатора JS. Не делайте решающих выводов на основании только этого бенчмарка


keys + for-of x 7,210,543 ops/sec ±2.47% (85 runs sampled)
values + flat x 747,610 ops/sec ±0.82% (91 runs sampled)
Fastest is keys + for-of

(Чем больше ops/sec тем лучше)


Код бенчмарка
const Benchmark = require("benchmark");

const suite = new Benchmark.Suite();

const phoneBook = {
  andrew: ["+345356245254", "+313232312312"],
  vasilina: ["+132313123123"],
  serhiy: ["+587234878234", "+321323124123"],
};

suite
  .add("keys + for-of", function () {
    const phoneNumbers = [];

    const personNames = Object.keys(phoneBook);

    for (const personName of personNames) {
      const personPhoneNumbers = phoneBook[personName];
      phoneNumbers.push(...personPhoneNumbers);
    }

    return phoneNumbers;
  })
  .add("values + flat", function () {
    return Object.values(phoneBook).flat();
  })
  .on("cycle", function (event) {
    console.log(String(event.target));
  })
  .on("complete", function () {
    console.log("Fastest is " + this.filter("fastest").map("name"));
  })
  .run({ async: true });

Читаемость вашего варианта очень хороша.


В виду этих различий я бы рассматривал насколько каждое из них важнее в вашем случае.


На мой взгляд читаемость важнее. Так что мой голос идёт в сторону Object.values(phoneBook).flat()

Возникла идея использовать библиотеку валидации для декларативного описания стратегии итерации по вложений структуре данных объекту.
Эта идея меня позабавила. Я хотел такой идеей поделиться. Об этом и статья
А так как эта идея обладает отрицательными качествами, я решил о них написать, чтобы не возникло ощущения что я предлагаю использовать валидации для итерации в «серьезном» коде.

Я хотел передать, что даже если бы такая оптимизация и была имплементирована в браузере — то изначальная функция не подпадала бы под эту оптимизацию, и её необходимо поправить, так чтобы вызов рекурсии был последней операцией в функции.


В любом случае такая оптимизация предусмотрена стандартом:
https://www.ecma-international.org/ecma-262/11.0/index.html#sec-evaluatecall


Спасибо за поправку, необходимо было упомянуть, что эта оптимизация не во всех интерпретаторах/компиляторах есть, и в том числе в большинстве Javascript интерпретаторов.


Мемоизация поможет с глубиной только на последующих вызовах. Первый вызов — будет иметь полную глубину рекурсии.

Ваш первый комментарий касался формы, а не содержания.


Спасибо, за критику формы подачи. Я подумаю, как это можно улучшить.


Надеюсь, что этот приём не ухудшил ваше восприятие сути. Будет интересно узнать ваше мнение и по содержанию.

возникло чувство, что этот Максим чем-то известен, но я его почему-то не знаю

Да, я об этом думал, но предпочёл использовать этот приём. Он принёс больше пользы, чем вреда в статью, по причинам описанным выше.


на мой взгляд, не надо так много заботиться о нежной психике читателя

Я не считаю читателя «нежным».


не надо так много заботиться о нежной психике читателя, ему интересна суть

Это ложное противопоставление. Использование этого приёма не ухудшает восприятие сути. И у меня есть основания полагать, что улучшает.

Здравствуйте, это осознанный выбор.


Эта история с ошибкой в рекурсии произошла не просто в вакууме. Значит эта ошибка была допущена некоторым агентом.


Есть три варианта кто им может быть: писатель, читатель или третье лицо («Максим»).


Если это будет писатель, то текст будет выглядеть так:


"Я написал что то и оно сломалось"


Читатель, который сталкивался с такой проблемой сразу начнет воспринимать писателя как не опытного, даже не осознанно. И это плохо для писателя, ведь он хочет поделиться опытом, который у него есть.


Предположим что этот агент — читатель. Тогда текст будет выглядеть так:


"Вот вы написали вот это, и оно сломалось"


Читатель воспримет это негативно, ведь ему вменили «преступление». И это плохо для читателя, которому станет тяжелее воспринимать описанный опыт. Это плохо и для писателя, ведь он хочет поделиться опытом, а не чувством вины или ненависти и ТД.


Таким образом, если действие имеет плохую окраску лучше использовать третье лицо. Это не будет окрашивать читателя или писателя в темные тона.


Про этот литературный прием я прочитал в книге «Пиши и сокращай» Ильяхова.


Спасибо за комментарий!

Добавлю ссылку на эту ветку комментариев в конец, спасибо за ваши комментарии! Я так мозгом давно не воротил)

Удаление с мутацией основного дерева без рекурсии. Очень сложный но вроде работает:


function findMostRightNodeWithParent<T>(tree: INode<T>): [INode<T>, INode<T> | null] {
    let parent: INode<T> | null = null
    let currentNode = tree
    while (currentNode.right) {
        parent = currentNode
        currentNode = currentNode.right
    }

    return [currentNode, parent]
}

function mutableRemove<T>(tree: Tree<T>, value: T): Tree<T> {
    if (!tree) return null
    const nodesWithParent: [INode<T>, INode<T> | null][] = [[tree, null]]
    let foundTree: INode<T> | null = null
    let foundTreeParent: INode<T> | null = null
    while (nodesWithParent.length > 0) {
        const [node, nodeParent] = nodesWithParent.pop() as [INode<T>, INode<T> | null]
        if (node.value === value) {
            foundTree = node
            foundTreeParent = nodeParent
            break
        }
        if (node.right) {
            nodesWithParent.push([node.right, node])
        }
        if (node.left) {
            nodesWithParent.push([node.left, node])
        }
    }
    // Такого элемента нет в дереве
    if (!foundTree) return tree
    // Такой элемент это корень дерева
    if (!foundTreeParent) {
        // если нет левого поддерева возвращаем правое поддерево
        if (!foundTree.left) return foundTree.right
        // если нет правого поддерева возвращаем левое поддерево
        if (!foundTree.right) return foundTree.left
        // Если есть и правое и левое, то ищем самый правый элемент левого поддерева, и ставим его в голову дерева. Удаляем его снизу
        const [mostRight, mostRightParent] = findMostRightNodeWithParent(foundTree.left)
        if (mostRightParent) {
            mostRightParent.right = mostRight.left
        }
        foundTree.value = mostRight.value
        return foundTree
    }

    const direction = foundTreeParent.left === foundTree ? 'left' : 'right'
    // Если это лист - удаляем его
    if (!foundTree.left && !foundTree.right) {
        foundTreeParent[direction] = null
        return tree
    }
    // Если это элемент - не содержит одной из ветвей - то ту ветвь которая есть присвоить родителю найденой ветви
    if (!foundTree.left) {

        foundTreeParent[direction] = foundTree.right
        return tree
    }
    if (!foundTree.right) {
        foundTreeParent[direction] = foundTree.left
        return tree
    }
    let [mostRightChild, mostRightChildParent] = findMostRightNodeWithParent(foundTree.left)
    // если есть промежуточный родитель до самого правого слева
    if (mostRightChildParent) {
        mostRightChildParent.right = mostRightChild.left
    } else {
        // промежуточного родителя слева нет
        foundTree.left = mostRightChild.left
    }
    foundTree.value = mostRightChild.value
    return tree
}

Спасибо большое за проявленное внимания!


В первом примере у вас ручная реализация абстракции call stack

Вы заметили основную суть. Именно ручную реализацию кол стека я и написал.


В чём тогда профит?

Профит в том. что такая функция не упадёт так рано, как рекурсивная реализация. Я об этом написал в фразе "Зависит от размера задачи". Когда нужно посчитать факториал для чисел меньше 1K-10K нужно юзать рекурсию. И это будет более ефективно. Но когда размер задачи достаточно велик — рекурсивная версия может просто слишком рано упасть. В то время как размер кучи обычно гораздо выше, а значит памяти хватит на большее.


при переполнении ему грозит не Stack overflow error, а Out of memory error

Итеративную версию можно уронить, вопрос только при каких размерах входных данных.


Спасибо за комментарий и проделанный анализ, надеюсь читатель моей статьи ознакомится и с вашим комментарием, чтобы не возникало ощущение, что это серебрянная пуля и не уронимая скала.

У меня тоже возник такой диссонанс, но мне показалось, что я просто не понял мысль комментатора и/или он имел в виду некий другой аспект математиков.

Пример итерации по дереву на Typescript. На Javascript было бы точно так, только без типов.


type Tree<T> = INode<T> | null

interface INode<T> {
    value: T,
    left: Tree<T>,
    right: Tree<T>
}

function breadthFirstIterate<T>(tree: Tree<T>, callback: (value: T) => void) {
    if (!tree) return
    const nodes: INode<T>[] = [tree]

    while (nodes.length > 0) {
        const node = nodes.shift() as INode<T>
        callback(node.value)
        if (node.left) {
            nodes.push(node.left)
        }
        if (node.right) {
            nodes.push(node.right)
        }
    }
}

function depthFirstIterate<T>(tree: Tree<T>, callback: (value: T) => void) {
    if (!tree) return
    const nodes: INode<T>[] = [tree]
    while (nodes.length > 0) {
        const node = nodes.pop() as INode<T>
        callback(node.value)
        if (node.right) {
            nodes.push(node.right)
        }
        if (node.left) {
            nodes.push(node.left)
        }
    }
}

Здесь итерация в ширину и в длину. Итерация в ширину не оптимально быстрая так как массив в JS не очень хорош для организации очереди. Лучше создать односвязный список, чтобы получение очередной ноды была операция за O(1). А не О(n).


Насчёт удаления или замены. Тут зависит от того, хочу ли я на выходе получить то же дерево что изначально с изменениями, или построить новое дерево с применёнными измениями.


Такого раньше не писал итеративно, но сейчас попробую

В глубину или в ширину?

Да, функция Аккермана была бы очень в тему для модельного примера, спасибо за ответ!


Люблю пролог. Самое необычное из того на чем приходилось писать.


Интересно в какой области у вас возникли такие задачи?

Да!


Не знаю насчёт развёрнутой итерации в сложных случаях будет ли она проще для дебагинга, но согласен, что когда stack-trace огромный это бывает сбивает с толку.


Спасибо за комментарий!

Считаю, что если нечто можно написать просто — необходимо писать просто.


Также считаю, что если абстракция не упрощает — то необходимо доказать, зачем она нужна прежде чем добавлять её в проект.


Интересно узнать, что именно вы бы поменяли в этом решении. Хотя бы в общих чертах.


В любом случае, спасибо за комментарий!

Абсолютно согласен с вами!


Насчёт оптимизации добавлю, что нужно чтобы Максим знал, каким требованиям должна соответствовать рекурсия, чтобы компилятор/интерпретатор мог оптимизировать её с помощью Tail Call Optimization. Если вы о ней.


Чтобы функция факториала была оптимизирована её нужно будет переписать вот так:


function factorial(n, acc = 1) {
  if (n <= 1) return acc
  return factorial(n-1, acc * n)
}

Изначальная вариант не будет оптимизирован в итерацию. Насколько я знаю, конечно.


Насчёт второго, мне больнее всего за читаемость, при таких разворотах.


Спасибо за комментарий, очень полезные примечания

Information

Rating
Does not participate
Location
Винница, Винницкая обл., Украина
Registered
Activity