วิธีตรวจสอบว่ามีค่าอยู่ในอาร์เรย์ใน Ruby หรือไม่


1315

ฉันมีค่า'Dog'และอาเร['Cat', 'Dog', 'Bird']ย์

ฉันจะตรวจสอบว่ามีอยู่ในอาร์เรย์โดยไม่วนซ้ำได้อย่างไร มีวิธีง่าย ๆ ในการตรวจสอบว่ามีค่าอยู่หรือไม่


5
ใช้. รวมหรือไม่ วิธี มันส่งคืนบูลีนซึ่งเป็นสิ่งที่คุณต้องการ ในกรณีของคุณเพียงพิมพ์: ['Cat', 'Dog', 'Bird']. รวม ('Dog') และควรคืนบูลีนจริง
Jwan622

อย่าใช้รวมถึง? วิธีการถ้าคุณต้องการตรวจสอบหลายครั้งสำหรับค่าที่แตกต่างที่จะอยู่ในอาร์เรย์หรือไม่เพราะรวม? แต่ละครั้งจะวนซ้ำการดำเนินการของอาร์เรย์ (n) เพื่อค้นหาในแต่ละครั้งแทนที่จะทำการแฮชhash = arr.map {|x| [x,true]}.to_hตอนนี้ให้ตรวจสอบว่าhash.has_key? 'Dog' คืนค่าจริงหรือไม่
aqfaridi

3
คุณไม่สามารถทำมันได้ทั้งหมด "โดยไม่ต้องวนซ้ำ" มันเป็นไปไม่ได้ในทางตรรกะคอมพิวเตอร์ก็ไม่สามารถรู้ได้อย่างแน่นอนว่าอาเรย์นั้นมีองค์ประกอบโดยไม่ต้องวนลูปผ่านองค์ประกอบต่างๆเพื่อตรวจสอบว่ามีสิ่งใดอยู่ในนั้นอยู่หรือไม่ แน่นอนเว้นเสียแต่ว่ามันว่างเปล่า ฉันเดาว่าคุณไม่จำเป็นต้องวนซ้ำ
Tim M.

ดูเกณฑ์มาตรฐานด้านล่างสำหรับการทดสอบความแตกต่างของวิธีการต่าง ๆ ในการค้นหาองค์ประกอบใน Array และ Set stackoverflow.com/a/60404934/128421
Tin Man

คำตอบ:


1945

คุณกำลังมองหาinclude?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true

73
ไวยากรณ์สำรอง:%w(Cat Dog Bird).include? 'Dog'
scarver2

184
บางครั้งฉันก็หวังว่ามันจะเป็น "บรรจุ" ไม่รวม ฉันมักจะผสมรวมกับ
Henley Chiu

19
ขอผมสังเกตว่าภายใน#include?ยังคงทำการวนซ้ำ แม้ว่า coder จะถูกบันทึกจากการเขียนลูปอย่างชัดเจน ฉันได้เพิ่มคำตอบที่ทำงานอย่างแท้จริงโดยไม่ต้องวนลูป
Boris Stitnicky

8
@HenleyChiu ฉันซึ่งมันถูกเรียกว่า[ 'Dog', 'Bird', 'Cat' ].has? 'Dog'

4
@AlfonsoVergara ใช่วิธีแก้ปัญหาใด ๆ สำหรับอาเรย์จะต้องทำการวนซ้ำภายใน ไม่มีวิธีทดสอบความเป็นสมาชิกของอาร์เรย์โดยไม่ต้องวนซ้ำ หากคุณไม่ต้องการวนลูปภายในคุณต้องใช้โครงสร้างข้อมูลที่แตกต่างกันเช่นตารางแฮชที่สมบูรณ์แบบพร้อมปุ่มขนาดคงที่ เนื่องจากไม่มีวิธีการทดสอบการเป็นสมาชิกในอาร์เรย์โดยไม่วนซ้ำภายในฉันตีความคำถามว่าหมายถึง "โดยไม่ต้องเขียนลูปอย่างชัดเจนตัวเอง"
Brian Campbell

250

มีin?วิธีการในActiveSupport(ส่วนหนึ่งของ Rails) ตั้งแต่ v3.1 ตามที่ระบุโดย @campaterson ดังนั้นภายใน Rails หรือถ้าคุณrequire 'active_support'คุณสามารถเขียน:

'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false

OTOH ไม่มีinโอเปอเรเตอร์หรือ#in?วิธีการในตัวของ Ruby แม้ว่าจะได้รับการเสนอมาก่อนโดยเฉพาะอย่างยิ่งโดย Yusuke Endohสมาชิกระดับแนวหน้าของ ruby-core

เป็นแหลมออกโดยคนอื่น ๆ วิธีการย้อนกลับinclude?ที่มีอยู่สำหรับทุกEnumerableท่านรวมทั้งArray, Hash, Set, Range:

