रूबी में, एक ऐरे पद्धति है जो 'चयन' और 'मानचित्र' को जोड़ती है?


95

मेरे पास एक रूबी सरणी है जिसमें कुछ स्ट्रिंग मान हैं। मुझे निम्न की जरूरत है:

  1. उन सभी तत्वों को खोजें जो कुछ विधेय से मेल खाते हैं
  2. परिवर्तन के माध्यम से मिलान तत्वों को चलाएं
  3. परिणाम को एक सरणी के रूप में लौटाएं

अभी मेरा समाधान इस तरह दिखता है:

def example
  matchingLines = @lines.select{ |line| ... }
  results = matchingLines.map{ |line| ... }
  return results.uniq.sort
end

क्या कोई ऐरे या एन्यूमरेबल तरीका है जो सिलेक्ट और मैप को सिंगल लॉजिकल स्टेटमेंट में मिलाता है?


5
इस समय विधि नहीं है, लेकिन रूबी में एक को जोड़ने का प्रस्ताव है: Bugs.ruby-lang.org/issues/5663
stefankolb

Enumerable#grepविधि वास्तव में क्या कहा था और अधिक दस साल के लिए रूबी में किया गया है नहीं करता है। यह एक विधेय तर्क और एक परिवर्तन ब्लॉक लेता है। @ अहिरौला इस प्रश्न का एकमात्र सही उत्तर देता है।
इनोपिनाटस

2
रूबी 2.7 filter_mapइस सटीक उद्देश्य के लिए शुरू कर रहा है। अधिक जानकारी यहाँ
SRack

जवाबों:


114

मैं आमतौर पर एक उपसर्ग के रूप में अपने चयन मानदंड के साथ उपयोग mapऔर compactएक साथ करता हूं ifcompactनिलों से छुटकारा मिलता है।

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}    
 => [3, 3, 3, nil, nil, nil] 


jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
 => [3, 3, 3] 

1
आह-हा, मैं यह पता लगाने की कोशिश कर रहा था कि मेरे नक्शे ब्लॉक द्वारा वापस लौटाए गए नाइलों को कैसे अनदेखा किया जाए। धन्यवाद!
सेठ पेट्री-जॉनसन

कोई बात नहीं, मैं कॉम्पैक्ट प्यार करता हूँ। यह विनीत रूप से वहाँ बैठता है और अपना काम करता है। मैं इस पद्धति को सरल चयन मानदंड के लिए असंख्य कार्यों को पूरा करने के लिए भी पसंद करता हूं क्योंकि यह बहुत ही घोषणात्मक है।
जैड श्नाइडर

4
मैं अनिश्चित था अगर map+ compactवास्तव में बेहतर प्रदर्शन करेगा injectऔर मेरे बेंचमार्क परिणामों को संबंधित थ्रेड पर पोस्ट करेगा: stackoverflow.com/questions/310426/list-comprehension-in-ruby/…
knuton

3
यह मूल निल्स और उन दोनों को हटा देगा जो आपके मानदंड को विफल करते हैं। तो बाहर देखो
user1143669

1
यह पूरी तरह से जंजीरों को खत्म नहीं करता है mapऔर selectयह सिर्फ compactएक विशेष मामला है rejectजो nils पर काम करता है और सी में सीधे लागू होने के कारण कुछ बेहतर प्रदर्शन करता है
जो एत्ज़बर्गर

53

आप इसके लिए उपयोग कर सकते हैं reduce, जिसके लिए केवल एक पास की आवश्यकता होती है:

[1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a }
=> [3, 3, 3] 

दूसरे शब्दों में, उस अवस्था को आरम्भिक करें जो आप चाहते हैं (हमारे मामले में, भरने के लिए एक खाली सूची:) [], फिर मूल सूची में प्रत्येक तत्व (हमारे मामले में, संशोधित तत्व) के लिए संशोधनों के साथ इस मान को लौटाना सुनिश्चित करें। सूची में धकेल दिया गया)।

यह सबसे अधिक कुशल है क्योंकि यह केवल एक पास ( map+ selectया compactदो पास) की आवश्यकता के साथ सूची से अधिक लूप करता है ।

आपके मामले में:

def example
  results = @lines.reduce([]) do |lines, line|
    lines.push( ...(line) ) if ...
    lines
  end
  return results.uniq.sort
end

20
नहीं है each_with_objectएक छोटे से अधिक समझ बनाने? आपको ब्लॉक के प्रत्येक पुनरावृत्ति के अंत में सरणी को वापस नहीं करना है। आप बस कर सकते हैं my_array.each_with_object([]) { |i, a| a << i if i.condition }
हेनरेबोथा

