Ruby карта дает странные результаты - PullRequest
3 голосов
/ 11 июля 2020

Рассмотрим следующий код

a="123456789"
t=[[1,4],[3,4],[4,5],[1,2]]
p t.map{|x,y|
    a[x],a[y]=a[y],a[x] 
    #p a  
    a
}

Я знаю, что метод ruby map собирает последнее выражение данного блока, но при использовании приведенного выше кода для замены символов в a с использованием индексов в t не удастся. Мое намерение состояло в том, чтобы собрать состояние a после каждого свопа в индексе t. Но map всегда дает массив a, который находится в последнем состоянии ie) ["135264789", "135264789", "135264789", "135264789"].

Результаты показывают, что метод карты собрал окончательный результат a после завершения каждого индекса в t. Но при печати a после каждого свопа печатает правильное значение a в каждое состояние.

Это правильное поведение или я что-то упустил?

Ответы [ 3 ]

5 голосов
/ 11 июля 2020

Это связано с тем, что метод String#[]= изменяет строку.

Быстрое исправление будет примерно таким:

a="123456789"
t=[[1,4],[3,4],[4,5],[1,2]]
p t.map{|x,y]
    b = "#{a}" # IMPORTANT - this builds a new string
    b[x],b[y]=b[y],b[x] # this mutates the new string
    #p b  
    b
}

Вместо "#{a}" можно было бы сказать a.clone, в этом случае он делает то же самое.

Причина, по которой это работает, в том, что вместо прямого изменения a с помощью a[x],a[y]=a[y],a[x] вы ' создайте временную копию a и измените ее вместо

edit - я неправильно прочитал вопрос - если вы хотите показать результат связывания каждой операции с предыдущим результатом, используйте dup / clone после модификации, как сказал Стефан в своем ответе

4 голосов
/ 11 июля 2020

Я правильно понимаю?

Да, я считаю, что это так. Я повторяю то, что говорит Макс, и также немного уточню на случай, если это поможет.

Каждый b - это вновь созданный объект, потому что он создается внутри блока, поэтому он воссоздается с каждой новой итерацией . a создается вне блока, поэтому один и тот же объект (a) продолжает получать ссылки внутри блока для каждой итерации.

Вы можете лучше понять, как это работает, поэкспериментировав с #object_id. Попробуйте запустить этот код:

a="123456789"
t=[[1,4],[3,4],[4,5],[1,2]]
p t.map { |x,y|
    b = "#{a}" # IMPORTANT - this builds a new string
    b[x],b[y]=b[y],b[x]
    p "a.object_id = #{a.object_id}"
    p "b.object_id = #{b.object_id}"
    b
}

Вы заметите, что a - это один и тот же объект для каждой итерации метода #map, а b - новый.

Это пример концепции закрытия . Замыкание - это своего рода закрытая структура кода, которая сохраняет доступ к любому состоянию, доступному в контексте, в котором оно было создано, в то время как этот контекст не имеет доступа к своему состоянию закрытого кода. Что-то вроде «одностороннего зеркала»: закрытый код может видеть снаружи, но снаружи не может видеть закрытый код.

В Ruby замыкания реализованы как блоки: блоки являются замыканиями. Таким образом, все, что видно для любого контекста, в котором создан блок (в данном случае main), также видно для этого блока, хотя обратное неверно - например, вы не можете ссылаться на b извне. блок. (Методы не являются закрытием: если бы ваш блок был методом, он не смог бы увидеть a, если бы вы не передали его в качестве аргумента вашему методу.)

Итак, как говорит Макс, когда вы вносите изменения в a внутри своего блока, вы фактически меняете (мутируете) тот же a, который вы определили вверху каждый раз.

Side topi c

Теперь, если вы ссылаетесь на отдельные символы в строках, важно понимать, что основная структура строк отличается от структуры массивов. Кроме того, массивы ведут себя по-разному, когда вы изменяете их элементы из строк, когда вы мутируете их символы. Ссылки. Это в значительной степени верно только в отношении синтаксиса.

Результаты выполнения этого кода могут показаться интересными:

a = '123456789'
p a.object_id
p a[0].object_id
p a[1].object_id
a[0] = '7'
p a.object_id
p a[0].object_id
p a[1].object_id

puts
a = '123456789'.chars
p a.object_id
p a[0].object_id
p a[1].object_id
a[0] = '7'
p a.object_id
p a[0].object_id
p a[1].object_id

В частности, сравнение четырех выходных данных a[1].object_id должен быть поучительным, потому что он показывает, где строки и массивы отличаются. Если вы переназначаете элемент в массиве, этот элемент и только этот элемент получает новый идентификатор объекта. Если вы переназначаете символ в строке, сам строковый объект остается прежним, но каждый символ в строке создается заново.

2 голосов
/ 11 июля 2020

Поскольку вы возвращаете a из map, результат будет содержать a четыре раза. Все эти a относятся к одному и тому же объекту.

Вероятно, вы захотите вернуть копию a, чтобы сохранить его текущее состояние:

a = '123456789'
t = [[1, 4], [3, 4], [4, 5], [1, 2]]
r = t.map { |x, y|
  a[x], a[y] = a[y], a[x]
  a.dup
}
r #=> ["153426789", "153246789", "153264789", "135264789"]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...