['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false

โปรดทราบว่าหากคุณมีค่ามากมายในอาเรย์ของคุณค่าเหล่านั้นจะถูกตรวจสอบหนึ่งค่า (เช่นO(n)) ในขณะที่การค้นหาแฮชนั้นจะเป็นเวลาคงที่ (เช่นO(1)) ดังนั้นหากคุณมีค่าคงที่เช่นคุณควรใช้ชุดแทน เช่น:

require 'set'
ALLOWED_METHODS = Set[:to_s, :to_i, :upcase, :downcase
                       # etc
                     ]

def foo(what)
  raise "Not allowed" unless ALLOWED_METHODS.include?(what.to_sym)
  bar.send(what)
end

การทดสอบอย่างรวดเร็วแสดงให้เห็นว่าการโทรหาinclude?องค์ประกอบ 10 Setนั้นเร็วกว่าการเรียกมันประมาณ 3.5 เท่าArray(ถ้าไม่พบองค์ประกอบ)

บันทึกการปิดท้าย: ระวังเมื่อใช้include?กับ a Range, มีรายละเอียดย่อยดังนั้นให้อ้างอิงเอกสารและเปรียบเทียบกับcover?...


2
แม้ว่า Ruby จะไม่รวม#in?อยู่ในแกน แต่ถ้าคุณใช้ Rails ก็สามารถใช้งานได้ api.rubyonrails.org/classes/Object.html#method-i-in-3F (ฉันรู้ว่านี่คือ Ruby ไม่ใช่คำถาม Rails แต่อาจช่วยให้ทุกคนที่ต้องการใช้งาน#in?ใน Rails ดูเหมือนว่าจะถูกเพิ่มเข้าไปใน Rails 3.1 apidock.com/rails/Object/in%3F
campeterson เมื่อ

166

ลอง

['Cat', 'Dog', 'Bird'].include?('Dog')

นี่เป็นรูปแบบที่เก่ากว่าดู ^^^ @ คำตอบของ brian
jahrichie

62
@jahrichie คุณคิดว่าอะไรคือ "ไวยากรณ์ที่เก่ากว่า" ในคำตอบนี้วงเล็บที่เป็นทางเลือกหรือไม่
Dennis

3
ฉันเห็นด้วย @Dennis, นี่ไม่ใช่รุ่นเก่า, วงเล็บเป็นตัวเลือกและในกรณีส่วนใหญ่เป็น GOOD PRACTICE .... ลองใช้ include โดยไม่มีวงเล็บในหนึ่งบรรทัดถ้าประโยคเช่น, สิ่งที่ฉันหมายถึงขึ้นอยู่กับกรณีของคุณ ควรหรือต้องใช้วงเล็บหรือไม่ (ไม่เกี่ยวข้องกับไวยากรณ์ทับทิม "เก่า" เลย)
d1jhoni1b

นี่เป็นไวยากรณ์เดียวที่ฉันจะได้รับจากการทำงานแบบไตรภาค
Michael Cameron

49

ใช้Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

หรือถ้าทำการทดสอบเป็นจำนวนมาก1คุณสามารถกำจัดลูป (ที่include?มี) และเปลี่ยนจากO (n)ถึงO (1)ด้วย:

h = Hash[[a, a].transpose]
h['Dog']


1. ฉันหวังว่ามันชัดเจน แต่จะคัดค้าน: ใช่สำหรับการค้นหาเพียงไม่กี่ Hash [] และ transs ops จะควบคุมโปรไฟล์และแต่ละO (n) เป็นของตัวเอง


48

หากคุณต้องการที่จะตรวจสอบได้โดยบล็อกคุณอาจจะลองหรือ any?all?

%w{ant bear cat}.any? {|word| word.length >= 3}   #=> true  
%w{ant bear cat}.any? {|word| word.length >= 4}   #=> true  
[ nil, true, 99 ].any?                            #=> true  

ดูEnumerableสำหรับข้อมูลเพิ่มเติม

แรงบันดาลใจของฉันมาจาก " ประเมินว่าอาร์เรย์มีรายการใดในทับทิม "


1
มีประโยชน์มากถ้าคุณต้องการตรวจสอบใด ๆ / ทั้งหมดของสตริงเหล่านั้นจะรวมอยู่ในสายอื่น / ค่าคงที่
thanikkal

43

Ruby มีสิบเอ็ดวิธีในการค้นหาองค์ประกอบในอาร์เรย์

ที่ต้องการหนึ่งinclude?หรือสำหรับการเข้าถึงซ้ำ creat ชุดแล้วโทรหรือinclude?member?

นี่คือทั้งหมดของพวกเขา:

array.include?(element) # preferred method
array.member?(element)
array.to_set.include?(element)
array.to_set.member?(element)
array.index(element) > 0
array.find_index(element) > 0
array.index { |each| each == element } > 0
array.find_index { |each| each == element } > 0
array.any? { |each| each == element }
array.find { |each| each == element } != nil
array.detect { |each| each == element } != nil

พวกเขาทั้งหมดจะส่งกลับtrueค่า ish หากองค์ประกอบที่มีอยู่

include?เป็นวิธีที่ต้องการ มันใช้forวนลูปภาษา C ภายในที่แบ่งเมื่อองค์ประกอบตรงกับrb_equal_opt/rb_equalฟังก์ชั่นภายใน ไม่สามารถมีประสิทธิภาพมากขึ้นได้นอกจากคุณจะสร้างชุดสำหรับการตรวจสอบการเป็นสมาชิกซ้ำ ๆ

VALUE
rb_ary_includes(VALUE ary, VALUE item)
{
  long i;
  VALUE e;

  for (i=0; i<RARRAY_LEN(ary); i++) {
    e = RARRAY_AREF(ary, i);
    switch (rb_equal_opt(e, item)) {
      case Qundef:
        if (rb_equal(e, item)) return Qtrue;
        break;
      case Qtrue:
        return Qtrue;
    }
  }
  return Qfalse;
}

member?ไม่ได้ถูกนิยามใหม่ในArrayคลาสและใช้การใช้งานที่ไม่ได้เพิ่มประสิทธิภาพจากEnumerableโมดูลที่ระบุอย่างแท้จริงผ่านองค์ประกอบทั้งหมด:

static VALUE
member_i(RB_BLOCK_CALL_FUNC_ARGLIST(iter, args))
{
  struct MEMO *memo = MEMO_CAST(args);

  if (rb_equal(rb_enum_values_pack(argc, argv), memo->v1)) {
    MEMO_V2_SET(memo, Qtrue);
    rb_iter_break();
  }
  return Qnil;
}

static VALUE
enum_member(VALUE obj, VALUE val)
{
  struct MEMO *memo = MEMO_NEW(val, Qfalse, 0);

  rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
  return memo->v2;
}

แปลเป็นรหัส Ruby สิ่งนี้จะเกี่ยวกับสิ่งต่อไปนี้:

def member?(value)
  memo = [value, false, 0]
  each_with_object(memo) do |each, memo|
    if each == memo[0]
      memo[1] = true 
      break
    end
  memo[1]
end

ทั้งสองinclude?และmember?มีความซับซ้อนของเวลา O (n) ตั้งแต่ทั้งคู่ค้นหาอาร์เรย์สำหรับการเกิดขึ้นครั้งแรกของค่าที่คาดหวัง

เราสามารถใช้ Set เพื่อให้ได้ O (1) เวลาเข้าถึงโดยที่ไม่ต้องสร้าง Hash ที่เป็นตัวแทนของอาร์เรย์ก่อน หากคุณตรวจสอบสมาชิกภาพซ้ำหลายครั้งในอาร์เรย์เดียวกันการลงทุนครั้งแรกสามารถชำระได้อย่างรวดเร็ว Setไม่ได้ดำเนินการใน C แต่เป็นคลาส Ruby ธรรมดายังคงเวลาเข้าถึง O (1) ของต้นแบบ@hashทำให้คุ้มค่า

นี่คือการใช้งานของ Set class:

module Enumerable
  def to_set(klass = Set, *args, &block)
    klass.new(self, *args, &block)
  end
end

class Set
  def initialize(enum = nil, &block) # :yields: o
    @hash ||= Hash.new
    enum.nil? and return
    if block
      do_with_enum(enum) { |o| add(block[o]) }
    else
      merge(enum)
    end
  end

  def merge(enum)
    if enum.instance_of?(self.class)
      @hash.update(enum.instance_variable_get(:@hash))
    else
      do_with_enum(enum) { |o| add(o) }
    end
    self
  end

  def add(o)
    @hash[o] = true
    self
  end

  def include?(o)
    @hash.include?(o)
  end
  alias member? include?

  ...
end

ในขณะที่คุณสามารถเห็นการตั้งค่าคลาสเพิ่งสร้าง@hashอินสแตนซ์ภายในแมปวัตถุทั้งหมดไปtrueแล้วตรวจสอบการเป็นสมาชิกHash#include?ที่ใช้กับ O (1) เวลาเข้าถึงในคลาสแฮช

ฉันจะไม่พูดถึงเจ็ดวิธีอื่น ๆ เพราะมันมีประสิทธิภาพน้อยกว่า

จริงๆแล้วมีวิธีการมากขึ้นที่มีความซับซ้อน O (n) เกินกว่า 11 รายการข้างต้น แต่ฉันตัดสินใจที่จะไม่แสดงรายการพวกเขาเนื่องจากพวกเขาสแกนอาร์เรย์ทั้งหมดแทนที่จะทำลายในการแข่งขันครั้งแรก

อย่าใช้สิ่งเหล่านี้:

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...

ช่างพูดเก่งแค่ไหนว่าทับทิมมี 11 วิธีที่จะทำอะไร! ไม่ช้ากว่าที่คุณบอกว่ามีคนชี้ให้เห็นว่าคุณพลาด # 12 จากนั้นจึงเป็น # 13 และอื่น ๆ เพื่อให้ประเด็นของฉันฉันจะแนะนำวิธีอื่น ๆ แต่ก่อนอื่นให้ฉันถาม11วิธีที่คุณระบุ ก่อนอื่นคุณแทบจะไม่สามารถนับindexและfind_index(หรือfindและdetect) เป็นวิธีแยกต่างหากเนื่องจากเป็นชื่อที่แตกต่างกันสำหรับวิธีการเดียวกัน ประการที่สองการแสดงออกทั้งหมดที่ลงท้ายด้วย> 0ไม่ถูกต้องซึ่งฉันแน่ใจว่าเป็นการกำกับ (ต่อ)
Cary Swoveland

... arr.index(e)ยกตัวอย่างเช่นส่งกลับถ้า0 arr[0] == eคุณจะเรียกคืนarr.index(e)ผลตอบแทนnilถ้าeไม่เป็นปัจจุบัน indexไม่สามารถนำมาใช้ แต่หากมีการค้นหาในnil arr(ปัญหาเดียวกันกับrindexที่ไม่ได้อยู่ในรายการ) การแปลงอาเรย์เป็นเซ็ตจากนั้นใช้เมธอด set เป็นบิตของการยืด ทำไมจึงไม่แปลงเป็นแฮช (ด้วยคีย์จากอาร์เรย์และค่าที่กำหนดเอง) จากนั้นใช้เมธอดแฮช แม้ว่าการแปลงไปเป็นชุดเป็น OK มีวิธีการตั้งค่าอื่น ๆ !arr.to_set.add?(e)ที่สามารถนำมาใช้เช่น (ต่อ)
Cary Swoveland

... ตามที่สัญญาที่นี่มีการวิธีการอื่น ๆ ไม่กี่คนที่สามารถนำมาใช้: arr.count(e) > 0, arr != arr.dup.delete(e) , และarr != arr - [e] arr & [e] == [e]หนึ่งยังสามารถจ้างและselect reject
Cary Swoveland

ฉันขอแนะนำให้ใช้ชุดหากเกณฑ์คือไม่อนุญาตให้มีการทำซ้ำและรู้ว่ามีองค์ประกอบเฉพาะอยู่ในรายการหรือไม่ เซตเร็วกว่ามาก ไม่ควรใช้อาร์เรย์จริง ๆ เมื่อต้องการค้นหา พวกเขาจะใช้เป็นคิวที่ดีกว่าสิ่งที่เก็บไว้ชั่วคราวที่จะดำเนินการในการสั่งซื้อ Hash and Set จะดีกว่าถ้ามองดูว่ามีบางสิ่งอยู่
ชายดีบุก

30

คำตอบหลายข้อเสนอแนะArray#include?แต่มีข้อแม้ที่สำคัญอย่างหนึ่ง: ดูที่แหล่งที่มาแม้Array#include?จะทำการวนซ้ำ:

rb_ary_includes(VALUE ary, VALUE item)
{
    long i;

    for (i=0; i<RARRAY_LEN(ary); i++) {
        if (rb_equal(RARRAY_AREF(ary, i), item)) {
            return Qtrue;
        }
    }
    return Qfalse;
}

วิธีทดสอบการมีอยู่ของคำโดยไม่ต้องวนซ้ำคือการสร้างtrieสำหรับอาร์เรย์ของคุณ มีการใช้งาน Trie จำนวนมากอยู่ที่นั่น (google "ruby trie") ฉันจะใช้rambling-trieในตัวอย่างนี้:

a = %w/cat dog bird/

require 'rambling-trie' # if necessary, gem install rambling-trie
trie = Rambling::Trie.create { |trie| a.each do |e| trie << e end }

และตอนนี้เราก็พร้อมที่จะทดสอบการมีอยู่ของคำศัพท์ต่าง ๆ ในอาร์เรย์ของคุณโดยไม่ต้องวนซ้ำในO(log n)เวลานั้นด้วยความเรียบง่ายทางวากยสัมพันธ์เช่นเดียวกับการArray#include?ใช้ซับลิเนียร์Trie#include?:

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false

8
a.each do ... endอืมม ... ไม่แน่ใจว่ามันไม่ใช่ลูปอย่างไร
Doorknob

27
โปรดทราบว่าสิ่งนี้รวมถึงการวนซ้ำจริง ๆ อะไรก็ตามที่ไม่ใช่ O (1) จะมีวงวนอยู่บ้าง มันเกิดขึ้นเป็นวนรอบตัวอักขระของสายป้อน นอกจากนี้ทราบว่ามีคำตอบที่กล่าวถึงแล้วSet#include?สำหรับผู้ที่กังวลเกี่ยวกับประสิทธิภาพ ควบคู่ไปกับการใช้สัญลักษณ์แทนสตริงมันสามารถเป็นกรณี O (1) โดยเฉลี่ย (ถ้าคุณใช้สตริงจากนั้นเพียงแค่คำนวณแฮชคือ O (n) โดยที่ n คือความยาวของสตริง) หรือถ้าคุณต้องการใช้ห้องสมุดบุคคลที่สามคุณสามารถใช้แฮชที่สมบูรณ์แบบซึ่งเป็นกรณี O ที่เลวร้ายที่สุด
Brian Campbell

4
AFAIK Setใช้แฮชเพื่อจัดทำดัชนีสมาชิกดังนั้นจริงๆแล้วSet#include? ควรมีความซับซ้อน O (1) สำหรับการกระจายที่ดีSet(โดยเฉพาะ O (ขนาดอินพุต) สำหรับการแฮชและ O (log (n / bucket-number)) สำหรับ การค้นหา)
Uri Agassi

11
ค่าใช้จ่ายในการสร้างและบำรุงรักษาคู่ชีวิตนั้นเท่ากัน หากคุณทำการค้นหาหลายครั้งในอาเรย์หน่วยความจำและค่าใช้จ่ายในการตั้งค่า trie และการบำรุงรักษาจะคุ้มค่า แต่สำหรับเช็คเดี่ยวหรือแม้กระทั่งหลายร้อยหรือนับพัน O (n) เหมาะอย่างยิ่ง ตัวเลือกอื่นที่ไม่ต้องการเพิ่มการขึ้นต่อกันคือการเรียงลำดับอาร์เรย์หรือคงไว้ในลำดับที่เรียงซึ่งในกรณีนี้การดำเนินการค้นหาแบบไบนารี O (lg n) สามารถใช้เพื่อตรวจสอบการรวม
speakingcode

1
@speakingcode คุณอาจถูกต้องจากมุมมองของทางปฏิบัติ แต่ OP ขอให้ "ตรวจสอบว่ามีค่าอยู่หรือไม่โดยไม่ต้องวนซ้ำ" เมื่อฉันเขียนคำตอบนี้มีวิธีแก้ปัญหาเชิงปฏิบัติมากมายที่นี่ แต่ไม่มีใครที่จะตอบสนองความต้องการที่แท้จริงของผู้ถาม การสังเกตของคุณที่ BSTs ที่เกี่ยวข้องกับการพยายามที่ถูกต้อง แต่สำหรับสตริง, Trie เป็นเครื่องมือที่เหมาะสมสำหรับงานที่แม้วิกิพีเดียรู้มากว่า ความซับซ้อนของการสร้างและบำรุงรักษาคู่ชีวิตที่นำมาใช้อย่างดีนั้นน่าพอใจ
Boris Stitnicky

18

หากคุณไม่ต้องการวนซ้ำคุณไม่สามารถทำได้ด้วย Arrays คุณควรใช้ชุดแทน

require 'set'
s = Set.new
100.times{|i| s << "foo#{i}"}
s.include?("foo99")
 => true
[1,2,3,4,5,6,7,8].to_set.include?(4) 
  => true

ตั้งค่าการทำงานภายในเช่นเดียวกับแฮชดังนั้น Ruby ไม่จำเป็นต้องวนซ้ำคอลเลกชันเพื่อค้นหาไอเท็มเนื่องจากชื่อมีความหมายมันจะสร้างแฮชของคีย์และสร้างแมปหน่วยความจำเพื่อให้แฮชแต่ละอันชี้ไปยังจุดหนึ่ง ตัวอย่างก่อนหน้านี้ทำด้วยแฮ:

fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
  => true

ข้อเสียคือปุ่มชุดและแฮชสามารถรวมเฉพาะรายการที่ไม่ซ้ำกันและถ้าคุณเพิ่มรายการจำนวนมากทับทิมจะต้องทำสิ่งใหม่ทั้งหมดหลังจากรายการจำนวนหนึ่งเพื่อสร้างแผนที่ใหม่ที่เหมาะกับพื้นที่คีย์ขนาดใหญ่ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ฉันขอแนะนำให้คุณดู " MountainWest RubyConf 2014 - Big O ในแฮ็คโฮมเมดโดย Nathan Long "

นี่คือมาตรฐาน:

require 'benchmark'
require 'set'

array = []
set   = Set.new

10_000.times do |i|
  array << "foo#{i}"
  set   << "foo#{i}"
end

Benchmark.bm do |x|
  x.report("array") { 10_000.times { array.include?("foo9999") } }
  x.report("set  ") { 10_000.times { set.include?("foo9999")   } }
end

และผลลัพธ์:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)

