Я создал приложение для небольшого магазина на Ruby. Концепция проста: я хочу иметь возможность добавлять, удалять и просматривать «базу данных» в этом примере. Код работает; однако он ведет себя не так, как можно было бы ожидать. Я могу добавить любой продукт, но когда я пытаюсь удалить их по определенному критерию, который соответствует каждому элементу, удаляются только некоторые из них.
class Store
PRODUCT = Struct.new(:identifier, :parameters)
def initialize(database = [])
@database = database
end
def increment
# Increment the unique identifier if the database is not empty
# Otherwise, set the initial value for the unique identifier
@database.any? ? @database.map { |object| object[:identifier] }.max + 1 : 1
end
def add(product)
@database.push(identifier: increment, parameters: product)
end
def remove(product)
@database.each do |object|
product.each do |key, value|
@database.delete(object) if object[key] == value
@database.delete(object) if object[:parameters][key] == value
end
end
end
def view
puts @database
puts "\n"
end
end
store = Store.new
store.add(name: 'Fanta', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.add(name: 'Sprite', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.add(name: 'Coca-Cola', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.view
store.remove(price: 9.95)
store.view
В этом примере мы добавим три произвольных элемента в экземпляр Store
и перечислим содержимое, найденное в массиве @database
. Этот массив, конечно, будет содержать значения:
{:identifier=>1, :parameters=>{:name=>"Fanta", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
{:identifier=>2, :parameters=>{:name=>"Sprite", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
{:identifier=>3, :parameters=>{:name=>"Coca-Cola", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
Затем код удаляет любой продукт с ценой 9,95, которая должна соответствовать всем продуктам в массиве @database
. Однако, когда я еще раз перечисляю его содержимое, остается один продукт:
{:identifier=>2, :parameters=>{:name=>"Sprite", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
Я не могу найти в этом никакой логики. Почему мой метод remove
не удаляет все элементы, если условие выполняется для каждого сравнения?
Ты прав! Хотя, как и вы, я понятия не имею, почему это могло произойти в коде, который я создал. Может быть, кто-нибудь сможет объяснить мне, что происходит?
Я думаю, что эта проблема возникает из-за сравнения с плавающей запятой. Есть много способов приблизиться к этому. Один из них:
def remove(product)
@database.delete_if do |object|
product.any? do |key, value|
matches?(object[key], value) || matches?(object[:parameters][key], value)
end
end
end
def matches?(obj1, obj2)
if obj1.is_a?(Float) || obj2.is_a?(Float)
(obj1-obj2).abs < 0.001
else
obj1 == obj2
end
end
Нет, проблема не возникает из-за сравнения с плавающей запятой. Отредактируйте ответ, чтобы объяснить, что он сделал не так, а не предоставить альтернативу.
Я не думаю, что это так, поскольку store.remove(currency: 'SEK')
вызывает такое же несоответствие. Поэтому я думаю, что моя логика в чем-то ущербна, кроме одного параметра.
Давайте сосредоточимся на следующем цикле,
@database.each do |object|
product.each do |key, value|
puts '-----------------'
@database.delete(object) if object[key] == value
@database.delete(object) if object[:parameters][key] == value
end
end
Предполагается выполнить 3 итерации в каждом цикле для @database
,
первая итерация - сначала удаляется с идентификатором = 1 для каждого счетчика цикла 0
вторая итерация - он изменяется @database
, так как первый удаляется, второй становится первым, а 3-й становится 2-м, а счетчик каждого цикла равен 1. Таким образом, он удаляет новый 2-й элемент @database
, имеющий идентификатор = 3.
третья итерация — каждый счетчик цикла будет равен 2, а новое обновленное @database
будет иметь только 1 элемент с идентификатором = 2 (спасенный во 2-й итерации), и нет третьего элемента для продолжения итерации, поэтому ничего не повторяется.
Необходима коррекция:
def remove(product)
@database.reject! do |object|
product.map do |k,v|
object[k] == v if object.has_key?(k)
object[:parameters][k] == v if object[:parameters].has_key?(k)
end.inject(&:|)
end
end
Ты прав! Похоже, я сломал итератор, удаляя элементы каждый раз, когда просматривал массив. Спасибо за предложенное решение :)
Если вы добавите в свой магазин еще несколько товаров, например, еще 3,
store.view
напечатает товары с идентификаторами = 2,4,6. Кажется, что ваш@database
зацикливает только нечетные элементы, когда вы пытаетесь удалить числовые параметры. Если попробуетеstore.remove(name: "Sprite")
- будет работать корректно. Почему - не знаю. Если найду ответ - выложу)