Ruby'de "Ters Aralık" üzerinde yinelemememizin bir nedeni var mı?


104

Bir Aralık kullanarak geriye doğru yinelemeye çalıştım ve each:

(4..0).each do |i|
  puts i
end
==> 4..0

Yineleme 0..4, sayıları yazar. Diğer taraftan Menzil r = 4..0ok gibi görünüyor, r.first == 4, r.last == 0.

Yukarıdaki yapının beklenen sonucu vermemesi bana garip geliyor. Bunun sebebi nedir? Bu davranışın makul olduğu durumlar nelerdir?


Açıkça desteklenmeyen bu yinelemenin nasıl gerçekleştirileceğiyle ilgilenmiyorum, bunun yerine neden 4..0 aralığının kendisini döndürdüğüyle ilgileniyorum. Dil tasarımcılarının niyeti neydi? Neden, hangi durumlarda iyidir? Diğer yakut yapılarında da benzer davranışlar gördüm ve kullanışlı olduğunda hala temiz değil.
fifigyuri

1
Aralığın kendisi kural olarak döndürülür. Yana .eachdeyimi şey değiştirmedi, geri dönüşü olmayan için "sonucunu" Orada hesaplanır. Böyle bir durumda, Ruby tipik olarak başarı ve nilhata durumunda orijinal nesneyi döndürür . Bu, bunun gibi ifadeleri bir ififadede koşullar olarak kullanmanıza izin verir .
bta

Yanıtlar:


99

Aralık sadece şudur: içeriği ile değil, başlangıcı ve bitişi ile tanımlanan bir şey. Bir aralıkta "yinelemek" genel bir durumda gerçekten mantıklı değildir. Örneğin, iki tarih tarafından üretilen aralık üzerinde nasıl "yineleme" yapacağınızı düşünün. Gün geçtikçe tekrar eder misin? ayda? yıla göre? haftalık İyi tanımlanmış değil. IMO, ileri menzillere izin verildiği gerçeği, yalnızca uygun bir yöntem olarak görülmelidir.

Böyle bir aralıkta geriye doğru yineleme yapmak istiyorsanız, her zaman kullanabilirsiniz downto:

$ r = 10..6
=> 10..6

$ (r.first).downto(r.last).each { |i| puts i }
10
9
8
7
6

İşte biraz daha düşünceler hem yinelemeyi izin ve tutarlı ters aralıkları ile uğraşmak zor olduğunu neden diğerlerinden.


10
Sezgisel olarak 1'den 100'e veya 100'den 1'e kadar bir aralıkta yinelemenin 1. adımı kullanmak anlamına geldiğini düşünüyorum. Birisi farklı bir adım isterse, varsayılanı değiştirir. Benzer şekilde, benim için (en azından) 1 Ocak'tan 16 Ağustos'a kadar yinelemek, günler adım atmak anlamına geliyor. Genelde üzerinde anlaşabileceğimiz bir şey olduğunu düşünüyorum, çünkü sezgisel olarak bunu böyle kastediyoruz. Cevabınız için teşekkürler, verdiğiniz bağlantı da faydalı oldu.
fifigyuri

3
Hala birçok aralık için "sezgisel" yinelemeleri tanımlamanın tutarlı bir şekilde yapılmasının zor olduğunu düşünüyorum ve bu şekilde sezgisel olarak tarihler arasında yinelemenin 1 güne eşit bir adımı ima ettiğini kabul etmiyorum - sonuçta bir günün kendisi zaten bir aralıktır zaman (gece yarısından gece yarısına kadar). Örneğin, "1 Ocak - 18 Ağustos" un (tam olarak 20 hafta) günler yerine haftaların yinelenmesi anlamına gelmediğini kim söyleyebilir? Neden saat, dakika veya saniye olarak yinelemiyorsunuz?
John Feminella

8
.eachGereksiz olduğunu, 5.downto(1) { |n| puts n }cezası çalışır. Ayrıca, tüm bu ilk ve son şeyler yerine, sadece yapın (6..10).reverse_each.
mk12

@ Mk12:% 100 katılıyorum, sadece yeni Rubyistler uğruna süper açık sözlü olmaya çalışıyordum. Belki de bu çok kafa karıştırıcıdır.
John Feminella

Bir forma yıl eklemeye çalışırken şunu kullandım:= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
Eric Norcross


18

Ruby'de bir aralık üzerinde yinelemek, aralıktaki ilk nesnede yöntemi eachçağırır succ.

$ 4.succ
=> 5

Ve 5 aralığın dışında.