หากคุณใช้การตรวจจับอย่างน้อยที่สุดคุณก็สามารถลดการวนซ้ำได้ ตรวจพบจะหยุดที่รายการแรก 'ตรวจพบ' (บล็อกผ่านสำหรับรายการประเมินเป็นจริง) นอกจากนี้คุณสามารถบอกการตรวจสอบว่าต้องทำอย่างไรหากตรวจพบสิ่งใด (คุณสามารถผ่านแลมบ์ดาได้)
aenw

1
@ aenw ไม่include?หยุดตีแรก?
Kimmo Lehto

คุณพูดถูก ฉันคุ้นเคยกับการใช้เครื่องมือตรวจจับที่ฉันลืมไป ขอบคุณสำหรับความคิดเห็นของคุณ - มันทำให้แน่ใจได้ว่าฉันได้รีเฟรชความรู้ของฉัน
aenw

include?หยุดที่การโจมตีครั้งแรก แต่ถ้าการเข้าตีนั้นอยู่ในตอนท้ายของรายการ .... โซลูชันใด ๆ ที่ใช้ Array สำหรับการจัดเก็บจะมีประสิทธิภาพลดลงเมื่อรายการเติบโตโดยเฉพาะอย่างยิ่งเมื่อต้องค้นหาองค์ประกอบที่ส่วนท้ายของ รายการ. Hash and Set ไม่มีปัญหานั้นและจะไม่มีรายการสั่งซื้อและการค้นหาแบบไบนารี
ชายดีบุก

