function singleDigit(num) {
let counter = 0
let number = [...num + ''].map(Number).reduce((x, y) => {return x * y})
if(number <= 9){
console.log(number)
}else{
console.log(number)
return singleDigit(number), counter += 1
}
}
singleDigit(39)
Приведенный выше код принимает целое число и сводит его к одной цифре, умножая его на свои собственные цифры.
Пример 39
3 x 9 = 27.
2 x 7 = 14.
1 x 4 = 4.
Консоль будет регистрировать:
27
14
4
Как отследить, чтобы рекурсивная функция была вызвана 3 раза?
Я попытался добавить счетчик, но он не обновляется. Буду признателен за любую помощь
javascript
recursion
chs242
источник
источник
.map(Number)
является избыточным, поскольку*
оператор в любом случае приводит значения к числу. ;-)-57
действительно a-50
и a-7
.. если смотреть таким образом, то будет сделано уменьшение-5
x,-7
дающее положительное число35
. Или вы хотите, чтобы он видел только отрицательный знак с,5
а не с7
, хотя на7
самом деле это тоже отрицательный знак . 2) Как вы собираетесь работать с числами, которые включают ноль? поскольку это автоматически обнулит сокращение. Следовательно, чем больше число, которое вы передаете, тем больше вероятность того, что оно обнулится. Другим вариантом будет пропустить нулиОтветы:
Это почти чисто академический вариант, но для этой цели вы можете использовать модифицированный комбинатор с фиксированной точкой .
Позволяет немного сократить и улучшить исходную функцию:
Из этого варианта мы можем выделить и вывести рекурсивный вызов:
Эта функция теперь может использоваться с комбинатором с фиксированной точкой; в частности, я реализовал Y-комбинатор, адаптированный для (строгого) JavaScript, следующим образом:
где у нас есть
Ynormal(singleDigitF, 123234234) == 0
.Теперь приходит хитрость. Поскольку мы вычеркнули рекурсию к Y-комбинатору, мы можем посчитать количество рекурсий в нем:
Быстрая проверка в узле REPL дает:
Этот комбинатор теперь будет работать для подсчета количества вызовов в любой рекурсивной функции, написанной в стиле
singleDigitF
.(Обратите внимание, что есть два источника получения нуля в качестве очень частого ответа: числовое переполнение (
123345456999999999
становление123345457000000000
и т. Д.) И тот факт, что вы почти наверняка получите ноль в качестве промежуточного значения где-то, когда размер входных данных увеличивается.)источник
Вы должны добавить встречный аргумент в определение вашей функции:
источник
counter
, ее отбрасывают и устанавливают на0
, если вы явно не переносите ее в своем рекурсивном вызове, как это делает Sheraff. AesingleDigit(number, ++counter)
++counter
наcounter+1
. Они функционально эквивалентны, но последний лучше определяет намерения, не (неоправданно) изменяет и не изменяет параметры и не имеет возможности случайного постинкрементного увеличения. Или еще лучше, так как это хвостовой вызов, используйте вместо этого цикл.Традиционное решение состоит в том, чтобы передать счет в качестве параметра функции, как это предлагается в другом ответе.
Тем не менее, есть другое решение в JS. Несколько других ответов предложили просто объявить count вне рекурсивной функции:
Это конечно работает. Однако это делает функцию не входящей (не может быть вызвана дважды правильно). В некоторых случаях вы можете игнорировать эту проблему и просто убедиться, что вы не вызываете
singleDigit
дважды (javascript является однопоточным, так что это не так уж сложно сделать), но это ошибка, ожидающая того, что произойдет, если вы обновитесьsingleDigit
позже, чтобы стать асинхронной, и она также чувствует некрасиво.Решение состоит в том, чтобы объявить
counter
переменную снаружи, но не глобально. Это возможно, потому что у javascript есть замыкания:Это похоже на глобальное решение, но каждый раз, когда вы вызываете
singleDigit
(которая теперь не является рекурсивной функцией), оно будет создавать новый экземплярcounter
переменной.источник
singleDigit
функции и предоставляет альтернативный чистый способ сделать это без передачи аргумента imo. +1recursion
как теперь он полностью изолирован, следует передать счетчик как последний параметр. Я не думаю, что создание внутренней функции необходимо. Если вам не нравится идея иметь параметры для единственной выгоды рекурсии (я понимаю, что пользователь может связываться с ними), тогда заблокируйте ихFunction#bind
в частично примененной функции.the traditional solution is to pass the count as a parameter
. Это альтернативное решение в языке с замыканиями. В некотором смысле проще следовать, потому что это только одна переменная, а не бесконечное число экземпляров переменных. В других отношениях знание этого решения помогает, когда отслеживаемая вещь является общим объектом (представьте, что вы создаете уникальную карту) или очень большим объектом (например, строкой HTML)counter--
было бы традиционным способом решить вашу претензию "нельзя дважды назвать правильно"Другой подход, поскольку вы производите все числа, заключается в использовании генератора.
Последний элемент - это ваше число,
n
уменьшенное до однозначного числа, и чтобы подсчитать, сколько раз вы повторяли, просто прочитайте длину массива.Последние мысли
Возможно, вы захотите рассмотреть возможность возврата в свое состояние. Все номера с нулем в нем будет возвращать ноль.
То же самое можно сказать и о любых числах, состоящих
1
только из.Наконец, вы не уточнили, принимаете ли вы только положительные целые числа. Если вы принимаете отрицательные целые числа, приведение их к строкам может быть рискованным:
Возможное решение:
источник
Здесь было много интересных ответов. Я думаю, что моя версия предлагает дополнительную интересную альтернативу.
Вы делаете несколько вещей с вашей требуемой функцией. Вы рекурсивно сводите его к одной цифре. Вы записываете промежуточные значения, и вам нужно подсчитать количество рекурсивных вызовов. Один из способов справиться со всем этим - написать чистую функцию, которая будет возвращать структуру данных, содержащую конечный результат, выполненные шаги и количество вызовов в одном:
Затем вы можете записать шаги, если хотите, или сохранить их для дальнейшей обработки.
Вот версия, которая делает это:
Обратите внимание, что мы отслеживаем,
steps
но выводимcalls
. Хотя мы могли бы отслеживать количество вызовов с помощью дополнительного параметра, это, похоже, ничего не дает. Мы также пропускаемmap(Number)
шаг - они будут приведены к числам в любом случае умножением.Если у вас есть опасения по поводу того, что этот
steps
параметр по умолчанию выставлен как часть вашего API, его достаточно легко скрыть с помощью внутренней функции, подобной этой:И в любом случае, было бы немного чище извлечь умножение цифр в вспомогательную функцию:
источник
digitProduct
вернетсяNaN
(-39 ~> ('-' * '3') * '9'
). Таким образом, вы можете использовать абсолютное значение n и использовать-1
или1
в качестве начального значения вашего снижения.{"digit":-39,"steps":[-39],"calls":0}
, так как-39 < 9
. Хотя я согласен, что это может быть связано с некоторой проверкой ошибок: параметр является числом? - это положительное целое число? - и т. д. Я не думаю, что обновлю, чтобы включить это. Это фиксирует алгоритм, и обработка ошибок часто зависит от кода.Если вы просто пытаетесь подсчитать, сколько раз оно уменьшается, и не заботитесь о рекурсии конкретно ... вы можете просто удалить рекурсию. Приведенный ниже код остается верным Исходному сообщению, поскольку он не считается
num <= 9
нуждающимся в сокращении. Следовательно,singleDigit(8)
будет иметьcount = 0
иsingleDigit(39)
будетcount = 3
, так же, как OP и принятый ответ демонстрируют:Нет необходимости обрабатывать числа 9 или меньше (т.е.
num <= 9
). К сожалению, код OP будет обрабатываться,num <= 9
даже если он его не считает. Код выше не будет ни обрабатывать, ни считатьnum <= 9
. Это просто проходит через.Я предпочитаю не использовать,
.reduce
потому что выполнение математики выполнялось намного быстрее. И, для меня, легче понять.Дальнейшее размышление о скорости
Я чувствую, хороший код тоже быстро. Если вы используете этот тип сокращения (который часто используется в нумерологии), вам может понадобиться использовать его для огромного количества данных. В этом случае скорость станет самым важным.
Использование обоих
.map(Number)
иconsole.log
(на каждом этапе сокращения) очень и очень долго для выполнения и не нужно. Простое удаление.map(Number)
из OP ускорило его примерно в 4,38 раза. Удалениеconsole.log
ускорило его настолько, что было почти невозможно правильно протестировать (я не хотел ждать этого).Так что , похоже на customcommander ответ «s, не используя
.map(Number)
ниconsole.log
и толкающие результаты в массив и использовать.length
дляcount
гораздо гораздо быстрее. К сожалению для ответа customcommander , использование функции генератора действительно очень медленно (этот ответ примерно в 2,68 раза медленнее, чем OP без.map(Number)
иconsole.log
)Кроме того, вместо использования
.reduce
я просто использовал фактическую математику. Это единственное изменение ускорило мою версию функции в 3,59 раза.Наконец, рекурсия медленнее, она занимает место в стеке, использует больше памяти и имеет ограничение на количество повторений. Или, в этом случае, сколько шагов сокращения он может использовать, чтобы завершить полное сокращение. Развертывание рекурсии в итеративных циклах сохраняет все это в одном и том же месте в стеке и не имеет теоретического ограничения на количество шагов сокращения, которое можно использовать для завершения. Таким образом, эти функции здесь могут «уменьшить» почти любое целое число, ограниченное только временем выполнения и продолжительностью массива.
Все это в виду ...
Вышеуказанная функция работает очень быстро. Это примерно в 3,13 раза быстрее, чем OP (без
.map(Number)
иconsole.log
), и примерно в 8,4 раза быстрее, чем ответ customcommander . Имейте в виду, что удалениеconsole.log
из OP предотвращает создание числа на каждом шаге сокращения. Следовательно, здесь необходимо поместить эти результаты в массив.PT
источник
I feel good code is also fast.
Я бы сказал , что качество кода имеет следует оценивать с определенным набором требований. Если производительность не является одним из них, вы ничего не получите, заменив код, который любой может понять, на «быстрый» код. Вы не поверите, что количество кода, которое я видел, было подвергнуто рефакторингу до такой степени, что никто больше не может его понять (по какой-то причине оптимальный код, как правило, также недокументирован;). Наконец, имейте в виду, что сгенерированные ленивые списки позволяют потреблять элементы по требованию.[...num+''].map(Number).reduce((x,y)=> {return x*y})
или даже[...String(num)].reduce((x,y)=>x*y)
утверждения, которые я вижу в большинстве ответов здесь. Так что для меня это было дополнительным преимуществом лучшего понимания того, что происходит на каждой итерации, и намного быстрее. Да, минимизированный код (который имеет свое место) ужасно труден для чтения. Но в этих случаях, как правило, сознательно не заботятся о его читабельности, а просто о конечном результате, который можно вырезать, вставить и продолжить.digit = num%10;
num /= 10;
? Необходимость выполнитьnum - x
сначала удаление последней цифры перед делением, вероятно, заставит JIT-компилятор сделать отдельное деление от того, которое он сделал, чтобы получить остаток.var
s (у JS нетint
s). Следовательно,n /= 10;
при необходимости преобразуетсяn
в число с плавающей точкой.num = num/10 - x/10
может преобразовать его в число с плавающей точкой, которое является длинной формой уравнения. Следовательно, я должен использовать реорганизованную версию,num = (num-x)/10;
чтобы сохранить ее целым числом. В JavaScript я не могу найти способ, который может дать вам как частное, так и остаток от одной операции деления. Кроме того,digit = num%10; num /= 10;
это два отдельных оператора и, следовательно, две отдельные операции деления. Прошло много времени с тех пор, как я использовал C, но я тоже думал, что это правда.Почему бы не сделать вызов
console.count
в вашей функции?Изменить: фрагмент, чтобы попробовать в вашем браузере:
У меня это работает в Chrome 79 и Firefox 72
источник
Вы можете использовать закрытие для этого.
Просто храните
counter
в закрытии функции.Вот пример:
источник
singleDigitDecorator()
будет увеличивать один и тот же счетчик при каждом вызове.Вот версия Python, которая использует функцию-обертку для упрощения счетчика, как было предложено в ответе slebetman - я пишу это только потому, что основная идея очень ясна в этой реализации:
источник