@henrebotha शायद यह करता है। मैं एक कार्यात्मक पृष्ठभूमि से आ रहा हूं, इसीलिए मैंने reduceपहली बार berg
एडम लिंडबर्ग

34

रूबी 2.7+

वहां है अभी!

रूबी 2.7 filter_mapइस सटीक उद्देश्य के लिए शुरू कर रहा है। यह मुहावरेदार और प्रदर्शनकारी है, और मैं उम्मीद करूंगा कि यह बहुत जल्द आदर्श बन जाएगा।

उदाहरण के लिए:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

यहाँ इस विषय पर एक अच्छा पढ़ा है

आशा है कि किसी के लिए उपयोगी है!


1
कोई फर्क नहीं पड़ता कि मैं कितनी बार अपग्रेड करता हूं, एक अच्छा फीचर हमेशा अगले संस्करण में होता है।
1

अच्छा लगा। एक समस्या यह हो सकती है कि चूंकि filter, selectऔर find_allपर्यायवाची हैं, जैसे हैं mapऔर वैसे collectहैं, इस पद्धति का नाम याद रखना कठिन हो सकता है। क्या यह filter_map, select_collect, find_all_mapया filter_collect?
एरिक डुमिनील

19

इसके निकट आने का एक और अलग तरीका है नया प्रयोग (इस प्रश्न के सापेक्ष) Enumerator::Lazy:

def example
  @lines.lazy
        .select { |line| line.property == requirement }
        .map    { |line| transforming_method(line) }
        .uniq
        .sort
end

.lazyविधि एक आलसी प्रगणक देता है। आलसी एन्यूमरेटर पर कॉल करने .selectया करने .mapपर एक और आलसी एन्यूमरेटर लौटता है। केवल एक बार जब आप कॉल करते हैं .uniqतो यह वास्तव में प्रगणक को मजबूर करता है और एक सरणी लौटाता है। तो प्रभावी रूप से क्या होता है आपका .selectऔर .mapकॉल को एक में जोड़ दिया जाता है - आप केवल एक ही @linesबार में दोनों को करने के लिए पुनरावृति करते हैं .selectऔर .map

मेरी वृत्ति यह है कि एडम की reduceविधि थोड़ी तेज़ होगी, लेकिन मुझे लगता है कि यह कहीं अधिक पठनीय है।


इसका प्राथमिक परिणाम यह है कि प्रत्येक बाद के विधि कॉल के लिए कोई मध्यवर्ती सरणी ऑब्जेक्ट नहीं बनाए जाते हैं। एक सामान्य @lines.select.mapस्थिति में, selectएक सरणी देता है जिसे फिर से संशोधित किया जाता है map, फिर से एक सरणी लौटाता है। तुलना करके, आलसी मूल्यांकन केवल एक बार एक सरणी बनाता है। यह तब उपयोगी है जब आपका प्रारंभिक संग्रह ऑब्जेक्ट बड़ा हो। यह आपको अनंत ज्ञानियों के साथ काम करने का अधिकार भी देता है - जैसे random_number_generator.lazy.select(&:odd?).take(10)


4
प्रत्येक अपने स्वयं के लिए। अपने तरह के समाधान के साथ मैं विधि के नामों पर नज़र डाल सकता हूं और तुरंत जान सकता हूं कि मैं इनपुट डेटा के सबसेट को बदलने जा रहा हूं, इसे अद्वितीय बनाऊंगा और इसे सॉर्ट करूंगा। reduceएक "सब कुछ" के रूप में परिवर्तन हमेशा मुझे काफी गड़बड़ लगता है।
henrebotha

2
@henrebotha: अगर आपने मुझे गलत समझा है तो मुझे माफ़ कर दें, लेकिन यह एक बहुत ही महत्वपूर्ण बिंदु है: यह कहना सही नहीं है कि "आप केवल @linesएक बार दोनों को करने के लिए पुनरावृति करते हैं .selectऔर .map"। उपयोग करने का .lazyमतलब यह नहीं है कि एक आलसी एन्यूमरेटर पर जंजीर संचालन कार्य एक एकल पुनरावृत्ति में "ढह" जाएगा। यह एक संग्रह पर आलसी मूल्यांकन wrt chaining संचालन की एक आम गलतफहमी है। (आप पहले उदाहरण में और ब्लॉकों putsकी शुरुआत में एक बयान जोड़कर इसका परीक्षण कर सकते हैं । आप पाएंगे कि वे समान संख्याओं को प्रिंट करते हैं)selectmap
pje