นั่นเป็นสิ่งที่คำตอบนี้เกี่ยวกับในตอนแรก :)
Kimmo Lehto

17

นี่เป็นอีกวิธีหนึ่งในการทำสิ่งนี้: ใช้Array#indexวิธีนี้

มันจะส่งกลับดัชนีการเกิดขึ้นครั้งแรกขององค์ประกอบในอาร์เรย์

ตัวอย่างเช่น:

a = ['cat','dog','horse']
if a.index('dog')
    puts "dog exists in the array"
end

index() ยังสามารถนำบล็อก:

ตัวอย่างเช่น:

a = ['cat','dog','horse']
puts a.index {|x| x.match /o/}

ส่งคืนดัชนีของคำแรกในอาร์เรย์ที่มีตัวอักษร 'o'


indexยังคงวนซ้ำอาร์เรย์ แต่จะคืนค่าขององค์ประกอบ
ชายดีบุก

13

สนุกกับความเป็นจริง,

คุณสามารถใช้*เพื่อตรวจสอบการเป็นสมาชิกอาร์เรย์ในcaseนิพจน์

case element
when *array 
  ...
else
  ...
end

สังเกตุสิ่งเล็ก ๆ น้อย ๆ*ในอนุประโยคเมื่อตรวจสอบนี้เป็นสมาชิกในอาร์เรย์

