2017-09-03 1 views
1

Ich möchte alle Elemente zwischen den Tags extrahieren/greifen ": ab:" und ":/desc" aus diesem ArrayExtrahieren wiederkehrende Elemente von Array (Bad Wert für Range Error)

array = ["hello", ":desc:", "claire", "et", "concise", ":/desc:", 
     ":desc:", "claire", "caca", "concise", "test", ":/desc:"] 

so, dass ich haben

new_array = [[":desc:", "claire", "et", "concise", ":/desc:"], 
      [":desc:", "claire", "caca", "concise", "test", ":/desc:"]] 

I

versucht
final_array = [] 

start_element = ':desc:' 
end_element = ':/desc:' 

while array.any? 
    final_array << array.slice! 
(array.find_index(start_element)..array.find_index(end_element)) 
end 

Aber es ist offensichtlich nicht funktioniert, weil ich einen bad value for range Fehler.

+0

Könnte es ein Tippfehler sein? Array Elemente sind '":/desc "' aber du hast 'end_element =":/desc: "' – Marco

Antwort

4

Es gibt ein paar Probleme hier. Von Ihrem Beispiel-Array sieht es so aus, als wäre das Endelement ':/desc' anstatt ':/desc:' (d. H. Kein nachlaufendes :). Das könnte aber nur ein Tippfehler in der Frage sein.