Bu hack ile ters yinelemeyi simüle edebilirsiniz:

(-4..0).each { |n| puts n.abs }

John, 0'ı kaplarsa bunun işe yaramayacağına işaret etti. Bu:

>> (-2..2).each { |n| puts -n }
2
1
0
-1
-2
=> -2..2

Bunların herhangi birini gerçekten sevdiğimi söyleyemem çünkü niyeti belirsizleştiriyorlar.


2
Hayır ama .abs yerine -1 ile çarparak yapabilirsiniz.
Jonas Elfström

12

"Programlama Ruby" kitabına göre, Range nesnesi aralığın iki uç noktasını saklar ve .succüyeyi ara değerleri oluşturmak için kullanır . Aralığınızda ne tür veri kullandığınıza bağlı olarak, her zaman bir alt sınıf oluşturabilir Integerve .succbir ters yineleyici gibi davranması için üyeyi yeniden tanımlayabilirsiniz (muhtemelen siz de yeniden tanımlamak isteyeceksiniz .next).

Ayrıca, Aralık kullanmadan da aradığınız sonuçları elde edebilirsiniz. Bunu dene:

4.step(0, -1) do |i|
    puts i
end

Bu, -1'lik adımlarla 4'ten 0'a adım atacaktır. Ancak, bunun Tamsayı bağımsız değişkenleri dışında herhangi bir şey için işe yarayıp yaramayacağını bilmiyorum.



5

Bir fordöngü bile kullanabilirsiniz :

for n in 4.downto(0) do
  print n
end

hangi yazdırır:

4
3
2
1
0

3

liste o kadar büyük değilse. bence [*0..4].reverse.each { |i| puts i } en basit yol.


2
IMO, genellikle büyük olduğunu varsaymak iyidir. Genel olarak takip edilmesi gereken doğru inanç ve alışkanlık olduğunu düşünüyorum. Ve şeytan asla uyumadığı için, bir dizi üzerinde nerede yinelediğimi hatırladığıma kendime güvenmiyorum. Ama haklısınız, eğer 0 ve 4 sabitimiz varsa, dizi üzerinde yinelemek herhangi bir soruna yol açmayabilir.
fifigyuri

1

Bta dediği gibi, nedeni olduğunu Range#eachgönderir succo sonucuna kendi başlarına succve böylece sonuç bitiş değerinden büyük olana kadar aramaya. Arayarak 4'ten 0'a ulaşamazsınız succve aslında zaten sondan daha büyük başlıyorsunuz.


1

Reverse Range üzerinden yinelemenin nasıl gerçekleştirileceğine dair bir olasılık daha ekliyorum. Kullanmıyorum ama bu bir olasılık. Yakut çekirdek nesnelerine yama yapmak biraz risklidir.

class Range

  def each(&block)
    direction = (first<=last ? 1 : -1)
    i = first
    not_reached_the_end = if first<=last
                            lambda {|i| i<=last}
                          else
                            lambda {|i| i>=last}
                          end
    while not_reached_the_end.call(i)
      yield i
      i += direction
    end
  end
end

0

Bu tembel kullanım durumum için çalıştı

(-999999..0).lazy.map{|x| -x}.first(3)
#=> [999999, 999998, 999997]

0

OP yazdı

Yukarıdaki yapının beklenen sonucu vermemesi bana garip geliyor. Bunun sebebi nedir? Bu davranışın makul olduğu durumlar nelerdir?

"Yapılabilir mi?" ama aslında sorulan soruya gelmeden önce sorulmayan soruyu cevaplamak için:

$ irb
2.1.5 :001 > (0..4)
 => 0..4
2.1.5 :002 > (0..4).each { |i| puts i }
0
1
2
3
4
 => 0..4
2.1.5 :003 > (4..0).each { |i| puts i }
 => 4..0
2.1.5 :007 > (0..4).reverse_each { |i| puts i }
4
3
2
1
0
 => 0..4
2.1.5 :009 > 4.downto(0).each { |i| puts i }
4
3
2
1
0
 => 4

Reverse_each'in tüm bir diziyi oluşturduğu iddia edildiğinden, aşağıya doğru açıkça daha verimli olacaktır. Bir dil tasarımcısının bu tür şeyleri uygulamayı bile düşünebilmesi, sorulduğu gibi asıl sorunun cevabına bağlanır.

Soruyu gerçekten sorulduğu gibi cevaplamak için ...

