Ruby - bir dizi değilse, değişkeni zarif bir şekilde diziye dönüştürür


120

Bir dizi, tek bir eleman veya nil verildiğinde, bir dizi elde edin - son ikisi sırasıyla tek eleman dizisi ve boş bir dizidir.

Yanlışlıkla Ruby'nin şu şekilde çalışacağını düşündüm:

[1,2,3].to_a  #= [1,2,3]     # Already an array, so no change
1.to_a        #= [1]         # Creates an array and adds element
nil.to_a      #= []          # Creates empty array

Ama gerçekten aldığınız şey:

[1,2,3].to_a  #= [1,2,3]         # Hooray
1.to_a        #= NoMethodError   # Do not want
nil.to_a      #= []              # Hooray

Bu yüzden bunu çözmek için ya başka bir yöntem kullanmam gerekiyor ya da kullanmayı düşündüğüm tüm sınıfların to_a yöntemini değiştirerek meta program yapabilirim - ki bu benim için bir seçenek değil.

Yani bir Yöntem:

result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))

Sorun şu ki, biraz karışıklık. Bunu yapmanın zarif bir yolu var mı? (Bu sorunu çözmek için Ruby'ye benzeyen yol buysa şaşırırdım)


Bunun hangi uygulamaları var? Neden bir diziye dönüştürülsün ki?

Rails'in ActiveRecord'unda, demek, user.postsbir dizi gönderi, tek bir gönderi veya sıfır döndürür. Bunun sonuçları üzerinde çalışan metotları yazarken, metodun sıfır, bir veya birçok eleman içeren bir dizi alacağını varsaymak en kolayıdır. Örnek yöntem:

current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}

2
user.postsasla tek bir gönderi döndürmemelidir. En azından onu hiç görmedim.
Sergio Tulentsev

1
Sanırım ilk iki kod bloğunuzda ==bunun yerine demek istiyorsunuz =, değil mi?
Patrick Oscity


3
Btw, [1,2,3].to_ayok değil dönmek [[1,2,3]]! Dönüyor [1,2,3].
Patrick Oscity

Teşekkürler kürek, soruyu güncelleyecek ...
kendime

Yanıtlar:


153

[*foo]ya Array(foo)da çoğu zaman işe yarayacaktır, ancak bir hash gibi bazı durumlarda, işleri berbat eder.

Array([1, 2, 3])    # => [1, 2, 3]
Array(1)            # => [1]
Array(nil)          # => []
Array({a: 1, b: 2}) # => [[:a, 1], [:b, 2]]

[*[1, 2, 3]]    # => [1, 2, 3]
[*1]            # => [1]
[*nil]          # => []
[*{a: 1, b: 2}] # => [[:a, 1], [:b, 2]]

Bunun bir hash için bile işe yaradığını düşünebilmemin tek yolu bir yöntem tanımlamaktır.

class Object; def ensure_array; [self] end end
class Array; def ensure_array; to_a end end
class NilClass; def ensure_array; to_a end end

[1, 2, 3].ensure_array    # => [1, 2, 3]
1.ensure_array            # => [1]
nil.ensure_array          # => []
{a: 1, b: 2}.ensure_array # => [{a: 1, b: 2}]

2
yerine ensure_arrayuzatto_a
Dan Grahn

9
@screenmutt Bu, orijinal kullanımına dayanan yöntemleri etkileyecektir to_a. Örneğin, {a: 1, b: 2}.each ...farklı çalışırdı.
sawa

1
Bu sözdizimini açıklayabilir misiniz? Ruby'nin uzun yıllarında bu türden bir çağrıya hiç rastlamadım. Bir sınıf adı üzerindeki parantezler ne işe yarar? Bunu belgelerde bulamıyorum.
mastaBlasta

1
@mastaBlasta Array (arg), to_ary'yi, ardından argümanda to_a'yı çağırarak yeni bir dizi oluşturmaya çalışır. Bu, resmi yakut belgelerinde belgelenmiştir. Avdi'nin "Kendine Güvenen Ruby" kitabından öğrendim.
mambo

2
@mambo Sorumu gönderdikten sonra bir noktada cevabı buldum. İşin zor yanı, Array sınıfıyla ilgisi olmamasıydı, ancak Kernel modülünde bir yöntemdi. ruby-doc.org/core-2.3.1/Kernel.html#method-i-Array
mastaBlasta

119

ActiveSupport (Rails) ile: Array.wrap

Array.wrap([1, 2, 3])     # => [1, 2, 3]
Array.wrap(1)             # => [1]
Array.wrap(nil)           # => []
Array.wrap({a: 1, b: 2})  # => [{:a=>1, :b=>2}]

Rails kullanmıyorsanız, kendi yönteminizi ray kaynağına benzer şekilde tanımlayabilirsiniz .

class Array
  def self.wrap(object)
    if object.nil?
      []
    elsif object.respond_to?(:to_ary)
      object.to_ary || [object]
    else
      [object]
    end
  end
end

12
class Array; singleton_class.send(:alias_method, :hug, :wrap); endekstra zarafet için.
rthbound

21

En basit çözüm kullanmaktır [foo].flatten(1). Önerilen diğer çözümlerin aksine, (iç içe geçmiş) diziler, karmalar ve nil:

def wrap(foo)
  [foo].flatten(1)
end

wrap([1,2,3])         #= [1,2,3]
wrap([[1,2],[3,4]])   #= [[1,2],[3,4]]
wrap(1)               #= [1]
wrap(nil)             #= [nil]
wrap({key: 'value'})  #= [{key: 'value'}]

