Не меняется list на tuple

Рейтинг: 7Ответов: 1Опубликовано: 01.04.2023

Есть работающий код для решения fizz-buzz:

python(`
r = [[15, 'fizzbuzz'], [3, 'fizz'], [5, 'buzz'], [1, '']]

for x in range(1, 33):
  for y,m in r:
    if x % y == 0:
      print(m or x)
      break
`)

function python(code) {
  with ({
    list: Array.from,
    *range(l, r, s=1) { for (r ?? (r=l,l=0); l<r; ++l) yield l },
    print: console.log,
  }) {
    eval(code
      .replace(/[ \t]+$/g, "")
      .replace(/^( *)(?:( +).*\r?\n)(?=(?: *\r?\n)*\1(?![ \r\n]))/gm, (m,s,d) => m + d.replace(/  /g, "}"))
      .replace(/^ *(\w+)(.*):\s*$/gm, (m,x,t) => { try { eval(`var ${x}`); return m; } catch { return `${x}(${t}):` } })
      .replace(/:\s*$/gm, "{")
      .replace(/\b((?=\w+,)[\w,]+\s+)in\b/g, '[$1]of')
      .replace(/\bin\b/g, 'of')
      .replace(/\band\b/g, '&&')
      .replace(/\bor\b/g, '||')
    )
  }
}

Хочу в нём переменную r сделать списком tuple'ов, но не получается - код запускается, никаких ошибок не происходит, но и ответ не выводится:

python(`
r = [(15, 'fizzbuzz'), (3, 'fizz'), (5, 'buzz'), (1, '')]

for x in range(1, 33):
  for y,m in r:
    if x % y == 0:
      print(m or x)
      break
`)

function python(code) {
  with ({
    list: Array.from,
    *range(l, r, s=1) { for (r ?? (r=l,l=0); l<r; ++l) yield l },
    print: console.log,
  }) {
    eval(code
      .replace(/[ \t]+$/g, "")
      .replace(/^( *)(?:( +).*\r?\n)(?=(?: *\r?\n)*\1(?![ \r\n]))/gm, (m,s,d) => m + d.replace(/  /g, "}"))
      .replace(/^ *(\w+)(.*):\s*$/gm, (m,x,t) => { try { eval(`var ${x}`); return m; } catch { return `${x}(${t}):` } })
      .replace(/:\s*$/gm, "{")
      .replace(/\b((?=\w+,)[\w,]+\s+)in\b/g, '[$1]of')
      .replace(/\bin\b/g, 'of')
      .replace(/\band\b/g, '&&')
      .replace(/\bor\b/g, '||')
    )
  }
}

PS: С 1 апреля!

Ответы

▲ 1Принят

Проблема заключается в том, как выглядит строка, приходящая в eval.

Если изменить код вашей функции так, чтобы перед eval код для запуска вывелся на экран, мы увидим следущее для первого варианта, который запускается правильно:

r = [[15, 'fizzbuzz'], [3, 'fizz'], [5, 'buzz'], [1, '']]

for( x of range(1, 33)){
for( [y,m ]of r){
if( x % y == 0){
      print(m || x)
      break
}}}

А для 2го варианта с картежами (который не работает) мы увидим следущее:

r = [(15, 'fizzbuzz'), (3, 'fizz'), (5, 'buzz'), (1, '')]

for( x of range(1, 33)){
for( [y,m ]of r){
if( x % y == 0){
      print(m || x)
      break
}}}

Здесь проблема заключается в том, что JS не понимает конструкции вида (значение, значение, значение) так, как вы ожидаете, но считает их валидными.

Например:

const a = (1,2,3,4); // a = 4;
const b = ('hello', 'world'); // b = 'world'

При использовании такого синтаксиса, как видно, вместо создания картежа (т.к. такого просто нет в JS) значением переменной будет последнее переданное в скобках значение. Таким образом после разбора в eval строка

r = [(15, 'fizzbuzz'), (3, 'fizz'), (5, 'buzz'), (1, '')]

Приведет к тому, что r будет равно [ 'fizzbuzz', 'fizz', 'buzz', '' ]

дальнейший же разбор кода:

for([y, m] of r) {
    ...
}

для каждого из значений r будет хранить в y - 1й символ строки на каждой из итераций, а m - 2й символ. То есть console.log(y,m) внутри массива

вернет

f i                       // первые 2 символа fizzbuzz(0й элемент массива r)
f i                       // первые 2 символа fizz(1й элемент массива r)
b u                       // первые 2 символа buzz(2й элемент массива r)
undefined undefined       // первые 2 симввола пустой строки '' (3й элемент массива r)

Для каждого из элементов проверка

if (x % y == 0) {
   ...
}

Не выполнится, и код не попадет в print (m || x)

По причине того, что x - всегда число, а y - 1й символ одной из строк, как я указал выше (либо undefined для последней строки), так что операция x % y всегда будет операцией между числом и буквой/undefuned.

И такая операция в JS выполнется без ошибок, но вернет NaN (not a number).

А NaN==0 всегда будет false.

Вам нужно изменить ваши регулярные выражения так, чтобы внутри объявления списка круглые скобки заменялись на квадратные (то есть, чтобы картежи заменялись на списки. Вот пример правильной функции python:

function python(code) {
  with ({
    list: Array.from,
    *range(l, r, s=1) { for (r ?? (r=l,l=0); l<r; ++l) yield l },
    print: console.log,
  }) {
      const res = (code
      .replace(/[ \t]+$/g, "")
      .replace(/^( *)(?:( +).*\r?\n)(?=(?: *\r?\n)*\1(?![ \r\n]))/gm, (m,s,d) => m + d.replace(/  /g, "}"))
      .replace(/^ *(\w+)(.*):\s*$/gm, (m,x,t) => { try { eval(`var ${x}`); return m; } catch { return `${x}(${t}):` } })
      .replace(/:\s*$/gm, "{")
      .replace(/\b((?=\w+,)[\w,]+\s+)in\b/g, '[$1]of')
      .replace(/\bin\b/g, 'of')
      .replace(/\band\b/g, '&&')
      .replace(/\bor\b/g, '||')
      .replace(/\((\d+,\s*['"][^'"]*['"](?:,\s*\d+,\s*['"][^'"]*['"])*)\)/g, '[$1]') // замена только кортежей
    );
    eval(res);
  }
}

Такая функция будет одинакого правильно отрабатывать и для 1го случая, и для 2го