รับดัชนีขององค์ประกอบอาร์เรย์เร็วกว่า O (n)


104

เนื่องจากฉันมีอาร์เรย์ขนาดใหญ่และมีค่าจากมัน ฉันต้องการรับดัชนีของค่าในอาร์เรย์ มีวิธีอื่นหรือไม่แล้วโทรArray#indexไปรับมัน? ปัญหามาจากความจำเป็นในการเก็บอาร์เรย์ขนาดใหญ่และการโทรArray#indexจำนวนครั้งมหาศาล

หลังจากลองสองสามครั้งฉันพบว่าการแคชดัชนีภายในองค์ประกอบโดยการจัดเก็บโครงสร้างที่มี(value, index)เขตข้อมูลแทนที่จะเป็นค่านั้นทำให้ประสิทธิภาพเป็นขั้นตอนใหญ่ (ชนะ 20 เท่า)

ฉันยังสงสัยว่ามีวิธีที่สะดวกกว่าในการค้นหาดัชนีขององค์ประกอบโดยไม่ต้องแคช (หรือมีเทคนิคการแคชที่ดีที่จะช่วยเพิ่มประสิทธิภาพ)

คำตอบ:


118

แปลงอาร์เรย์เป็นแฮช จากนั้นมองหาคีย์

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1

2
เร็วที่สุดถ้าอาร์เรย์ยาวมาก
Kevin

17
ขึ้นอยู่กับกรณีการใช้งานของคุณซึ่งอาจเป็นปัญหาหากมีค่าที่ซ้ำกัน วิธีการที่อธิบายไว้ข้างต้นจะส่งคืนค่าที่เท่ากันหรือ #rindex (การเกิดครั้งสุดท้ายของค่า) เพื่อให้ได้ผลลัพธ์ที่เทียบเท่า #index หมายความว่าแฮชส่งคืนดัชนีแรกของค่าที่คุณต้องทำบางอย่างตามบรรทัดของการย้อนกลับอาร์เรย์ก่อนที่จะสร้าง จากนั้นแฮชจะลบค่าดัชนีที่ส่งคืนออกจากความยาวทั้งหมดของอาร์เรย์เริ่มต้น - 1. # (array.length - 1) - แฮช ['b']
ashoda

2
การแปลงเป็นแฮชใช้เวลา O (n) ไม่ใช่หรือ? ฉันคิดว่าถ้าจะใช้มากกว่าหนึ่งครั้งการแปลงแฮชจะมีประสิทธิภาพมากขึ้น แต่สำหรับการใช้งานครั้งเดียวมันไม่ต่างจากการทำซ้ำผ่านอาร์เรย์หรือไม่?
ahnbizcad

ใช่และอาจแย่กว่าสำหรับการใช้งานครั้งเดียวหากมีความสำคัญจริง ๆ เนื่องจากการคำนวณแฮชจะไม่ลัดวงจรเร็วเท่ากับการเปรียบเทียบ
Peter DeWeese

199

ทำไมไม่ใช้ดัชนีหรือ rindex?

array = %w( a b c d e)
# get FIRST index of element searched
puts array.index('a')
# get LAST index of element searched
puts array.rindex('a')

ดัชนี: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-index

rindex: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-rindex


13
นี่คือสิ่งที่ OP กล่าวว่าพวกเขาไม่ต้องการเนื่องจากอาร์เรย์มีขนาดใหญ่ ดัชนีอาร์เรย์ # คือ O (n) และการทำเช่นนั้นหลาย ๆ ครั้งจะทำให้ประสิทธิภาพลดลง การค้นหาแฮชคือ O (1)
ทิม

4
@ ทิมฉันจำไม่ได้ว่าตอนที่ฉันตอบคำถามนี้เป็นคำถามเดียวกันบางที OP อาจแก้ไขคำถามในภายหลังซึ่งจะทำให้คำตอบนี้เป็นโมฆะ
Roger

3
มันจะไม่บอกว่ามันถูกแก้ไขในเวลาที่กำหนดหรือไม่?
ทิม

ใช่ใช่นั่นเป็นเรื่องจริง ตอนนั้นฉันและอีก 30 คนกำลังอ่านเรื่องนี้ ฉันเดาว่า: /
Roger

9

