Почему некоторые элементы остаются, даже если условие выполняется для каждого из них?

Я создал приложение для небольшого магазина на 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 не удаляет все элементы, если условие выполняется для каждого сравнения?

Если вы добавите в свой магазин еще несколько товаров, например, еще 3, store.view напечатает товары с идентификаторами = 2,4,6. Кажется, что ваш @database зацикливает только нечетные элементы, когда вы пытаетесь удалить числовые параметры. Если попробуете store.remove(name: "Sprite") - будет работать корректно. Почему - не знаю. Если найду ответ - выложу)

Scrobot 28.05.2019 15:26

Ты прав! Хотя, как и вы, я понятия не имею, почему это могло произойти в коде, который я создал. Может быть, кто-нибудь сможет объяснить мне, что происходит?

user10111321 28.05.2019 15:35
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
3
2
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, что эта проблема возникает из-за сравнения с плавающей запятой. Есть много способов приблизиться к этому. Один из них:

  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

Нет, проблема не возникает из-за сравнения с плавающей запятой. Отредактируйте ответ, чтобы объяснить, что он сделал не так, а не предоставить альтернативу.

ray 28.05.2019 15:20

Я не думаю, что это так, поскольку store.remove(currency: 'SEK') вызывает такое же несоответствие. Поэтому я думаю, что моя логика в чем-то ущербна, кроме одного параметра.

user10111321 28.05.2019 15:22
Ответ принят как подходящий

Давайте сосредоточимся на следующем цикле,

@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

Ты прав! Похоже, я сломал итератор, удаляя элементы каждый раз, когда просматривал массив. Спасибо за предложенное решение :)

user10111321 28.05.2019 17:00

Другие вопросы по теме