พฤติกรรมเวทย์มนตร์ปกติของตัวดำเนินการ splat จะมีผลบังคับใช้ตัวอย่างเช่นถ้าarrayไม่ใช่อาร์เรย์จริง ๆ แต่มีองค์ประกอบเดียวที่จะจับคู่กับองค์ประกอบนั้น


ดีแล้วที่รู้..!
Cary Swoveland

มันอาจเป็นการตรวจสอบครั้งแรกช้าในคำสั่ง case ดังนั้นฉันจะใช้มันในครั้งสุดท้ายที่whenเป็นไปได้ดังนั้นการตรวจสอบอื่น ๆ ที่เร็วกว่าและกำจัดวัชพืชออกอย่างรวดเร็ว
Tin Man

9

มีหลายวิธีในการทำสิ่งนี้ให้สำเร็จ บางส่วนของพวกเขามีดังนี้:

a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false

6
โปรดทราบว่าObject#in?เพิ่มเฉพาะ Rails (เช่นActiveSupport) v3.1 + มันไม่สามารถใช้ได้ใน core Ruby
ลอร์ดทอม

5

หากคุณต้องการตรวจสอบหลายครั้งสำหรับคีย์ใด ๆ แปลงarrเป็นhashและตอนนี้ตรวจสอบใน O (1)

arr = ['Cat', 'Dog', 'Bird']
hash = arr.map {|x| [x,true]}.to_h
 => {"Cat"=>true, "Dog"=>true, "Bird"=>true}
hash["Dog"]
 => true
hash["Insect"]
 => false

ประสิทธิภาพของHash # has_key? กับArray # รวมถึง?

พารามิเตอร์แฮช # has_key? อาร์เรย์ # ได้แก่

ความซับซ้อนของเวลาการดำเนินงาน O (1) O (n) 

ประเภทการเข้าถึงการเข้าถึง Hash [แป้น] ถ้ามันซ้ำผ่านแต่ละองค์ประกอบ
                        ส่งคืนค่าใด ๆ จากนั้นอาร์เรย์จนกว่าจะได้รับ
                        true ถูกส่งคืนไปยังการค้นหาค่าใน Array
                        แฮ # has_key? โทร
                        โทร    

