Move to front of array


#1

Hi,

I have an array of Strings, and I’d like to find one of the items by
Regexp, then move that item to the front of the array, eg:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/i
}))) if array.find { |i| i =~ /cool/i }

Better/cleaner/shorter ways to do it?

-Payton


#2

Payton S. wrote:

I have an array of Strings, and I’d like to find one of the items by
Regexp, then move that item to the front of the array, eg:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/i
}))) if array.find { |i| i =~ /cool/i }

In 1.8.x:

require ‘enumerator’
if f = array.enum_for(:each_index).find { |i| array[i] =~ /cool/i }
array.unshift array.delete_at(f)
end

In 1.9.x you can do:

array.unshift array.delete_at(array.index { |x| x =~ /cool/i })


#3

On 12/21/05, Payton S. removed_email_address@domain.invalid wrote:

-Payton

array = %w(a B c d Cool e f G)
if cool = array.find { |i| i =~ /cool/i }
array.delete(cool)
array.unshift(cool)
end

?

Jacob F.


#4

On Dec 21, 2005, at 3:08 PM, Payton S. wrote:

-Payton

I don’t know about shorter but…

require ‘enumerator’
array = %w(a B c d Cool e f G)
index = array.to_enum(:each_index).find { |i| array[i] =~ /cool/i }
array.unshift( array.delete_at( index ) )


#5

Payton S. wrote:

Hi,

I have an array of Strings, and I’d like to find one of the items by
Regexp, then move that item to the front of the array, eg:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/i
}))) if array.find { |i| i =~ /cool/i }

Better/cleaner/shorter ways to do it?

This should work (but without error checking):

array.unshift(array.delete(array.grep(/cool/i).first))

-Payton

E


#6

Try this:

array1 = %w(a B c d Cool e f G)
array1.partition {|i| i =~ /cool/i}
array2=array1.partition {|i| i =~ /cool/i}
array2.flatten #or array2.flatten!


#7

On Wed, 21 Dec 2005 20:08:34 -0000, Payton S. removed_email_address@domain.invalid
wrote:

Hi,

I have an array of Strings, and I’d like to find one of the items by
Regexp, then move that item to the front of the array, eg:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/i
}))) if array.find { |i| i =~ /cool/i }

Better/cleaner/shorter ways to do it?

Not sure about better or cleaner, but here’s a couple of ideas:

Without error checking:

(ary - [t = ary.detect { |$_| ~/cool/i }]).unshift(t)

If you know there’s only one of each element to start with, you could
do:

(ary.unshift(ary.detect { |$_| ~/cool/i })).uniq

still without error checking, or:

ary.unshift(ti).uniq if ti = ary.detect { |$_| ~/cool/i }

To ensure you don’t get a nil at the start, or

ary.unshift(*ti).uniq if ti = ary.grep(/cool/i)

to ensure you don’t get flamed for using $_ :wink:

Of course, if you have dupes to start with this won’t work.


DISCLAIMER


I am not advocating the default input space, I don’t think you should
use
$_, it’s only a few extra characters to do it the ‘proper way’, I just
wanted to go for brevity here.


:slight_smile:


#8

On Dec 21, 2005, at 12:08 PM, Payton S. wrote:

-Payton

array.unshift(array.delete_at(array.index(“Cool”)))

=> [“Cool”, “a”, “B”, “c”, “d”, “e”, “f”, “G”]

-Ezra


#9

Jacob F. wrote:

Better/cleaner/shorter ways to do it?

-Payton

array = %w(a B c d Cool e f G)
if cool = array.find { |i| i =~ /cool/i }
array.delete(cool)
array.unshift(cool)
end

array.unshift(array.delete(“Cool”))

Regards,

Dan


#10

On Wed, 21 Dec 2005 21:13:11 -0000, Ezra Z.
removed_email_address@domain.invalid
wrote:

array.unshift(array.delete_at(array.index(“Cool”)))

=> [“Cool”, “a”, “B”, “c”, “d”, “e”, “f”, “G”]

Nice :slight_smile: I missed ‘index’. It led me to this:

([ary[ary.index("Cool")]] + ary).uniq

Then from there to

([*ary.grep(/Cool/)] + ary).uniq

Obviously it’s useless line-noise and doesn’t perform well I expect, but
I
enjoy this game :slight_smile:


#11

On 12/21/05, Dan D. removed_email_address@domain.invalid wrote:

Try this:

array1 = %w(a B c d Cool e f G)
array1.partition {|i| i =~ /cool/i}
array2=array1.partition {|i| i =~ /cool/i}
array2.flatten #or array2.flatten!

So something like:

array = %w(a B c d Cool e f G)
array = array.partition{ |el| el =~ /cool/i }.flatten

That certainly would work (assuming that only one element matches the
condition), and has a certain elegance. I wonder if there’s a
significant performance penalty?

It also gave me this idea:

class Array
def bubble_up
score = lambda{ |a| (yield a) ? -1 : 1 }
self.sort_by { |a,b| score[a] <=> score[b] }
end

def bubble_up!( &proc )
  self.replace(self.bubble_up( &proc ))
end

end

array = array.bubble_up! { |el| el =~ /cool/i }

Jacob F.


#12

That certainly would work (assuming that only one element matches the
condition),

seems to work on multiple matches:

array = %w(a B c d Cool e f G Cool Cooler)
=> [“a”, “B”, “c”, “d”, “Cool”, “e”, “f”, “G”, “Cool”, “Cooler”]
array = array.partition{ |el| el =~ /cool/i }.flatten!
=> [“Cool”, “Cool”, “Cooler”, “a”, “B”, “c”, “d”, “e”, “f”, “G”]

I was uncertain about needing parenthesis before applying flatten but it
makes perfect sense:

array = (array.partition{ |el| el =~ /cool/i }).flatten!


#13

On 12/21/05, Dan D. removed_email_address@domain.invalid wrote:

That certainly would work (assuming that only one element matches the
condition),

seems to work on multiple matches:

Yeah, I realized that as I was working through my variation, but never
made it back up to change the text. :slight_smile:

I was uncertain about needing parenthesis before applying flatten but it makes perfect sense:

array = (array.partition{ |el| el =~ /cool/i }).flatten!

You don’t need the bang here, however. Since Array#partition will
always create nested arrays, you won’t run into the flatten! returning
nil bug, but it is still wasted effort to change a temporary array in
place.

Jacob F.


#14

Payton S. wrote:

Hi,

I have an array of Strings, and I’d like to find one of the items by
Regexp, then move that item to the front of the array, eg:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/i
}))) if array.find { |i| i =~ /cool/i }

Better/cleaner/shorter ways to do it?

I’m going to do something controversial and suggestion a completely
imperative method:

array.each_with_index {|o,i| array.unshift(array.delete_at(i)) if o =~
/cool/i}

Oddly, this works with multiple instances of Cool. I take it that
#each_with_index doesn’t miss a beat wrt the modifications.

Oh, and if you’ll be doing it more than once in your code, then put the
method where it belongs:
class Array
def bubble_if!(regex=nil)
if block_given? then each_with_index {|o,i| unshift(delete_at(i)) if
yield o}
else super() {|o| =~ regex}
end
end
end
array.bubble_if!(/cool/i)

Devin
YMMV.


#15

I like it! I used to puzzle over why they named it “inject” but then I
realized that injection is like mainlining a variable through each
element of an enum. The injected variable is often used as some type of
accumulator but this convention is not necessary; the injected variable
can be used for any purpose.


#16

Just for the sake of completeness, here is Devin’s suggestion, with
syntax errors corrected:

class Array
def bubble_if!(regex=nil)
if block_given? then each_with_index {|o,i| unshift(delete_at(i))
if yield o}
else bubble_if! {|o| o =~ regex}
end
end
end

Thanks for the suggestions, everyone!

-Payton


#17

Ezra Z. wrote:

On Dec 21, 2005, at 12:08 PM, Payton S. wrote:

array = %w(a B c d Cool e f G)
array.unshift(array.slice!(array.index(array.find { |i| i =~ /cool/ i
}))) if array.find { |i| i =~ /cool/i }

Better/cleaner/shorter ways to do it?

array.unshift(array.delete_at(array.index(“Cool”)))

array.sort_by{|x| x=~/cool/i ? 0 : 1}


#18

No inject solution?

here it is:

array = %w(a B c d Cool e f G)
p array.inject([]){|s, o| o =~ /cool/i ? [o, *s] : s << o}

=> [“Cool”, “a”, “B”, “c”, “d”, “e”, “f”, “G”]

cheers

Simon


#19

Ron M wrote:

array.sort_by{|x| x=~/cool/i ? 0 : 1}

+1