maalesef bunun diğer yaklaşımlara göre ciddi bir performans sorunu var. Kernel#Arrayyani Array()bunların en hızlısı. Ruby 2.5.1 karşılaştırması: Dizi (): 7936825.7 i / s. Array.wrap: 4199036.2 i / s - 1.89x daha yavaş. sarma: 644030,4 i / sn - 12,32 kat daha yavaş
Wasif Hossain

19

Array(whatever) hile yapmalı

Array([1,2,3]) # [1,2,3]
Array(nil) # []
Array(1337)   # [1337]

14
Hash için çalışmaz. Dizi ({a: 1, b: 2}) olacak [[: a, 1], [: b, 2]]
davispuh

13

ActiveSupport (Raylar)

ActiveSupport'un bunun için oldukça güzel bir yöntemi var. Rails ile dolu, bunu yapmanın en güzel yolu meydan okurcasına:

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Uyarı (Yakut 1.9+)

Splat operatörü ( *) bir diziyi diziyi çözebilir, eğer yapabilir:

*[1,2,3] #=> 1, 2, 3 (notice how this DOES not have braces)

Elbette, bir dizi olmadan garip şeyler yapar ve "savurduğunuz" nesnelerin dizilere yerleştirilmesi gerekir. Biraz tuhaf ama şu anlama geliyor:

[*[1,2,3]] #=> [1, 2, 3]
[*5] #=> [5]
[*nil] #=> []
[*{meh: "meh"}] #=> [[:meh, "meh"], [:meh2, "lol"]]

ActiveSupport'a sahip değilseniz, yöntemi tanımlayabilirsiniz:

class Array
    def self.wrap(object)
        [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Bununla birlikte, büyük dizilere ve daha az dizi olmayan şeylere sahip olmayı planlıyorsanız, onu değiştirmek isteyebilirsiniz - yukarıdaki yöntem büyük dizilerde yavaştır ve hatta Stack'inizin Taşmasına (omg so meta) neden olabilir. Her neyse, bunun yerine bunu yapmak isteyebilirsiniz:

class Array
    def self.wrap(object)
        object.is_a? Array ? object : [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> [nil]

Ayrıca teneray operatörü olan ve olmayan bazı kriterlerim var .


Büyük diziler için işe yaramaz. SystemStackError: stack level too deep1M elementler için (yakut 2.2.3).
denis.peplin

@ denis.peplin bir StackOverflow hatası almış gibi görünüyor: D - dürüst olmak gerekirse, ne olduğundan emin değilim. Afedersiniz.
Ben Aubin

Geçenlerde Hash#values_at1M argümanlarını denedim (kullanarak splat) ve aynı hatayı atıyor.
denis.peplin

@ denis.peplin ile çalışıyor object.is_a? Array ? object : [*object]mu?
Ben Aubin

1
Array.wrap(nil)döner []değil nil/:
Aeramor

7

Ne dersin

[].push(anything).flatten

2
Evet, sanırım [herhangi bir şey] .flatten'ı kendi durumumda kullandım ... ama genel durum için bu aynı zamanda iç içe geçmiş dizi yapılarını da düzleştirecek
xxjjnn

1
[].push(anything).flatten(1)işe yarar! İç içe dizileri düzleştirmez!
xxjjnn

2

Bariz olanı belirtme riskiyle ve bunun gezegende ve çevresindeki bölgelerde şimdiye kadar görülen en lezzetli sözdizimsel şeker olmadığını bilerek, bu kod tam olarak tanımladığınız şeyi yapıyor gibi görünüyor:

foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]

1

Object'in dizi yönteminin üzerine yazabilirsiniz

class Object
    def to_a
        [self]
    end
end

her şey Object'i miras alır, bu nedenle to_a şimdi güneşin altındaki her şey için tanımlanacak


3
kafir maymun yamalıyor! Tövbe edin!
xxjjnn

1

Tüm cevapları okudum ve çoğunlukla Ruby 2+ ile çalışmıyorum

Ancak elado en zarif çözüme sahiptir.

ActiveSupport (Rails) ile: Array.wrap

Array.wrap ([1, 2, 3]) # => [1, 2, 3]

Array.wrap (1) # => [1]

Array.wrap (nil) # => []

Array.wrap ({a: 1, b: 2}) # => [{: a => 1,: b => 2}]

Maalesef bu Ruby 2+ için de işe yaramıyor çünkü bir hata alacaksınız

undefined method `wrap' for Array:Class

Yani bunu düzeltmek için ihtiyacınız olan şey.

'active_support / kullanımdan kaldırma' gerektiriyor

"active_support / core_ext / array / wrap" gerektir


0

Yöntem #to_a, iki ana sorunlu sınıf ( Nilve Hash) için zaten mevcut olduğundan , geri kalanı için genişleterek bir yöntem tanımlamanız yeterlidir Object:

class Object
    def to_a
        [self]
    end
end

ve sonra bu yöntemi herhangi bir nesnede kolayca çağırabilirsiniz:

"Hello world".to_a
# => ["Hello world"]
123.to_a
# => [123]
{a:1, b:2}.to_a
# => [[:a, 1], [:b, 2]] 
nil.to_a
# => []

5
Maymunun bir çekirdek Ruby sınıfını, özellikle nesneyi yamalamasından kaçınılması gerektiğini düşünüyorum. ActiveSupport'a geçiş izni vereceğim, bu yüzden beni ikiyüzlü olarak kabul edin. @Sawa'nın yukarıdaki çözümleri bundan çok daha uygulanabilir.
pho3nixf1re
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.