สำหรับการตรวจสอบครั้งเดียวใช้งานinclude?ได้ดี


คุณยังไม่ได้วนลูปผ่านอาร์เรย์เพื่อแปลงเป็นแฮชหรือไม่?
chrisjacob

2
ใช่ฉันทำ แต่ตอนนี้ฉันมีคำตอบที่คำนวณล่วงหน้าเพื่อตรวจสอบว่ามีองค์ประกอบใด ๆ ในอาร์เรย์หรือไม่ ตอนนี้ครั้งต่อไปเมื่อฉันค้นหาคำอื่น ๆ มันจะใช้ O (1) สำหรับการค้นหาขวาแม้ว่าการคำนวณล่วงหน้าจะใช้ O (n) ที่จริงฉันเคยใช้รวมถึง? ในแอปพลิเคชันของฉันภายในลูปสำหรับการตรวจสอบว่ามีองค์ประกอบเฉพาะในอาเรย์หรือไม่มันทำให้ประสิทธิภาพการทำงานลดลงตรวจสอบใน ruby-prof นั่นคือคอขวด
aqfaridi

1
.... แต่พื้นที่ O (n) และยังไม่มีเวลา O (n) เพราะเนื่องจาก @ chris72205 ชี้ให้เห็นอย่างถูกต้องคุณจะต้องวนซ้ำอาร์เรย์ของคุณก่อน O (1) + O (n) = O (n) ดังนั้นในความเป็นจริงนี้เป็นวิธีที่เลวร้ายยิ่งกว่ารวม?
Rambatino

เพื่อนฉันแค่บอกว่าอย่าใช้รวมวงในสำหรับการตรวจสอบครั้งเดียวโดยใช้รวมใช้ได้ ดังนั้นโปรดอ่านกรณีการใช้งานก่อนมันจะดีกว่าถ้าคุณต้องการตรวจสอบหลายครั้งภายในวง
aqfaridi

การแปลงอาร์เรย์เป็นแฮชมีค่าใช้จ่ายสูง แต่การใช้อาร์เรย์เพื่อค้นหาเป็นโครงสร้างที่ผิด อาร์เรย์จะดีกว่าเป็นคิวที่คำสั่งซื้ออาจมีความสำคัญ Hashes and Sets ดีกว่าเมื่อการรวม / มีอยู่ / ไม่ซ้ำกันมีความสำคัญ เริ่มต้นด้วยคอนเทนเนอร์ที่เหมาะสมและปัญหาคือ moot
ชายดีบุก

4

สิ่งนี้จะบอกคุณไม่เพียงว่ามันมีอยู่จริง แต่ยังปรากฏว่ามีกี่ครั้ง:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1

12
ไม่มีเหตุผลในการใช้สิ่งนี้เว้นแต่ว่าคุณต้องการทราบว่ามีกี่ครั้งที่มันปรากฏขึ้นและ.any?จะกลับมาทันทีที่พบองค์ประกอบแรกที่ตรงกัน.countจะประมวลผลอาร์เรย์ทั้งหมดเสมอ
Zaz

แม้ว่าเทคนิคนี้จะบอกได้ว่ามีบางสิ่งอยู่หรือไม่ แต่ก็ไม่ใช่วิธีที่ถูกต้องหากคุณต้องการความเร็ว
ชายดีบุก

4

สำหรับสิ่งที่คุ้มค่าเอกสารของ Rubyเป็นแหล่งข้อมูลที่น่าอัศจรรย์สำหรับคำถามประเภทนี้

ฉันจะคำนึงถึงความยาวของอาเรย์ที่คุณกำลังค้นหาด้วย include?วิธีการจะเรียกใช้การค้นหาเชิงเส้นที่มี O (n) ความซับซ้อนซึ่งสามารถได้รับน่าเกลียดสวยขึ้นอยู่กับขนาดของอาร์เรย์

หากคุณกำลังทำงานกับอาร์เรย์ขนาดใหญ่ (เรียงลำดับ) ฉันจะลองเขียนอัลกอริทึมการค้นหาแบบไบนารีซึ่งไม่ควรยากเกินไปและมีกรณีที่แย่ที่สุดของ O (log n)

หรือถ้าคุณกำลังใช้ทับทิม 2.0 bsearchคุณสามารถใช้ประโยชน์จาก


3
การค้นหาแบบไบนารีถือว่าอาร์เรย์ถูกเรียงลำดับ (หรือเรียงลำดับในบางรูปแบบ) ซึ่งอาจมีราคาแพงสำหรับอาร์เรย์ขนาดใหญ่มักจะมองข้ามข้อดี
ชายดีบุก

การค้นหาแบบไบนารี่ยังต้องการองค์ประกอบทั้งหมดเพื่อให้สามารถเทียบเคียงได้<=>ซึ่งไม่ได้เป็นทุกกรณี ตัวอย่างเช่นสมมติว่าองค์ประกอบของอาร์เรย์มีแฮช
Cary Swoveland

1
@CarySwoveland มันควรจะมากหรือน้อยโดยนัยว่าองค์ประกอบภายในอาร์เรย์ที่เรียงลำดับนั้นเปรียบได้
davissp14

4

คุณสามารถลอง:

ตัวอย่าง: ถ้ามี Cat และ Dog อยู่ในอาร์เรย์:

(['Cat','Dog','Bird'] & ['Cat','Dog'] ).size == 2   #or replace 2 with ['Cat','Dog].size

แทน:

['Cat','Dog','Bird'].member?('Cat') and ['Cat','Dog','Bird'].include?('Dog')

หมายเหตุ: member?และinclude?เหมือนกัน

สามารถทำงานได้ในหนึ่งบรรทัด!


3

