रूबी में शून्य मानों को कैसे मैप करें और निकालें


361

मेरे पास एक mapया तो एक मूल्य है या इसे शून्य पर सेट करता है। फिर मैं सूची से नील प्रविष्टियाँ निकालना चाहता हूँ। सूची को रखने की आवश्यकता नहीं है।

वर्तमान में मेरे पास यही है:

# A simple example function, which returns a value or nil
def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]

मुझे पता है कि मैं बस एक लूप कर सकता हूं और सशर्त इस तरह से एक और सरणी में इकट्ठा कर सकता हूं:

new_items = []
items.each do |x|
    x = transform(x)
    new_items.append(x) unless x.nil?
end
items = new_items

लेकिन यह मुहावरेदार नहीं लगता। वहाँ एक सूची पर एक समारोह नक्शा करने के लिए एक अच्छा तरीका है, के रूप में आप जाते हैं / हटाने nils को छोड़कर?


3
रूबी 2.7 पेश करता है filter_map, जो इसके लिए एकदम सही लगता है। सरणी को फिर से संसाधित करने की आवश्यकता को बचाता है, इसके बजाय इसे पहली बार वांछित के रूप में प्राप्त कर रहा है। अधिक जानकारी यहाँ।
SRack

जवाबों:


21

रूबी 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]

आपके मामले में, जैसा कि ब्लॉक फाल्सी का मूल्यांकन करता है, बस:

items.filter_map { |x| process_x url }

" रूबी 2.7 ने कहा कि Enumerable # filter_map " इस विषय पर एक अच्छा पढ़ा है, इस समस्या के लिए पहले के कुछ दृष्टिकोणों के खिलाफ कुछ प्रदर्शन बेंचमार्क हैं:

N = 1_00_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
  x.report("select + map")  { N.times { enum.select { |i| i.even? }.map{|i| i + 1} } }
  x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
  x.report("filter_map")    { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end

# Rehearsal -------------------------------------------------
# select + map    8.569651   0.051319   8.620970 (  8.632449)
# map + compact   7.392666   0.133964   7.526630 (  7.538013)
# filter_map      6.923772   0.022314   6.946086 (  6.956135)
# --------------------------------------- total: 23.093686sec
# 
#                     user     system      total        real
# select + map    8.550637   0.033190   8.583827 (  8.597627)
# map + compact   7.263667   0.131180   7.394847 (  7.405570)
# filter_map      6.761388   0.018223   6.779611 (  6.790559)

1
अच्छा! अपडेट के लिए धन्यवाद :) एक बार रूबी 2.7.0 जारी होने के बाद, मुझे लगता है कि यह संभवतः इस एक को स्वीकार किए गए उत्तर को स्विच करने के लिए समझ में आता है। मुझे यकीन नहीं है कि शिष्टाचार यहां क्या है, हालांकि, क्या आप आम तौर पर मौजूदा स्वीकृत प्रतिक्रिया को अपडेट करने का मौका देते हैं? मेरा तर्क है कि यह 2.7 में नए दृष्टिकोण को संदर्भित करने वाला पहला उत्तर है, इसलिए इसे स्वीकार किया जाना चाहिए। @ टिन-टिन-मैन क्या आप इससे सहमत हैं?
पीट हैमिल्टन

धन्यवाद @Peter Hamilton - प्रतिक्रिया की सराहना करते हैं, और आशा है कि यह बहुत से लोगों के लिए उपयोगी साबित होगा। मुझे आपके निर्णय के साथ जाने में खुशी हो रही है, हालांकि जाहिर है मुझे आपके द्वारा किए गए तर्क पसंद हैं :)
SRack

हां, यह उन भाषाओं के बारे में अच्छी बात है जिनकी कोर टीमें हैं जो सुनती हैं।
टीन मैन

चयनित उत्तरों को बदले जाने की अनुशंसा करना अच्छा संकेत है, लेकिन ऐसा बहुत कम होता है। एसओ लोगों को याद दिलाने के लिए एक गुदगुदी प्रदान नहीं करता है और लोग आमतौर पर पुराने प्रश्नों को दोबारा नहीं लेते हैं जब तक कि एसओ का कहना है कि गतिविधि नहीं हुई है। एक साइडबार के रूप में, मैं बेंचमार्क के लिए फ्रूटी को देखने की सलाह देता हूं क्योंकि यह बहुत कम काल्पनिक है और समझदार परीक्षण करना आसान बनाता है।
टिन मैन

930

आप उपयोग कर सकते हैं compact:

[1, nil, 3, nil, nil].compact
=> [1, 3] 