1
@henrebotha: और यदि आप .lazyइसे हटाते हैं तो कई बार एक ही नंबर प्रिंट करता है। मेरा कहना है कि आपके mapब्लॉक और आपके selectब्लॉक को आलसी और उत्सुक संस्करणों में एक ही समय में निष्पादित किया जाता है। आलसी संस्करण "अपने कॉल .selectऔर .mapकॉल्स को संयोजित नहीं करता है "
pJ

1
@ पीजे: वास्तव में lazy उन्हें जोड़ती है क्योंकि एक तत्व जो selectस्थिति को विफल करता है वह पास नहीं होता है map। दूसरे शब्दों में: प्रीपेंडिंग lazyमोटे तौर पर एक के स्थान पर selectऔर mapएक ही reduce([]), और "समझदारी" के बराबर है , जो select's reduce' परिणाम में शामिल होने के लिए एक पूर्व शर्त को अवरुद्ध करता है ।
हेनरेबोथा

1
@henrebotha: मुझे लगता है कि सामान्य रूप से आलसी मूल्यांकन के लिए एक भ्रामक सादृश्य है, क्योंकि आलस्य इस एल्गोरिथ्म की समय जटिलता को नहीं बदलता है। यह मेरी बात है: हर मामले में, एक आलसी चयन-तत्कालीन मानचित्र हमेशा इसके उत्सुक संस्करण के समान संगणना का प्रदर्शन करेगा। यह कुछ भी गति नहीं देता है, यह सिर्फ प्रत्येक पुनरावृत्ति के निष्पादन के क्रम को बदल देता है - श्रृंखला में अंतिम फ़ंक्शन "खींचता है" मूल्यों को रिवर्स ऑर्डर में पिछले कार्यों से आवश्यकतानुसार।
पीजे

13

यदि आपके पास ऑपरेटर का selectउपयोग कर सकते हैं case( ===), grepएक अच्छा विकल्प है:

p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3]

p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"]

यदि हमें अधिक जटिल तर्क की आवश्यकता है तो हम लंबोदर बना सकते हैं:

my_favourite_numbers = [1,4,6]

is_a_favourite_number = -> x { my_favourite_numbers.include? x }

make_awesome = -> x { "***#{x}***" }

my_data = [1,2,3,4]

p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"]

यह एक विकल्प नहीं है - यह सवाल का एकमात्र सही उत्तर है।
इनोपिनाटस

@inopinatus: अब और नहीं । यह अभी भी एक अच्छा जवाब है, हालांकि। मुझे याद नहीं है कि अन्यथा किसी ब्लॉक के साथ grep देखना।
एरिक डुमिनील

8

मुझे यकीन नहीं है कि एक है। Enumerable मॉड्यूल है, जो कहते हैं selectऔर map, एक नहीं दिखाती है।

आपको select_and_transformविधि के लिए दो ब्लॉकों में पास करना होगा, जो कि एक अनचाहे IMHO होगा।

जाहिर है, आप उन्हें सिर्फ एक साथ चेन कर सकते हैं, जो अधिक पठनीय है:

transformed_list = lines.select{|line| ...}.map{|line| ... }

3

सरल उत्तर:

आप n रिकॉर्ड है, और आप चाहते हैं selectऔर mapइस शर्त पर तो आधारित

records.map { |record| record.attribute if condition }.compact

यहां, वह विशेषता है जो आप रिकॉर्ड और शर्त से चाहते हैं जो आप किसी भी चेक को लगा सकते हैं।

कॉम्पैक्ट अनावश्यक निल के फ्लश करने के लिए है जो उस स्थिति से बाहर आया था


1
जब तक आप भी स्थिति के साथ उपयोग कर सकते हैं। जैसा कि मेरे दोस्त ने पूछा।
स्क। इरफान

2

नहीं, लेकिन आप इसे इस तरह से कर सकते हैं:

lines.map { |line| do_some_action if check_some_property  }.reject(&:nil?)

या इससे भी बेहतर:

lines.inject([]) { |all, line| all << line if check_some_property; all }

14
reject(&:nil?)मूल रूप से के रूप में ही है compact
डब्ल्यू में जोर्ग डब्ल्यू मित्तग

हाँ, इसलिए इंजेक्शन विधि और भी बेहतर है।
डैनियल ओ'हारा

2

मुझे लगता है कि यह तरीका अधिक पठनीय है, क्योंकि फ़िल्टर की शर्तों को विभाजित करता है और शेष मूल्य स्पष्ट करता है कि क्रियाएँ जुड़ी हुई हैं:

results = @lines.select { |line|
  line.should_include?
}.map do |line|
  line.value_to_map
end