ถ้าเราไม่ต้องการใช้include?สิ่งนี้ก็ใช้งานได้:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?

3
ใด ๆ รับบล็อคด้วย: ['cat', 'dog', 'horse']. any? {| x | x == 'dog'}
maikonas


2

ด้วยวิธีนี้

['Cat', 'Dog', 'Bird'].index('Dog')

มันยังคงวนซ้ำไปเรื่อย ๆ เพื่อหาองค์ประกอบ จากนั้นจะต้องส่งคืนดัชนีขององค์ประกอบนั้น
ชายดีบุก


2

มีวิธีอื่น ๆ รอบนี้

สมมติว่าอาร์เรย์เป็น[ :edit, :update, :create, :show ]อย่างดีอาจจะเป็นทั้งเจ็ดบาปร้ายแรง / พักผ่อน

และของเล่นต่อไปด้วยความคิดที่จะดึงการกระทำที่ถูกต้องจากสตริง:

"my brother would like me to update his profile"

แล้ว:

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}

1
Regex ของคุณ/[,|.| |]#{v.to_s}[,|.| |]/ทำให้ฉันคิดว่าคุณต้องการค้นหา 'ชื่อของการกระทำที่ล้อมรอบด้วยหนึ่งใน: จุลภาคระยะเวลาช่องว่างหรืออะไรเลย' แต่มีข้อบกพร่องเล็กน้อย "|update|"จะกลับมา[:update]และจะกลับมา"update" []คลาสอักขระ ( [...]) ไม่ใช้ไพพ์ (| ) เพื่อแยกอักขระ แม้ว่าเราจะเปลี่ยนเป็นกลุ่ม ( (...)) คุณไม่สามารถจับคู่อักขระว่างเปล่าได้ ดังนั้น regex ที่คุณอาจต้องการคือ/(,|\.| |^)#{v.to_s}(,|\.| |$)/
bkDJ

regex นั้นโอเค (ตรวจสอบแล้ว rubular.com) - และ @Rambatino: ทำไมจะเป็นเช่น Amazon Echo;) คุณสามารถพูดได้ว่า: "โปรดเพิ่มไก่ลงในรายการช้อปปิ้งของฉัน" (และ - ดี - จากนั้นคุณต้อง เพิ่ม: เพิ่มไปยังอาร์เรย์ แต่ฉันคิดว่าคุณจะได้รับส่วนสำคัญของมัน;)
walt_die

1
"regex ก็โอเค (ตรวจสอบ rubular.com)" ไม่เป็นไร regex ของคุณจะไม่ตรงกับคำค้นหาที่จุดเริ่มต้นหรือจุดสิ้นสุดของสตริง (เช่น "อัปเดตโปรไฟล์พี่ชายของฉัน") หากคุณไม่ต้องการให้ตรงกับจุดเริ่มต้นหรือจุดสิ้นสุด regex ของคุณยังคงไม่เป็นไรเพราะคลาสอักขระทางด้านใดด้านหนึ่งของคำหลักควรเป็น/[,. ]/
bkDJ

ตามที่ @bkDJ พูดว่า regex นั้นผิด rubular.com/r/4EG04rANz6KET6
Tin Man

1

หากคุณต้องการส่งคืนค่าไม่ใช่แค่จริงหรือเท็จใช้

array.find{|x| x == 'Dog'}

นี่จะส่งคืน 'สุนัข' หากมีอยู่ในรายการมิฉะนั้นจะไม่มี


หรือการใช้งานarray.any?{|x| x == 'Dog'}ถ้าคุณไม่ต้องการ / จริงเท็จ (ไม่ได้ค่า) แต่ยังต้องการที่จะเปรียบเทียบกับบล็อกเช่นนี้
mahemoff

0

นี่เป็นอีกวิธีหนึ่งในการทำสิ่งนี้:

arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

present = arr.size != (arr - [e]).size

1
นี่เป็นวิธีที่ไม่มีประสิทธิภาพอย่างน่ากลัวในการทำเช่นนี้! ฉันไม่ได้ลงคะแนนเพราะมันไม่ถูกต้องทางเทคนิคและบางคนอาจเรียนรู้บางอย่างเกี่ยวกับทับทิมจากการอ่าน แต่มีคำตอบที่ดีกว่ามากมาย
jibberia

Conehead arr != arr - [e]คุณสามารถลดความซับซ้อนของการ arr & [e] == [e]เป็นอีกวิธีในแนวเดียวกัน
Cary Swoveland

@CarySwoveland อย่าทำให้สนุกกับหมวกของพ่อมด ;-)
Wand Maker

ฉันหมายถึงศีรษะของพ่อมดไม่ใช่หมวกด้วยความเคารพอย่างยิ่ง
Cary Swoveland


0

มีหลายวิธีในการค้นหาองค์ประกอบในอาร์เรย์ใด ๆ แต่วิธีที่ง่ายที่สุดคือ 'ใน' วิธี.

example:
arr = [1,2,3,4]
number = 1
puts "yes #{number} is present in arr" if number.in? arr

2
หมายเหตุ: ตามที่อธิบายไว้ในคำตอบนี้วิธีการที่in?ต้องนำเข้า:ActiveSupport require active_support
แพทริค

มันไม่จำเป็นต้องทั้งหมดของ ActiveSupport ถ้าคุณใช้นามสกุลหลัก
ชายดีบุก

0

หากคุณไม่ต้องการใช้include?คุณสามารถห่อองค์ประกอบในอาร์เรย์ก่อนจากนั้นตรวจสอบว่าองค์ประกอบที่ถูกห่อนั้นเท่ากับจุดตัดของอาร์เรย์และองค์ประกอบที่ห่อหรือไม่ นี่จะคืนค่าบูลีนตามความเสมอภาค

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end

-1