मैं लोगों को याद दिलाना चाहता हूं कि यदि आपको एक mapब्लॉक के आउटपुट के रूप में एक सरणी युक्त नल मिल रहा है , और यह ब्लॉक सशर्त रूप से मूल्यों को वापस करने की कोशिश करता है, तो आपको कोड गंध मिल गया है और अपने तर्क पर पुनर्विचार करने की आवश्यकता है।

उदाहरण के लिए, यदि आप कुछ ऐसा कर रहे हैं जो ऐसा करता है:

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]

तब नहीं। इसके बजाय, पहले map, rejectवह सामान जो आप नहीं चाहते या selectजो आप चाहते हैं:

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]

मैं compactउन चीजों से छुटकारा पाने के लिए एक गंदगी को साफ करने के लिए उपयोग करने पर विचार करता हूं जो हम उन चीजों से छुटकारा पाने के प्रयास करते हैं, जिन्हें हम सही तरीके से नहीं संभालते हैं, आमतौर पर क्योंकि हम नहीं जानते थे कि हम पर क्या आ रहा है। हमें हमेशा पता होना चाहिए कि हमारे प्रोग्राम में किस तरह का डेटा इधर-उधर फेंका जा रहा है; अप्रत्याशित / अज्ञात डेटा खराब है। जब भी मैं काम कर रहा होता हूं, तब मैं किसी ऐसे ऐंगल में देखता हूं, मैं खोदता हूं कि वे क्यों मौजूद हैं, और देखें कि क्या मैं ऐरे को उत्पन्न करने वाले कोड में सुधार कर सकता हूं, बजाय इसके कि रूबी को समय बर्बाद करने की अनुमति दें और मेमोरी नाइल बनाने के लिए एरे को हटा दें। उन्हें बाद में।

'Just my $%0.2f.' % [2.to_f/100]

29
अब वह रूबी-एस्क है!
क्रिस्टोफ मारोस

4
क्यों करना चाहिए? ओपी को nilप्रविष्टियों को हटाने की जरूरत है , खाली तारों की नहीं। BTW, nilएक खाली-स्ट्रिंग के समान नहीं है।
टिन मैन

9
दोनों समाधान संग्रह पर दो बार पुनरावृति करते हैं ... उपयोग reduceया क्यों नहीं inject?
जिग्गी

4
ऐसा नहीं लगता कि आप ओपीएस प्रश्न या उत्तर को पढ़ते हैं। सवाल यह है कि किसी ऐरे से निलों को कैसे हटाया जाए। compactसबसे तेज़ है लेकिन वास्तव में प्रारंभ में कोड को सही तरीके से लिखना निल्स से पूरी तरह से निपटने की आवश्यकता को हटा देता है।
टिन मैन

3
मैं असहमत हूं! सवाल है "मानचित्र और शून्य मान हटाएं"। खैर, शून्य मानों को मैप करने और हटाने के लिए कम करना है। उनके उदाहरण में, ओपी मैप करता है और फिर निल्स का चयन करता है। कॉलिंग मैप और फिर कॉम्पैक्ट, या सिलेक्ट और फिर मैप, एक ही गलती करने की मात्रा: जैसा कि आप अपने जवाब में बताते हैं, यह एक कोड गंध है।
जिग्गी

96

प्रयोग करके देखें reduceया inject

[1, 2, 3].reduce([]) { |memo, i|
  if i % 2 == 0
    memo << i
  end

  memo
}

मैं स्वीकार किए गए उत्तर से सहमत हूं कि हमें नहीं करना चाहिए mapऔर compactउसी कारणों से नहीं।

मैं गहरी अंदर है कि लग रहा है mapतो compactके बराबर है selectतो map। विचार करें: mapएक-से-एक फ़ंक्शन है। यदि आप कुछ मानों के सेट से मैपिंग कर रहे हैं, और आप map, तो आप इनपुट सेट में प्रत्येक मान के लिए आउटपुट सेट में एक मान चाहते हैं । यदि आप selectपहले से काम कर रहे हैं , तो आप शायद mapसेट पर नहीं चाहते हैं । यदि आप selectबाद में (या compact) कर रहे हैं तो आप शायद mapसेट पर नहीं चाहते हैं । या तो मामले में आप पूरे सेट पर दो बार पुनरावृत्ति कर रहे हैं, जब reduceकेवल एक बार जाने की आवश्यकता होती है।

इसके अलावा, अंग्रेजी में, आप "पूर्णांकों के एक सेट को भी पूर्णांकों के एक समूह में कम करने" का प्रयास कर रहे हैं।


4
गरीब जिग्गी, आपके सुझाव के लिए कोई प्यार नहीं करता। जबरदस्त हंसी। प्लस एक, किसी और के सैकड़ों अपवोट्स हैं!
DDDD 16

2
मुझे विश्वास है कि एक दिन, आपकी सहायता से, यह उत्तर स्वीकार किए गए को पार कर जाएगा। ^ ओ ^ //
ज़िग्जी