คำตอบอื่น ๆ ไม่ได้คำนึงถึงความเป็นไปได้ของรายการที่แสดงหลายครั้งในอาร์เรย์ สิ่งนี้จะส่งคืนแฮชโดยที่แต่ละคีย์เป็นอ็อบเจ็กต์ที่ไม่ซ้ำกันในอาร์เรย์และแต่ละค่าคืออาร์เรย์ของดัชนีที่สอดคล้องกับตำแหน่งที่อ็อบเจ็กต์อาศัยอยู่:

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

indices = a.each_with_index.inject(Hash.new { Array.new }) do |hash, (obj, i)| 
    hash[obj] += [i]
    hash
end
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5], 4 => [6] }

สิ่งนี้ช่วยให้สามารถค้นหารายการที่ซ้ำกันได้อย่างรวดเร็ว:

indices.select { |k, v| v.size > 1 }
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5] }

6

มีเหตุผลที่ดีที่จะไม่ใช้แฮชหรือไม่? การค้นหาO(1)เทียบกับO(n)อาร์เรย์


ประเด็นคือ - ฉันกำลังเรียก#keysใช้แฮชซึ่งส่งคืนอาร์เรย์ที่ฉันใช้อยู่ ถึงกระนั้นฉันก็อาจคิดถึงสถาปัตยกรรมของฉันเช่นกัน ...
gmile

3

หากเป็นอาร์เรย์ที่จัดเรียงคุณสามารถใช้อัลกอริทึมการค้นหาแบบไบนารี ( O(log n)) ตัวอย่างเช่นการขยาย Array-class ด้วยฟังก์ชันนี้:

class Array
  def b_search(e, l = 0, u = length - 1)
    return if lower_index > upper_index

    midpoint_index = (lower_index + upper_index) / 2
    return midpoint_index if self[midpoint_index] == value

    if value < self[midpoint_index]
      b_search(value, lower_index, upper_index - 1)
    else
      b_search(value, lower_index + 1, upper_index)
    end
  end
end

3
จริงๆแล้วมันไม่ยากที่จะอ่าน ส่วนแรกส่งคืนหากขอบเขตล่างใหญ่กว่าขอบเขตบน (มีการเรียกซ้ำ) ส่วนที่สองตรวจสอบว่าเราต้องการด้านซ้ายหรือด้านขวาโดยเปรียบเทียบจุดกึ่งกลาง m กับค่าที่จุดนั้นกับ e หากเราไม่มีคำตอบที่ต้องการเราจะเรียกคืน
ioquatix

ฉันคิดว่ามันจะดีกว่ากับอัตตาของคนที่ลงคะแนนมากกว่าการแก้ไข
Andre Figueiredo

2

การผสมผสานระหว่างคำตอบของ @sawa และความคิดเห็นที่แสดงในนั้นคุณสามารถใช้ดัชนี "ด่วน" และ rindex ในคลาสอาร์เรย์ได้

class Array
  def quick_index el
    hash = Hash[self.map.with_index.to_a]
    hash[el]
  end

  def quick_rindex el
    hash = Hash[self.reverse.map.with_index.to_a]
    array.length - 1 - hash[el]
  end
end

2

หากอาร์เรย์ของคุณมีลำดับตามธรรมชาติให้ใช้การค้นหาแบบไบนารี

ใช้การค้นหาแบบไบนารี

การค้นหาแบบไบนารีมีO(log n)เวลาในการเข้าถึง

นี่คือขั้นตอนในการใช้การค้นหาแบบไบนารี

  • ลำดับของอาร์เรย์ของคุณคืออะไร? เช่นจัดเรียงตามชื่อหรือไม่?
  • ใช้bsearchเพื่อค้นหาองค์ประกอบหรือดัชนี

ตัวอย่างโค้ด

# assume array is sorted by name!

array.bsearch { |each| "Jamie" <=> each.name } # returns element
(0..array.size).bsearch { |n| "Jamie" <=> array[n].name } # returns index

0

ฉันยังสงสัยว่ามีวิธีที่สะดวกกว่าในการค้นหาดัชนีขององค์ประกอบโดยไม่ต้องแคช (หรือมีเทคนิคการแคชที่ดีที่จะช่วยเพิ่มประสิทธิภาพ)

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

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