15. Пета задача. Класови променливи. Регулярни изрази, част 2

15. Пета задача. Класови променливи. Регулярни изрази, част 2

15. Пета задача. Класови променливи. Регулярни изрази, част 2

5 декември 2016

Днес

Пета задача

Пета задача

цели

Хранилища

HashStore

#create

def initialize
  @storage = {}
end

def create(record)
  @storage[record[:id]] = record
end

HashStore

#find

def find(query)
  @storage.values.select do |record|
    query.all? { |key, value| record[key] == value }
  end
end

HashStore

#delete

def delete(query)
  find(query).each { |record| @storage.delete(record[:id]) }
end

HashStore

#update

def update(id, record)
  return unless @storage.key? id

  @storage[id] = record
end

Проблем 1

overuse на each

def find(search_hash)
  result = []
  @storage.each do |_, current_hash|
    result << current_hash if sub_hash?(current_hash, search_hash)
  end
  result
end

Проблем 1

помните ли select/reject?

def find(query)
  @storage.select do |_, record|
    sub_hash?(record, query)
  end
end

Проблем 1

...

def find(query)
  @storage.select { |_, record| sub_hash?(record, query) }
end

Проблем 1

още един пример

to_overload.each do |key, value|
  desired_hash[key] = value
end

Проблем 1

...

desired_hash.merge!(to_overload)

Проблем 2

джаваподобни типове

class DataStore
  def create; end
  def find;   end
  def delete; end
  def update; end
end

class ArrayStore < DataStore
  # ...
end

class HashStore < DataStore
  # ...
end

Проблем 2

...

Проблем 3

опити за DRY на прост код

module DataStore
  def delete(query)
    find(query).each do |obj|
      delete_by_id(obj[:id])
    end
  end
end

class HashStore
  include DataStore
  def delete_by_id(id)
    @storage.delete(id)
  end
end

Проблем 3

...

class HashStore
  def delete(query)
    find(query) { |record| @storage.delete(record[:id]) }
  end
end

The wrong abstraction

The wrong abstraction

The wrong abstraction

Sandi Metz

The wrong abstraction

The wrong abstraction

The wrong abstraction

Изводи

The wrong abstraction

The wrong abstraction

Existing code exerts a powerful influence. Its very presence argues that it is both correct and necessary. We know that code represents effort expended, and we are very motivated to preserve the value of this effort. [...] It's as if our unconscious tell us "Goodness, that's so confusing, it must have taken ages to get right. Surely it's really, really important. It would be a sin to let all that effort go to waste."

Из The Wrong Abstraction

Проблем 4

id-та в модела

Проблем 5

преизползване на id-та

Представете си StackOverflow:

HashStore

id-та

class HashStore
  def initialize
    # ...
    @next_id = 0
  end

  def next_id
    @next_id += 1
  end
end

Проблем 6

attributes.each do |attribute|
  class_eval { attr_accessor attribute }
end

Проблем 6

...

attributes.each do |attribute|
  attr_accessor attribute
end

Проблем 7

self.class.instance_variable_get(:@repository)
# vs
self.class.data_model

Инстанционни променливи на класове

преговор

class DataModel
  def self.attributes(*attributes)
    return @attributes if attributes.empty?
    @attributes = attributes
    # ...
  end
end

class User < DataModel
  attributes :name
end

class Picture < DataModel
  attributes :date
end

Инстанционни променливи на класове

User.attributes      #=> [:name]
Picture.attributes   #=> [:date]
DataModel.attributes #=> nil

Класови променливи

class Person
  @@count = 0

  def initialize
    @@count += 1
  end

  def self.how_many
    @@count
  end
end

Person.new
Person.new
Person.how_many # => 2

Клас променливи

семантиката

Клас променливи

class B
  @@foo = 1
  def self.foo() @@foo end
  def self.hmm() @@bar end
end

class D < B
  @@bar = 2
  def self.bar() @@bar end
  def self.change() @@foo = 3; @@bar = 4; end
end

[B.foo, D.foo, D.bar] # => [1, 1, 2]
B.hmm                 # => error: NameError
D.change
[B.foo, D.foo, D.bar] # => [3, 3, 4]
B.hmm                 # => error: NameError
D.hmm                 # => error: NameError