ฉันมักจะพบว่ามันน่าสนใจที่จะใช้การวัดประสิทธิภาพเพื่อดูความเร็วสัมพัทธ์ของวิธีต่างๆในการทำบางสิ่งบางอย่าง

การค้นหาองค์ประกอบอาเรย์ที่เริ่มต้นกลางหรือท้ายจะส่งผลต่อการค้นหาเชิงเส้นใด ๆ แต่แทบจะไม่ส่งผลกระทบต่อการค้นหากับชุด

การแปลง Array เป็น Set จะทำให้เวลาในการประมวลผลเป็นผลดังนั้นสร้าง Set จาก Array หนึ่งครั้งหรือเริ่มด้วย Set ตั้งแต่เริ่มต้น

นี่คือรหัสมาตรฐาน:

# frozen_string_literal: true

require 'fruity'
require 'set'

ARRAY = (1..20_000).to_a
SET = ARRAY.to_set

DIVIDER = '-' * 20

def array_include?(elem)
  ARRAY.include?(elem)
end

def array_member?(elem)
  ARRAY.member?(elem)
end

def array_index(elem)
  ARRAY.index(elem) >= 0
end

def array_find_index(elem)
  ARRAY.find_index(elem) >= 0
end

def array_index_each(elem)
  ARRAY.index { |each| each == elem } >= 0
end

def array_find_index_each(elem)
  ARRAY.find_index { |each| each == elem } >= 0
end

def array_any_each(elem)
  ARRAY.any? { |each| each == elem }
end

def array_find_each(elem)
  ARRAY.find { |each| each == elem } != nil
end

def array_detect_each(elem)
  ARRAY.detect { |each| each == elem } != nil
end

def set_include?(elem)
  SET.include?(elem)
end

def set_member?(elem)
  SET.member?(elem)
end

puts format('Ruby v.%s', RUBY_VERSION)

{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include?        { array_include?(element)        }
    _array_member?         { array_member?(element)         }
    _array_index           { array_index(element)           }
    _array_find_index      { array_find_index(element)      }
    _array_index_each      { array_index_each(element)      }
    _array_find_index_each { array_find_index_each(element) }
    _array_any_each        { array_any_each(element)        }
    _array_find_each       { array_find_each(element)       }
    _array_detect_each     { array_detect_each(element)     }
  end
end

puts '', DIVIDER, 'Sets vs. Array.include?', DIVIDER
{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include? { array_include?(element) }
    _set_include?   { set_include?(element)   }
    _set_member?    { set_member?(element)    }
  end
end

ซึ่งเมื่อทำงานบนแล็ปท็อป Mac OS ของฉันผลลัพธ์จะเป็น:

Ruby v.2.7.0
--------------------
First
--------------------
Running each test 65536 times. Test will take about 5 seconds.
_array_include? is similar to _array_index
_array_index is similar to _array_find_index
_array_find_index is faster than _array_any_each by 2x ± 1.0
_array_any_each is similar to _array_index_each
_array_index_each is similar to _array_find_index_each
_array_find_index_each is faster than _array_member? by 4x ± 1.0
_array_member? is faster than _array_detect_each by 2x ± 1.0
_array_detect_each is similar to _array_find_each
--------------------
Middle
--------------------
Running each test 32 times. Test will take about 2 seconds.
_array_include? is similar to _array_find_index
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 2x ± 0.1
_array_member? is faster than _array_index_each by 2x ± 0.1
_array_index_each is similar to _array_find_index_each
_array_find_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each
--------------------
Last
--------------------
Running each test 16 times. Test will take about 2 seconds.
_array_include? is faster than _array_find_index by 10.000000000000009% ± 10.0%
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 3x ± 0.1
_array_member? is faster than _array_find_index_each by 2x ± 0.1
_array_find_index_each is similar to _array_index_each
_array_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each

--------------------
Sets vs. Array.include?
--------------------
--------------------
First
--------------------
Running each test 65536 times. Test will take about 1 second.
_array_include? is similar to _set_include?
_set_include? is similar to _set_member?
--------------------
Middle
--------------------
Running each test 65536 times. Test will take about 2 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 1400x ± 1000.0
--------------------
Last
--------------------
Running each test 65536 times. Test will take about 4 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 3000x ± 1000.0

โดยพื้นฐานแล้วผลลัพธ์บอกให้ฉันใช้ Set สำหรับทุกอย่างถ้าฉันจะค้นหาการรวมถ้าฉันสามารถรับประกันได้ว่าองค์ประกอบแรกคือสิ่งที่ฉันต้องการซึ่งไม่น่าเป็นไปได้มาก มีค่าใช้จ่ายบางอย่างเมื่อใส่องค์ประกอบลงในแฮช แต่เวลาในการค้นหาเร็วขึ้นมากฉันไม่คิดว่าควรจะพิจารณา อีกครั้งหากคุณต้องการค้นหาอย่าใช้ Array ให้ใช้ Set (หรือแฮช)

อาเรย์ที่เล็กกว่าจะเร็วกว่าวิธีอาเรย์ แต่ก็ยังคงไม่ทันตั้งตัว แต่ในอาเรย์เล็ก ๆ ความแตกต่างอาจจะเล็กน้อย

"ครั้งแรก", "กลาง" และ "สุดท้าย" สะท้อนให้เห็นถึงการใช้first, size / 2และlastสำหรับARRAYสำหรับเป็นองค์ประกอบค้นหา องค์ประกอบนั้นจะถูกใช้เมื่อค้นหาARRAYและSETตัวแปร

มีการเปลี่ยนแปลงเล็กน้อยสำหรับวิธีการที่เปรียบเทียบกับ> 0เพราะการทดสอบควรใช้>= 0สำหรับindexการทดสอบประเภท

ข้อมูลเพิ่มเติมเกี่ยวกับฟรุ๊ตตี้และวิธีการที่สามารถใช้ได้ในของREADME

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.