Das Hauptproblem ist, dass die 2 Scheiben nach dem Entfernen der Array nicht leer sein wird (es wird immer noch die "hello" aus der Zeit vor dem ersten start_element enthalten. Dies bedeutet, dass die array.any? Bedingung noch wahr sein wird, wenn find_index(start_element) nicht geht ein passendes Element finden in diesem Fall find_index wird nil zurückkehren, zu einem no implicit conversion from nil to integer führen, wenn sie versuchen slice! zu verwenden

Wenn Sie wissen, dass Ihre Daten start_element und end_element in passenden Paaren immer ein enthält dann Ansatz wäre:..

while start_index = array.find_index(start_element) 
    end_index = array.find_index(end_element) 
    final_array << array.slice!(start_index..end_index) 
end 

Wenn mit dieser Art von Fehlern in der Zukunft konfrontiert wird einig treuer puts Debugging helfen, in diesem Fall den 2-Indizes überprüft und den restlichen Inhalt des Arrays:

while array.any? 
    start_index = array.find_index(start_element) 
    end_index = array.find_index(end_element) 
    puts "#{start_index}..#{end_index}" 
    final_array << array.slice!(start_index..end_index) 
    puts array.inspect 
end 

1..5 
["hello", ":desc:", "claire", "caca", "concise", "test", ":/desc"] 
1..6 
["hello"] 
.. 
TypeError: no implicit conversion from nil to integer 
from (pry):146:in `slice!' 
+0

danke @mikej yep es ist ziemlich gut und funktioniert perfekt mit Ruby. Aber wenn es auf Rails (im Controller) implementiert wird, tut es das nicht. Rails wirft eine "undefinierte Methode' find_index '". Irgendeine Idee ? Ich rufe das Array korrekt auf und gebe das Start- und Endeelement ein. Der Debugger zeigt diese Codezeile als Problem an: während start_index = array.find_index (start_element) Beliebige Eingabe? – Goeast

+0

Wenn es keine 'find_index' Methode für das Objekt gibt, dann hört es sich so an, als hätten Sie kein Array. Woher kommen die Daten? Wird es in Params eingereicht? Können Sie Ihren Controller-Code am Ende der Frage hinzufügen oder alternativ eine neue Frage damit posten? – mikej

2

Sie auch können Kombination von Enumarable#slice_after und Enumarable#drop_while:

array.slice_after(':/desc').map { |e| e.drop_while { |i| i != ':desc:' } } 
#=> [[":desc:", "claire", "et", "concise", ":/desc"], 
# [":desc:", "claire", "caca", "concise", "test", ":/desc"]] 
+0

Dies setzt voraus, dass das Array eine bestimmte Struktur hat. Was ist, wenn 'arr = [':/desc ',': desc ',' /: desc '] 'oder' [': desc: '. ':/desc', 'bob', ': desc:', ':/desc'] '. –

+0

@ilya das gibt Folgendes zurück: # > auf Schienen jede Idee warum? – Goeast

+1

@Geast Verwenden Sie Ruby 2.3 oder höher? – tadman

1

ich gehe davon aus, dass Sub-Arrays mitAnfangund endend mit ":/desc", und enthalten keine anderen Instanzen von ":/desc", sind extrahiert werden. Beachten Sie, dass arr = [":desc:", ":desc:", ":/desc"], [a] zurückgegeben wird. Ich habe keine Annahmen über die Struktur des Arrays gemacht (aber ich habe nicht alle Möglichkeiten getestet). Wenn bestimmte Annahmen gemacht werden (beispielsweise die Existenz von übereinstimmenden, nicht überlappenden Paaren), sind Vereinfachungen möglich.

-Code

def extract(arr, target_start, target_end) 
    arr.select { |s| (s == target_start)..(s == target_end) ? true : false }. 
     slice_when { |s,t| [s, t] == [target_end, target_start] }. 
     to_a. 
     tap { |a| a.pop unless a.last.last == target_end } 
end 

Beispiele

target_start = ":desc:" 
target_end = ":/desc" 

arr = ["hello", ":desc:", "claire", "et", "concise", ":/desc", 
     ":desc:", "claire", "caca", "concise", "test", ":/desc"] 
extract(arr, target_start, target_end) 
    #=> [[":desc:", "claire", "et", "concise", ":/desc"], 
    # [":desc:", "claire", "caca", "concise", "test", ":/desc"]] 

arr = ["hello", ":desc:", "claire", "et", "concise", ":/desc", "wanda", 
     ":desc:", "claire", "caca", "concise", "test", ":/desc", "herb"] 
extract(arr, target_start, target_end) 
    # => [[":desc:", "claire", "et", "concise", ":/desc"], 
    #  [":desc:", "claire", "caca", "concise", "test", ":/desc"]] 

arr = ["hello", ":desc:", "claire", "et", "concise", ":/desc", 
     ":desc:", "claire", "caca", "concise", "test"] 
extract(arr, target_start, target_end) 
    #=> [[":desc:", "claire", "et", "concise", ":/desc"]] 

arr = ["hello", ":desc:", "claire", "et", "concise", ":desc:", "claire", 
     "caca", "concise", "test"] 
extract(arr, target_start, target_end) 
    #=> [] 

Erklärung

Betrachten

arr = ["hello", ":desc:", "claire", "et", "concise", ":/desc", 
     ":desc:", "claire", "caca", "concise", "test"] 

und target_start und target_end wie im Beispiel angegeben. Die Schritte sind wie folgt.

b = arr.select { |s| (s == target_start)..(s == target_end) ? true : false } 
    #=> [":desc:", "claire", "et", "concise", ":/desc", ":desc:", "claire", 
    # "caca", "concise", "test"] 

Dieser erste Schritt, der flip-flop operator Verwendung von Ruby macht, gibt ein Array, das alle Elemente der arr mit Ausnahme derjenigen, die precede enthält die erste ":desc:" und diejenigen, die zwischen zwischen jedem ":/desc" und dem ersten ":desc:" sind, die folgt.

Als nächstes verwenden wir Enumerable#slice_when (neu in Ruby v2.2), um einen Enumerator zu erstellen, der b wie gewünscht schneidet und dann diesen Enumerator in ein Array konvertiert.

c = b.slice_when { |s,t| [s, t] == [target_end, target_start] } 
    #=> #<Enumerator: #<Enumerator::Generator:0x00000001dd4f18>:each> 
d = c.to_a 
    #=> [[":desc:", "claire", "et", "concise", ":/desc"], 
    # [":desc:", "claire", "caca", "concise", "test"]] 

Der letzte Schritt ist die letzte Reihe von d zu entfernen, wenn sie nicht mit ":/desc", was hier der Fall ist, nicht beenden. Wir können dafür verwenden, aber nicht direkt, da es das popping-Element zurückgibt, was dazu führen würde, dass die Methode auch diesen Wert zurückgibt. Wenn wir es jedoch in einem Object#tap Block verwenden, ist alles in Ordnung.

d.tap { |a| a.pop unless a.last.last == target_end } 
    #=> [[":desc:", "claire", "et", "concise", ":/desc"]]