और, अपने विशिष्ट मामले में, resultचर को एक साथ समाप्त करें :

def example
  @lines.select { |line|
    line.should_include?
  }.map { |line|
    line.value_to_map
  }.uniq.sort
end

1
def example
  @lines.select {|line| ... }.map {|line| ... }.uniq.sort
end

रूबी 1.9 और 1.8.7 में, आप श्रृंखला बना सकते हैं और पुनरावृत्तियों को लपेटकर बस उन्हें एक ब्लॉक पास नहीं कर सकते हैं:

enum.select.map {|bla| ... }

लेकिन यह इस मामले में वास्तव में संभव नहीं है, क्योंकि ब्लॉक के प्रकार वापस आते हैं selectऔर mapमेल नहीं खाते हैं। यह इस तरह से कुछ के लिए अधिक समझ में आता है:

enum.inject.with_index {|(acc, el), idx| ... }

AFAICS, सबसे अच्छा आप कर सकते हैं पहला उदाहरण है।

यहाँ एक छोटा सा उदाहरण दिया गया है:

%w[a b 1 2 c d].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["a", "b", "c", "d"]

%w[a b 1 2 c d].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end }
# => ["A", "B", false, false, "C", "D"]

लेकिन आप वास्तव में क्या चाहते हैं ["A", "B", "C", "D"]


मैंने कल रात "रूबी में विधि निर्धारण" के लिए एक बहुत ही संक्षिप्त वेब खोज की और ऐसा लगा कि यह अच्छी तरह से समर्थित नहीं है। थू, मुझे शायद यह कोशिश करनी चाहिए थी ... यह भी, आप क्यों कहते हैं कि ब्लॉक तर्क के प्रकार मेल नहीं खाते हैं? मेरे उदाहरण में दोनों खंड मेरे सरणी से पाठ की एक पंक्ति ले रहे हैं, है ना?
सेठ पेट्री-जॉनसन जूल

@ सेठ पेट्री-जॉनसन: हाँ, क्षमा करें, मेरा मतलब था कि वापसी मूल्य। selectएक बूलियन-ईश मान लौटाता है जो यह तय करता है कि तत्व को रखना है या नहीं, mapरूपांतरित मान लौटाता है। रूपांतरित मान ही शायद सत्य है, इसलिए सभी तत्व चयनित हो जाते हैं।
जोर्ग डब्ल्यू मित्तग

1

आपको मेरी लाइब्रेरी Rearmed Ruby का उपयोग करने का प्रयास करना चाहिए जिसमें मैंने विधि जोड़ी है Enumerable#select_map। एक उदाहरण है:

items = [{version: "1.1"}, {version: nil}, {version: false}]

items.select_map{|x| x[:version]} #=> [{version: "1.1"}]
# or without enumerable monkey patch
Rearmed.select_map(items){|x| x[:version]}

select_mapइस लाइब्रेरी में select { |i| ... }.map { |i| ... }ऊपर दिए गए कई उत्तरों से एक ही रणनीति लागू होती है।
जॉर्डन सितकिन

1

यदि आप दो अलग-अलग सरणियों का निर्माण नहीं करना चाहते हैं, तो आप उपयोग कर सकते हैं compact!लेकिन इसके बारे में सावधान रहें।

array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}
new_array.compact!

दिलचस्प है, compact!नील को हटाने की जगह है। का परिवर्तन मान compact!एक ही सरणी है अगर वहाँ परिवर्तन थे, लेकिन अगर कोई निल्स नहीं थे तो शून्य।

array = [1,1,1,2,3,4]
new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! }

एक लाइनर होगा।


0

आपका संस्करण:

def example
  matchingLines = @lines.select{ |line| ... }
  results = matchingLines.map{ |line| ... }
  return results.uniq.sort
end

मेरा संस्करण:

def example
  results = {}
  @lines.each{ |line| results[line] = true if ... }
  return results.keys.sort
end

यह 1 पुनरावृत्ति (सॉर्ट को छोड़कर) करेगा, और इसमें विशिष्टता रखने का अतिरिक्त बोनस है (यदि आप यूनीक के बारे में परवाह नहीं करते हैं, तो बस परिणाम एक सरणी बनाएं results.push(line) if ...


-1

यहाँ एक उदाहरण है। यह आपकी समस्या के समान नहीं है, लेकिन हो सकता है कि आप क्या चाहते हैं, या आपके समाधान का कोई संकेत दे सकते हैं:

def example
  lines.each do |x|
    new_value = do_transform(x)
    if new_value == some_thing
      return new_value    # here jump out example method directly.
    else
      next                # continue next iterate.
    end
  end
end
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.