Bunun nedeni, Ruby'nin sonsuz derecede şaşırtıcı bir dil olmasıdır. Bazı sürprizler hoştur, ancak tamamen bozulmuş birçok davranış vardır. Aşağıdaki örneklerden bazıları daha yeni sürümlerle düzeltilse bile, başka pek çok örnek var ve bunlar orijinal tasarımın zihniyetine yönelik suçlamalar olarak kalıyor:

nil.to_s
   .to_s
   .inspect

"" ile sonuçlanır ancak

nil.to_s
#  .to_s   # Don't want this one for now
   .inspect

sonuçlanır

 syntax error, unexpected '.', expecting end-of-input
 .inspect
 ^

Muhtemelen << ve dizilere ekleme için aynı olmasını beklersiniz, ancak

a = []
a << *[:A, :B]    # is illegal but
a.push *[:A, :B]  # isn't.

Muhtemelen 'grep'in Unix komut satırı eşdeğeri gibi davranmasını beklersiniz, ancak ismine rağmen === eşleşmeyen = ~ ile eşleşmez.

$ echo foo | grep .
foo
$ ruby -le 'p ["foo"].grep(".")'
[]

Çeşitli yöntemler beklenmedik şekilde birbirlerinin takma adlarıdır, bu nedenle aynı şey için birden fazla ad öğrenmeniz gerekir - örneğin findve detect- çoğu geliştiriciyi sevseniz ve yalnızca birini veya diğerini kullansanız bile. Hemen hemen aynı için de geçerlidir size, countve lengthfarklı şekilde tanımlamak ya da hiç bir ya da iki tanımlamaz sınıfları haricinde.

Birisi başka bir şey uygulamadıysa - tapekrandaki bir şeye basmak için çeşitli otomasyon kitaplıklarında çekirdek yöntem yeniden tanımlanmış gibi. Neler olup bittiğini öğrenmede iyi şanslar, özellikle de başka bir modülün gerektirdiği bazı modül, belgelenmemiş bir şey yapmak için başka bir modül başlattıysa.

Ortam değişkeni nesnesi, ENV 'birleştirmeyi' desteklemiyor, bu yüzden yazmanız gerekiyor

 ENV.to_h.merge('a': '1')

Bonus olarak, ne olması gerektiği konusunda fikrinizi değiştirirseniz, kendinizin veya bir başkasının sabitlerini yeniden tanımlayabilirsiniz.


Bu soruya hiçbir şekilde, biçimde veya biçimde yanıt vermez. Yazarın Ruby hakkında sevmediği şeyler hakkında söylenenlerden başka bir şey değil.
Jörg W Mittag

Gerçekte sorulan cevaba ek olarak, sorulmayan soruyu cevaplamak için güncellendi. rant: fiil 1. Kızgın, ateşli bir şekilde uzun uzun konuşun veya bağırın. Asıl cevap kızgın ya da heyecanlı değildi: örneklerle düşünülmüş bir cevaptı.
android.weasel

@ JörgWMittag Orijinal soru şunları da içeriyor: Yukarıdaki yapının beklenen sonucu vermemesi bana garip geliyor. Bunun sebebi nedir? Bu davranışın makul olduğu durumlar nelerdir? yani kod çözümlerinin değil nedenlerin peşinde.
android.weasel

Yine, grepboş bir aralık üzerinde yinelemenin işlemsiz olduğu gerçeğiyle ilgili herhangi bir şekilde davranışının nasıl olduğunu göremiyorum . Boş bir aralık üzerinde yinelemenin işlemsiz olduğu gerçeğinin nasıl bir şekilde "sonsuz derecede şaşırtıcı" ve "düpedüz kırılmış" olduğunu görmüyorum.
Jörg W Mittag

Çünkü 4..0 aralığı açık bir şekilde [4, 3, 2, 1, 0] niyetine sahiptir, ancak şaşırtıcı bir şekilde bir uyarı bile vermez. OP'yi şaşırttı ve beni şaşırttı ve şüphesiz birçok insanı şaşırttı. Şaşırtıcı davranışların diğer örneklerini listeledim. İstersen daha fazla alıntı yapabilirim. Bir şey belli bir miktar şaşırtıcı davranıştan fazlasını sergilediğinde, 'kırılmış' alanına doğru sürüklenmeye başlar. Biraz, sabitlerin üzerine yazıldığında, özellikle de yöntemler olmadığında bir uyarı oluşturmasına benzer.
android.weasel

0

Bana gelince, en basit yol:

[*0..9].reverse

Numaralandırma için yinelemenin başka bir yolu:

(1..3).reverse_each{|v| p v}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.