Ruby / Rails의 루프에서 매직 첫 번째와 마지막 표시기?
Ruby / Rails는 기본적인 일을 위해 설탕에 관해서는 멋진 일을 많이합니다. 그리고 누군가 도우미 나 비슷한 일을 한 적이 있는지 궁금했던 매우 일반적인 시나리오가 있다고 생각합니다.
a = Array.new(5, 1)
a.each_with_index do |x, i|
if i == 0
print x+1
elsif i == (a.length - 1)
print x*10
else
print x
end
end
추악함을 용서하십시오. 그러나 이것은 누군가가 원하는 것을 얻습니다 ... 루비의 처음과 마지막에 무언가를하는 루비 방법이 있습니까?
[편집] 이상적으로 이것이 매개 변수 (배열 인스턴스, 모든 요소 함수, 첫 번째 요소 함수, 마지막 요소 함수)가있는 배열의 확장이라고 생각합니다.하지만 다른 생각에 열려 있습니다.
원하는 경우 첫 번째와 마지막 요소를 가져 와서 다르게 처리 할 수 있습니다.
first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
첫 번째 및 마지막 반복의 코드가 다른 반복의 코드와 공통점이없는 경우 다음을 수행 할 수도 있습니다.
do_something( a.first )
a[1..-2].each do |x|
do_something_else( x )
end
do_something_else_else( a.last )
다른 경우에 공통된 코드가 있다면 당신의 방식은 괜찮습니다.
할 수 있다면 어떨까요?
%w(a b c d).each.with_position do |e, position|
p [e, position] # => ["a", :first]
# => ["b", :middle]
# => ["c", :middle]
# => ["d", :last]
end
아니면 이거?
%w(a, b, c, d).each_with_index.with_position do |(e, index), position|
p [e, index, position] # => ["a,", 0, :first]
# => ["b,", 1, :middle]
# => ["c,", 2, :middle]
# => ["d", 3, :last]
end
MRI> = 1.8.7에서는 다음 원숭이 패치 만 있으면됩니다.
class Enumerable::Enumerator
def with_position(&block)
state = :init
e = nil
begin
e_last = e
e = self.next
case state
when :init
state = :first
when :first
block.call(e_last, :first)
state = :middle
when :middle
block.call(e_last, :middle)
end
rescue StopIteration
case state
when :first
block.call(e_last, :first)
when :middle
block.call(e_last, :last)
end
return
end while true
end
end
한 번의 반복을 미리보아야하기 때문에 약간의 상태 엔진이 있습니다.
트릭은 each_with_index, & c입니다. 블록이 주어지지 않으면 열거자를 반환합니다. Enumerator는 Enumerable이하는 모든 작업을 수행합니다. 그러나 우리에게 중요한 것은 우리가 원숭이 패치 Enumerator를 사용하여 반복하는 한 가지 방법을 더 추가하여 기존 반복을 "래핑"할 수 있다는 것입니다.
또는 아주 작은 도메인 특정 언어 :
a = [1, 2, 3, 4]
FirstMiddleLast.iterate(a) do
first do |e|
p [e, 'first']
end
middle do |e|
p [e, 'middle']
end
last do |e|
p [e, 'last']
end
end
# => [1, "first"]
# => [2, "middle"]
# => [3, "middle"]
# => [4, "last"]
그리고 그것을 만드는 코드 :
class FirstMiddleLast
def self.iterate(array, &block)
fml = FirstMiddleLast.new(array)
fml.instance_eval(&block)
fml.iterate
end
attr_reader :first, :middle, :last
def initialize(array)
@array = array
end
def first(&block)
@first = block
end
def middle(&block)
@middle = block
end
def last(&block)
@last = block
end
def iterate
@first.call(@array.first) unless @array.empty?
if @array.size > 1
@array[1..-2].each do |e|
@middle.call(e)
end
@last.call(@array.last)
end
end
end
저는 "루비 함수에 여러 블록을 전달할 수만 있다면이 질문에 대한 매끄럽고 쉬운 해결책을 얻을 수있을 것"이라고 생각하기 시작했습니다. 그런 다음 DSL이 거의 여러 블록을 통과하는 것과 같은 작은 트릭을 수행한다는 것을 깨달았습니다.
많은 사람들이 지적했듯이 each_with_index
이것이 핵심 인 것 같습니다. 내가 좋아하는 코드 블록이 있습니다.
array.each_with_index do |item,index|
if index == 0
# first item
elsif index == array.length-1
# last item
else
# middle items
end
# all items
end
또는
array.each_with_index do |item,index|
if index == 0
# first item
end
# all items
if index == array.length-1
# last item
end
end
또는 배열 확장으로
class Array
def each_with_position
array.each_with_index do |item,index|
if index == 0
yield item, :first
elsif index == array.length-1
yield item, :last
else
yield item, :middle
end
end
end
def each_with_index_and_position
array.each_with_index do |item,index|
if index == 0
yield item, index, :first
elsif index == array.length-1
yield item, index, :last
else
yield item, index, :middle
end
end
end
def each_with_position_and_index
array.each_with_index do |item,index|
if index == 0
yield item, :first, index
elsif index == array.length-1
yield item, :last, index
else
yield item, :middle, index
end
end
end
end
상용구를 추가하려면 다음과 같이 배열 클래스에 추가 할 수 있습니다.
class Array
def each_fl
each_with_index do |x,i|
yield [i==0 ? :first : (i==length-1 ? :last : :inner), x]
end
end
end
그런 다음 필요한 곳에서 다음 구문을 얻습니다.
[1,2,3,4].each_fl do |t,x|
case t
when :first
puts "first: #{x}"
when :last
puts "last: #{x}"
else
puts "otherwise: #{x}"
end
end
다음 출력의 경우 :
first: 1
otherwise: 2
otherwise: 3
last: 4
Ruby에는 "(처음 | 마지막)"구문이 없습니다. 그러나 간결함을 찾고 있다면 다음과 같이 할 수 있습니다.
a.each_with_index do |x, i|
print (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1)
end
결과는 다음과 같습니다.
irb(main):001:0> a = Array.new(5,1)
=> [1, 1, 1, 1, 1]
irb(main):002:0> a.each_with_index do |x,i|
irb(main):003:1* puts (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1)
irb(main):004:1> end
2
1
1
1
10
흥미로운 질문과 제가 조금 생각한 질문도 있습니다.
3 개의 다른 블록 / 프로 시저 / 그들이 호출되는 모든 것을 생성 한 다음 올바른 블록 / 프로 시저 / 무엇을 호출하는 메서드를 생성해야한다고 생각합니다. (모호해서 죄송합니다. 저는 아직 블랙 벨트 메타 프로그래머가 아닙니다.) [ 편집 :하지만 맨 아래에있는 사람에게서 복사했습니다.)
class FancyArray
def initialize(array)
@boring_array = array
@first_code = nil
@main_code = nil
@last_code = nil
end
def set_first_code(&code)
@first_code = code
end
def set_main_code(&code)
@main_code = code
end
def set_last_code(&code)
@last_code = code
end
def run_fancy_loop
@boring_array.each_with_index do |item, i|
case i
when 0 then @first_code.call(item)
when @boring_array.size - 1 then @last_code.call(item)
else @main_code.call(item)
end
end
end
end
fancy_array = FancyArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"])
fancy_array.set_first_code {|item| puts "#{item} came first in ski jumping at the 1988 Winter Olympics"}
fancy_array.set_main_code {|item| puts "#{item} did not come first or last in ski jumping at the 1988 Winter Olympics"}
fancy_array.set_last_code {|item| puts "#{item} came last in ski jumping at the 1988 Winter Olympics"}
fancy_array.run_fancy_loop
생산하다
Matti Nykanen came first in ski jumping at the 1988 Winter Olympics
Erik Johnsen did not come first or last in ski jumping at the 1988 Winter Olympics
Michael Edwards came last in ski jumping at the 1988 Winter Olympics
편집 : 관련 질문에 대한 Svante의 답변 (molf의 제안 포함)은 여러 코드 블록을 단일 메서드로 전달하는 방법을 보여줍니다.
class FancierArray < Array
def each_with_first_last(first_code, main_code, last_code)
each_with_index do |item, i|
case i
when 0 then first_code.call(item)
when size - 1 then last_code.call(item)
else main_code.call(item)
end
end
end
end
fancier_array = FancierArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"])
fancier_array.each_with_first_last(
lambda {|person| puts "#{person} came first in ski jumping at the 1988 Winter Olympics"},
lambda {|person| puts "#{person} did not come first or last in ski jumping at the 1988 Winter Olympics"},
lambda {|person| puts "#{person} came last in ski jumping at the 1988 Winter Olympics"})
나는 때때로이 기능이 필요했기 때문에 그 목적을 위해 약간의 수업을 만들었다.
최신 버전 : https://gist.github.com/3823837
견본:
("a".."m").to_a.each_pos do |e|
puts "Char\tfirst?\tlast?\tprev\tnext\twrapped?\tindex\tposition" if e.first?
print "#{e.item}\t"
print "#{e.first?}\t"
print "#{e.last?}\t"
print "#{e.prev}\t"
print "#{e.next}\t"
print "#{e.wrapped?}\t\t"
print "#{e.index}\t"
puts "#{e.position}\t"
end
# Char first? last? prev next wrapped? index position
# a true false b false 0 1
# b false false a c true 1 2
# c false false b d true 2 3
# d false false c e true 3 4
# e false false d f true 4 5
# f false false e g true 5 6
# g false false f h true 6 7
# h false false g i true 7 8
# i false false h j true 8 9
# j false false i k true 9 10
# k false false j l true 10 11
# l false false k m true 11 12
# m false true l false 12 13
{
a: "0",
b: "1",
c: "2",
d: "3",
e: "4",
f: "5",
g: "6",
h: "7",
i: "8",
j: "9",
k: "10",
l: "11",
m: "12",
}.each_pos do |(k, v), e|
puts "KV\tChar\t\tfirst?\tlast?\tprev\t\tnext\t\twrapped?\tindex\tposition" if e.first?
print "#{k} => #{v}\t"
print "#{e.item}\t"
print "#{e.first?}\t"
print "#{e.last?}\t"
print "#{e.prev || "\t"}\t"
print "#{e.next || "\t"}\t"
print "#{e.wrapped?}\t\t"
print "#{e.index}\t"
puts "#{e.position}\t"
end
# KV Char first? last? prev next wrapped? index position
# a => 0 [:a, "0"] true false [:b, "1"] false 0 1
# b => 1 [:b, "1"] false false [:a, "0"] [:c, "2"] true 1 2
# c => 2 [:c, "2"] false false [:b, "1"] [:d, "3"] true 2 3
# d => 3 [:d, "3"] false false [:c, "2"] [:e, "4"] true 3 4
# e => 4 [:e, "4"] false false [:d, "3"] [:f, "5"] true 4 5
# f => 5 [:f, "5"] false false [:e, "4"] [:g, "6"] true 5 6
# g => 6 [:g, "6"] false false [:f, "5"] [:h, "7"] true 6 7
# h => 7 [:h, "7"] false false [:g, "6"] [:i, "8"] true 7 8
# i => 8 [:i, "8"] false false [:h, "7"] [:j, "9"] true 8 9
# j => 9 [:j, "9"] false false [:i, "8"] [:k, "10"] true 9 10
# k => 10 [:k, "10"] false false [:j, "9"] [:l, "11"] true 10 11
# l => 11 [:l, "11"] false false [:k, "10"] [:m, "12"] true 11 12
# m => 12 [:m, "12"] false true [:l, "11"] false 12 13
실제 수업 :
module Enumerable
# your each_with_position method
def each_pos &block
EachWithPosition.each(self, &block)
end
end
class EachWithPosition
attr_reader :index
class << self
def each *a, &b
handler = self.new(*a, :each, &b)
end
end
def initialize collection, method, &block
@index = 0
@item, @prev, @next = nil
@collection = collection
@callback = block
self.send(method)
end
def count
@collection.count
end
alias_method :length, :count
alias_method :size, :count
def rest
count - position
end
def first?
@index == 0
end
def last?
@index == (count - 1)
end
def wrapped?
!first? && !last?
end
alias_method :inner?, :wrapped?
def position
@index + 1
end
def prev
@prev
end
def next
@next
end
def current
@item
end
alias_method :item, :current
alias_method :value, :current
def call
if @callback.arity == 1
@callback.call(self)
else
@callback.call(@item, self)
end
end
def each
@collection.each_cons(2) do |e, n|
@prev = @item
@item = e
@next = n
self.call
@index += 1
# fix cons slice behaviour
if last?
@prev, @item, @next = @item, @next, nil
self.call
@index += 1
end
end
end
end
키스
arr.each.with_index do |obj, index|
p 'first' if index == 0
p 'last' if index == arr.count-1
end
"마지막"작업이 중간에있는 것보다 먼저 발생하는 것을 신경 쓰지 않는다면이 원숭이 패치는 다음과 같습니다.
class Array
def for_first
return self if empty?
yield(first)
self[1..-1]
end
def for_last
return self if empty?
yield(last)
self[0...-1]
end
end
다음을 허용합니다.
%w(a b c d).for_first do |e|
p ['first', e]
end.for_last do |e|
p ['last', e]
end.each do |e|
p ['middle', e]
end
# => ["first", "a"]
# => ["last", "d"]
# => ["middle", "b"]
# => ["middle", "c"]
나는 저항 할 수 없었습니다 :) 이것은 여기에있는 다른 대부분의 답변보다 훨씬 느리지 않아야한다고 생각하지만 성능을 위해 조정되지 않았습니다. 설탕에 관한 모든 것!
class Array
class EachDSL
attr_accessor :idx, :max
def initialize arr
self.max = arr.size
end
def pos
idx + 1
end
def inside? range
range.include? pos
end
def nth? i
pos == i
end
def first?
nth? 1
end
def middle?
not first? and not last?
end
def last?
nth? max
end
def inside range
yield if inside? range
end
def nth i
yield if nth? i
end
def first
yield if first?
end
def middle
yield if middle?
end
def last
yield if last?
end
end
def each2 &block
dsl = EachDSL.new self
each_with_index do |x,i|
dsl.idx = i
dsl.instance_exec x, &block
end
end
end
예 1 :
[1,2,3,4,5].each2 do |x|
puts "#{x} is first" if first?
puts "#{x} is third" if nth? 3
puts "#{x} is middle" if middle?
puts "#{x} is last" if last?
puts
end
# 1 is first
#
# 2 is middle
#
# 3 is third
# 3 is middle
#
# 4 is middle
#
# 5 is last
예 2 :
%w{some short simple words}.each2 do |x|
first do
puts "#{x} is first"
end
inside 2..3 do
puts "#{x} is second or third"
end
middle do
puts "#{x} is middle"
end
last do
puts "#{x} is last"
end
end
# some is first
# short is second or third
# short is middle
# simple is second or third
# simple is middle
# words is last
배열을 각 범위 내의 요소가 다르게 작동해야하는 범위로 분할합니다. 이렇게 생성 된 각 범위를 블록에 매핑합니다.
class PartitionEnumerator
include RangeMaker
def initialize(array)
@array = array
@handlers = {}
end
def add(range, handler)
@handlers[range] = handler
end
def iterate
@handlers.each_pair do |range, handler|
@array[range].each { |value| puts handler.call(value) }
end
end
end
손으로 범위를 만들 수 있지만 아래의 도우미를 사용하면 더 쉽게 만들 수 있습니다.
module RangeMaker
def create_range(s)
last_index = @array.size - 1
indexes = (0..last_index)
return (indexes.first..indexes.first) if s == :first
return (indexes.second..indexes.second_last) if s == :middle
return (indexes.last..indexes.last) if s == :last
end
end
class Range
def second
self.first + 1
end
def second_last
self.last - 1
end
end
용법:
a = [1, 2, 3, 4, 5, 6]
e = PartitionEnumerator.new(a)
e.add(e.create_range(:first), Proc.new { |x| x + 1 } )
e.add(e.create_range(:middle), Proc.new { |x| x * 10 } )
e.add(e.create_range(:last), Proc.new { |x| x } )
e.iterate
여기에 꽤 가까운 해킹이 많이 있지만, 모두 고정 된 크기를 가지고 있고 반복자가 아닌 주어진 반복기에 크게 의존합니다. 반복 된 첫 번째 / 마지막 요소를 알기 위해 반복하면서 이전 요소를 저장하는 것도 제안하고 싶습니다.
previous = {}
elements.each do |element|
unless previous.has_key?(:element)
# will only execute the first time
end
# normal each block here
previous[:element] = element
end
# the last element will be stored in previous[:element]
배열의 항목이 고유 한 경우 (이 경우와 달리) 다음을 수행 할 수 있습니다.
a = [1,2,3,4,5]
a.each_with_index do |x, i|
if x == a.first
print x+1
elsif x == a.last
print x*10
else
print x
end
end
때로는 for 루프가 최선의 선택입니다.
if(array.count > 0)
first= array[0]
#... do something with the first
cx = array.count -2 #so we skip the last record on a 0 based array
for x in 1..cx
middle = array[x]
#... do something to the middle
end
last = array[array.count-1]
#... do something with the last item.
end
I know this question was answered, but this method has no side effects, and doesn't check if the 13th, 14th, 15th.. 10thousandth, 10,001th... record is the first record, or the last.
Previous answers would have failed the assignment in any data structures class.
ReferenceURL : https://stackoverflow.com/questions/2241684/magic-first-and-last-indicator-in-a-loop-in-ruby-rails
'IT박스' 카테고리의 다른 글
웹 페이지에서 .svg 파일을 사용하는 방법은 무엇입니까? (0) | 2021.01.08 |
---|---|
Subversion : 트렁크에 병합되지 않은 모든 개정을 찾는 방법은 무엇입니까? (0) | 2021.01.08 |
Facebook "좋아요"버튼 콜백 (0) | 2021.01.08 |
CSS 성능 프로파일 러? (0) | 2021.01.08 |
Ruby on Rails 3.1 및 jQuery UI 이미지 (0) | 2021.01.08 |