2
+1 वर्तमान में स्वीकार जवाब आप आपरेशनों आप कुछ चुनिंदा चरण के दौरान प्रदर्शन किया के परिणामों का प्रयोग करने की अनुमति नहीं
chees

1
स्वीकार्य आधार पर दो बार गणना करने योग्य डेटास्ट्रक्चर पर पुनरावृत्ति की आवश्यकता होती है जैसे स्वीकृत उत्तर में यह बेकार लगता है। इस प्रकार कम करके पास की संख्या कम करें! धन्यवाद @Ziggy
sebisnow

यह सच है! लेकिन n तत्वों के संग्रह पर दो पास करना अभी भी O (n) है। जब तक कि आपका संग्रह इतना बड़ा न हो जाए कि वह आपके कैश में फिट न हो, दो पास करना शायद ठीक है (मुझे लगता है कि यह अधिक सुरुचिपूर्ण, अभिव्यंजक है, और भविष्य में बग की संभावना कम होती है, जब कहते हैं, लूप्स गिर जाते हैं मेल से बाहर)। यदि आप चीजों को एक पास में करना पसंद करते हैं, तो आपको ट्रांसड्यूसर्स के बारे में जानने में रुचि हो सकती है! github.com/cognitect-labs/transducers-ruby
Ziggy

33

आपके उदाहरण में:

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]

ऐसा नहीं लगता कि मूल्यों को बदलने के अलावा अन्य बदल गए हैं nil। अगर ऐसा है, तो:

items.select{|x| process_x url}

पर्याप्त होगा।


27

यदि आप अस्वीकृति के लिए एक शिथिल मानदंड चाहते थे, उदाहरण के लिए, खाली तार को अस्वीकार करने के साथ-साथ शून्य भी, तो आप निम्न कार्य कर सकते हैं:

[1, nil, 3, 0, ''].reject(&:blank?)
 => [1, 3, 0] 

यदि आप आगे जाना चाहते हैं और शून्य मानों को अस्वीकार करते हैं (या प्रक्रिया में अधिक जटिल तर्क लागू करते हैं), तो आप अस्वीकार करने के लिए एक ब्लॉक पास कर सकते हैं:

[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
 => [1, 3]

[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
 => [1, 3]

5
.blank? केवल रेल में उपलब्ध है।
इक्वल

भविष्य के संदर्भ के लिए, चूंकि blank?केवल रेल में उपलब्ध है, हम उपयोग कर सकते हैं items.reject!(&:nil?) # [1, nil, 3, nil, nil] => [1, 3]जो रेल के लिए युग्मित नहीं है। (हालांकि खाली तारों या 0s को बाहर नहीं करेगा)
Fotis

27

निश्चित रूप compactसे इस कार्य को हल करने के लिए सबसे अच्छा तरीका है। हालाँकि, हम केवल एक साधारण घटाव के साथ एक ही परिणाम प्राप्त कर सकते हैं:

[1, nil, 3, nil, nil] - [nil]
 => [1, 3]

4
हां, सेट घटाव काम करेगा, लेकिन इसके उपरी हिस्से के कारण यह लगभग आधा है।
टीन मैन

4

each_with_object शायद यहाँ जाने का सबसे साफ तरीका है:

new_items = items.each_with_object([]) do |x, memo|
    ret = process_x(x)
    memo << ret unless ret.nil?
end

मेरी राय में, सशर्त मामलों में / each_with_objectसे बेहतर है क्योंकि आपको ब्लॉक के वापसी मूल्य के बारे में चिंता करने की ज़रूरत नहीं है।injectreduce


0

इसे पूरा करने का एक और तरीका नीचे दिखाया गया है। यहां, हम Enumerable#each_with_objectमानों को इकट्ठा करने के लिए उपयोग करते हैं, और Object#tapअस्थायी चर से छुटकारा पाने के लिए उपयोग करते हैं जो अन्यथा विधि के nilपरिणाम पर जांच के लिए आवश्यक है process_x

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

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

items = [1,2,3,4,5]
def process x
    rand(10) > 5 ? nil : x
end

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

वैकल्पिक दृष्टिकोण:

जिस पद्धति से आप कॉल कर रहे हैं process_x url, उसे देखकर यह स्पष्ट नहीं xहै कि उस पद्धति में इनपुट का उद्देश्य क्या है । अगर मुझे लगता है कि आप xइसे पारित करके कुछ के मूल्य को संसाधित करने जा रहे हैं urlऔर यह निर्धारित करते हैं कि xवास्तव में मान्य गैर-शून्य परिणामों में से कौन सा संसाधित होता है - तो, ​​इससे Enumerabble.group_byबेहतर विकल्प हो सकता है Enumerable#map

h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}

h["Good"]
#=> [3,4,5]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.