রুবিতে শূন্য মানগুলি কীভাবে ম্যাপ করবেন এবং মুছবেন


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

তবে এটিকে অদ্ভুত বলে মনে হচ্ছে না। তালিকার উপরে কোনও ফাংশন মানচিত্র করার, নীলগুলি আপনি যাওয়ার সাথে সাথে সরিয়ে / বাদ দিয়ে কি কোনও দুর্দান্ত উপায় আছে?


3
রুবি ২.7 পরিচয় করিয়ে দেয় filter_mapযা এটি এর জন্য নিখুঁত বলে মনে হয়। অ্যারে পুনরায় প্রক্রিয়া করার প্রয়োজনীয়তা সংরক্ষণ করে পরিবর্তে এটি প্রথমবারের মতো কাঙ্ক্ষিত হয়ে ওঠে। আরও তথ্য এখানে।
এসআরাক

উত্তর:


21

রুবি ২.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 }

" রুবি ২.7 যোগ করা যায় অনুমানযোগ্য # ফিল্টার_ম্যাপ " এই সমস্যাটিতে পূর্বের কিছু পদ্ধতির বিরুদ্ধে কিছু পারফরম্যান্স বেঞ্চমার্ক সহ, বিষয়টিতে একটি ভাল পঠনযোগ্য:

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
নিস! আপডেটের জন্য ধন্যবাদ :) একবার রুবি ২.7.০ প্রকাশিত হওয়ার পরে, আমি মনে করি এটির উত্তরটি স্যুইচ করা সম্ভবত বোধগম্য। এখানে শিষ্টাচারটি কী তা আমি নিশ্চিত নই, আপনি সাধারণত বিদ্যমান গৃহীত প্রতিক্রিয়ার আপডেট করার সুযোগ দিচ্ছেন কিনা? আমি যুক্তি দিয়েছিলাম যে এটিই প্রথম উত্তরটি ২.7-এ নতুন পদ্ধতির উল্লেখ করে, তাই গ্রহণযোগ্য হওয়া উচিত। @ দ্য টিন-ম্যান আপনি কি এই গ্রহণের সাথে একমত?
পিট হ্যামিল্টন

ধন্যবাদ @ পিটারহ্যামিলটন - প্রতিক্রিয়াটির প্রশংসা করুন, এবং আশা করি এটি প্রচুর লোকের পক্ষে কার্যকর প্রমাণিত হবে। আমি আপনার সিদ্ধান্ত নিয়ে যেতে পেরে খুশি, যদিও আপনি যে যুক্তিটি করেছেন তা স্পষ্টতই আমি পছন্দ করি :)
এসআরাক

হ্যাঁ, ভাষাগুলি সম্পর্কে এটি দুর্দান্ত জিনিস যা শুনে মূল দল রয়েছে।
টিন ম্যান

নির্বাচিত উত্তরগুলি পরিবর্তন করার পরামর্শ দেওয়ার জন্য এটি একটি দুর্দান্ত অঙ্গভঙ্গি, তবে এটি খুব কমই ঘটে। SO লোকদের মনে করিয়ে দেওয়ার জন্য কোনও টিকলার সরবরাহ করে না এবং লোকেরা সাধারণত তাদের জিজ্ঞাসা করা পুরানো প্রশ্নগুলিতে পুনরায় দেখা না দেয় যদি না এসও না বলে যে ক্রিয়াকলাপ হয়েছে। পার্শ্বদণ্ড হিসাবে, আমি বেঞ্চমার্কগুলির জন্য ফলটির দিকে নজর দেওয়ার পরামর্শ দিচ্ছি কারণ এটি অনেক কম ফিডে এবং বুদ্ধিমান পরীক্ষা করা সহজ করে তোলে।
টিন ম্যান

930

আপনি ব্যবহার করতে পারেন compact:

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

আমি লোকদের মনে করিয়ে দিতে চাই যে আপনি যদি একটি আউটপুট হিসাবে শূন্যযুক্ত একটি অ্যারে পেয়ে থাকেন map ব্লকের এবং সেই ব্লকটি শর্তসাপেক্ষে মানগুলি ফিরিয়ে দেওয়ার চেষ্টা করে তবে আপনার কোড গন্ধ পেয়েছে এবং আপনার যুক্তিটি পুনর্বিবেচনা করা দরকার need

উদাহরণস্বরূপ, আপনি যদি এমন কিছু করেন যা এটি করে:

[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খালি স্ট্রিং নয়, এন্ট্রিগুলি স্ট্রিপ করা দরকার । বিটিডাব্লু, 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
দরিদ্র জিগি, আপনার পরামর্শের জন্য কোনও ভালবাসা নেই। হাঃ হাঃ হাঃ. এক, অন্য কারও কাছে শত শত উপাখ্যান রয়েছে!
ডিডিডিডি

2
আমি বিশ্বাস করি যে একদিন আপনার সহায়তায় এই উত্তরটি গ্রহণযোগ্যদের ছাড়িয়ে যাবে। ^ o ^ //
জিগজি

2
+1 টি বর্তমানে গৃহীত উত্তরটি আপনাকে অপারেশন আপনি নির্বাচিত ফেজ সময় সঞ্চালিত ফলাফল ব্যবহার করতে অনুমতি দেয় না
chees

1
গ্রহণযোগ্য উত্তরের মতো কেবল পাসের প্রয়োজন হলে দু'বার গণ্য ডেটাস্ট্রাকচারের মাধ্যমে পুনরাবৃত্তি করা অপব্যয়যুক্ত বলে মনে হয়। এভাবে হ্রাস ব্যবহার করে পাসের সংখ্যা হ্রাস করুন! ধন্যবাদ @ জিগি
সেবিসনু

সেটা সত্য! তবে এন উপাদানগুলির সংগ্রহের জন্য দুটি পাস করা এখনও ও (এন)। যদি আপনার সংগ্রহ এত বড় না হয় যে এটি আপনার ক্যাশে ফিট করে না, দুটি পাস করাই সম্ভবত ভাল (আমি কেবল মনে করি এটি ভবিষ্যতের ক্ষেত্রে আরও মার্জিত, ভাবপ্রবণ এবং বাগগুলি নিয়ে যাওয়ার সম্ভাবনা কম রয়েছে, যখন বলুন, লুপগুলি পড়ে যায়) সিঙ্কের বাইরে)। আপনি যদি এক পাসে জিনিসগুলি করতে পছন্দ করেন তবে আপনি ট্রান্সডুসার সম্পর্কে শিখতে আগ্রহী হতে পারেন! 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]যা রেলের সাথে মিলিত নয়। (যদিও খালি স্ট্রিং বা 0
সেগুলি

27

compactএই কাজটি সমাধানের জন্য অবশ্যই সর্বোত্তম পন্থা। তবে, আমরা কেবল একটি সাধারণ বিয়োগের মাধ্যমে একই ফলাফল অর্জন করতে পারি:

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

4
হ্যাঁ, সেট বিয়োগফল কাজ করবে, তবে এটি ওভারহেডের কারণে প্রায় অর্ধেক দ্রুত fast
টিন ম্যান

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]
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.