@@ vs @

Вътрешно представяне

def initialize(attributes)
  attributes.each do |attribute, value|
    send("#{attribute}=", value)
  end
end

def to_hash
  attributes.map do |attribute|
    [attribute, send(attribute)]
  end.to_h
end

def create
  self.class.data_store.create(to_hash)
end

Вътрешно представяне

има, разбира се

def initialize(attributes)
  @attributes = attributes
end

def create
  self.class.data_store.create(@attributes)
end

Ама attr_accessor иска така

attr_accessor(attribute)
# vs
define_method("#{attribute}=") { |value| @attributes[attribute] = value }
define_method(attribute) { @attributes[attribute] }

Въпроси?

Продължаваме с регулярните изрази...

Бърз преговор

класове от символи

Бърз преговор

котви

Бърз преговор

повторители

Бърз преговор

алчност

Бърз преговор

групи

Backtracking

/".*"/.match '"Quoted"' # => #<MatchData "\"Quoted\"">

Частта от шаблона .* хваща Quoted", тъй като е алчна. Това води до невъзможност да се намери съвпадение и алгоритъмът backtrack-ва -- връща се една стъпка/символ назад.

Атомарни (неделими) групи

/"(?>.*)"/.match('"Quote"') # => nil

Рекурсивни групи

/(\w+), \1/.match    'testing, twice'   # => nil
/(\w+), \g<1>/.match 'testing, twice'   # => #<MatchData "testing, twice" 1:"twice">

Рекурсивни групи

втора част

Да валидирате изрази от следния тип за правилно отворени/затворени скоби:
  • (car (car (car ...)))
  • Например: (car (car (car (car list))))
  • Целта е израз, чийто резултат да може да се ползва в условен оператор (true/false-еквивалент)
  • Можете да ползвате произволни методи от класа Regexp
  • И регулярен израз, разбира се

Примерно решение

с рекурсивни групи

validator = /^(\(car (\g<1>*|\w*)\))$/

valid   = '(car (car (car (car list))))'
invalid = '(car (car (car list))'

validator.match(valid)   ? true : false # => true
validator.match(invalid) ? true : false # => false

MOAR, MOAR!!!!111!

Проверете дали нещо е валиден математически израз
  • Произволно цяло число 1337
  • Променлива (малка латинска буква) x
  • Знак пред валиден израз (+, -) -33 + 22 * -y
  • Операция между валидни изрази (+, -, *, /) x + y - 21 / 3
  • Скоби, ограждащи валидни изрази -x * (y + -5 * (7 - 13)) / 44 - 9000

Примерно решение

so simple, right?

validator = /^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/

valid   = '-(3 + (x * (7 / y))) * (44 * y - z / 22)'
invalid = '((33 - 7) * x'

validator.match(valid)   ? true : false
validator.match(invalid) ? true : false

Примерно решение

nope... fail!

/^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/# ~> -:1: never ending recursion: /^([-+]?(\d+|[a-z]|\(\g<1>\)|\g<1> [-+*\/] \g<1>))$/

Примерно решение

с рекурсивни групи

validator = /^([-+]?(\d+|[a-z]|\(\g<1>\))( [-+*\/] \g<1>)?)$/

valid   = '-(3 + (x * (7 / y))) * (44 * y - z / 22)'
invalid = '((33 - 7) * x'

validator.match(valid)   ? true : false # => true
validator.match(invalid) ? true : false # => false

Look-ahead и look-behind

/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favours the <b>bold</b>") # => #<MatchData "bold">

Пример

Сменете * на % ако тя не е екранирана (escape-ната)
  • foo* => foo%
  • foo\* => foo\*
  • foo\\* => foo\\%
  • *\\** => %\\*%

Първи начин

"*\\**".gsub(/((?<!\\)(?:\\\\)*)\*/, '\1%') # => "%\\*%"

Втори начин

"*\\**".gsub(/\G([^*\\]*(?:\\.[^*\\]*)*)\*/, '\1%') # => "%\\*%"

Въпроси