Map an array without losing unchanged elements - arrays

I split the following string:
str = "0001110010101000011111100001110010000010000000011101011100001"
into an array of grouped "0"s and "1"s:
str_arr = []
str.scan(/((.)\2*)/) { |x| str_arr.push(x[0]) }
str_arr # => ["000", "111", "00", "1", "0", "1", "0", "1", "0000", "111111", "0000", "111", "00", "1", "00000", "1", "00000000", "111", "0", "1", "0", "111", "0000", "1"]
I want to replace elements which contain the character "1" and have a length of less than 3 characters with the character ".", but retain the other elements.
I reached this far in my code:
str_arr.map!{|x| if x.include?("1") && x.length < 3; x = "." end}
str_arr # => [nil, nil, nil, ".", nil, ".", nil, ".", nil, nil, nil, nil, nil, ".", nil, ".", nil, nil, nil, ".", nil, nil, nil, "."]
The last step is where I trip up. I converted the elements I wanted to to ".", but converted the remaining elements to nil.
Is there a way to replace the elements I want to change without affecting the remaining elements?

The reason they are getting converted is because if statements in ruby return nil if the condition wasn't met:
if false; end # => nil
To fix this, just use the ternary operator (?:) in place of if then end and provide the original value as alternative:
str_arr.map!{ |x| x.include?('1') && x.length < 3 ? '.' : x }
Bonus:
A simpler regex, which will do the same:
0+|1+

Using each_index would be appropriate in this case.
str_arr.each_index{|i| str_arr[i] = "." if str_arr[i] =~ /\A1{,2}\z/}
# => ["000", "111", "00", ".", "0", ".", "0", ".", "0000", "111111", "0000", "111",
# "00", ".", "00000", ".", "00000000", "111", "0", ".", "0", "111", "0000", "."]

Related

How to check if hash keys match value from array

I have:
arr = ['test', 'testing', 'test123']
ht = {"test": "abc", "water": "wet", "testing": "fun"}
How can I select the values in ht whose key matches arr?
ht_new = ht.select {|hashes| arr.include? hashes}
ht_new # => "{"test": "abc", "testing": "fun"}"
Additionally, how can we return values from:
arr = ["abc", "123"]
ht = [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}, {"key": "doremi", "value": "rain"}}]
output # => [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}]
Only a slight change is needed:
ht.select { |k,_| arr.include? k.to_s }
##=> {:test=>"abc", :testing=>"fun"}
See Hash#select.
The block variable _ (a valid local variable), which is the value of key k, signifies to the reader that it is not used in the block calculation. Some prefer writing that |k,_v|, or some-such.
One option is mapping (Enumerable#map) the keys in arr:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun", "test123"=>nil}
If you want to get rid of pairs with nil value:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] if ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun"}
This is an option for the last request:
ht.select{ |h| h if h.values.any? { |v| arr.include? v} }
# or
arr.map { |e| ht.find { |h| h.values.any?{ |v| v == e } } }
#=> [{:key=>"abc", :value=>"test"}, {:key=>"123", :value=>"money"}]
A straightforward way is:
ht.slice(*arr.map(&:to_sym))
# => {:test => "abc", :testing => "fun"}

Split string in ruby when character is different to previous character [duplicate]

This question already has answers here:
How to split a string of repeated characters with uneven amounts? Ruby
(2 answers)
Closed 5 years ago.
I would like to split a string when a character changes.
For example "aabbbc226%%*" should be split into an array like this ["aa", "bbb", "c", "22", "6", "%%", "*"]
Heres what I have right now
def split_when_char_change(str)
array = Array.new
chars = str.split('')
chars.each { |c|
array.push c
}
array
end
split_when_char_change("aabbbc226%%*")
I am getting this output: ["a", "a", "b", "b", "b", "c", "2", "2", "6", "%", "%", "*"] which is wrong.
How can I get my desired array?
Here is a one liner using chunk, map and join:
"aabbbc226%%*".chars.chunk(&:itself).map{|_,c| c.join}
# => ["aa", "bbb", "c", "22", "6", "%%", "*"]
"aabbbc226%%*".scan(/((.)\2*)/).to_h.keys
# => ["aa", "bbb", "c", "22", "6", "%%", "*"]
or
"aabbbc226%%*".scan(/((.)\2*)/).map(&:first)
# => ["aa", "bbb", "c", "22", "6", "%%", "*"]

How do I find consecutive strings in my array that match a regex?

I'm using Ruby 2.4. I want to find consecutive tokens in my array of strings that match a regular expression. So if my regex is
/\p{L}/
and my array is
["2917", "m", "neatty", "fff", "46", "u", "28", "56"]
I would want the result to be
["m", "neatty", "fff"]
However, my attempt to do this has failed (notice the "neatty" token is repeated) ...
2.4.0 :020 > arr = ["2917", "m", "neatty", "fff", "46", "u", "28", "56"]
=> ["2917", "m", "neatty", "fff", "46", "u", "28", "56"]
2.4.0 :021 > arr.each_cons(2).select{|pair| pair.all?{|elem| elem =~ /\p{L}/ }}.flatten
=> ["m", "neatty", "neatty", "fff"]
How do I find consecutive tokens in an array that match a pattern that also don't repeat?
If r is your regex then use chunk_while
arr.chunk_while { |a,b| a[r] && b[r] }.select { |arr| arr.size > 1 }
#=> [["m", "neatty", "fff"]]
You can also use slice_when to find borders of the sub array that bound the condition:
> arr.slice_when {|x,y| !x[reg] || !y[reg] }.select {|e| e.length>1}
=> [["m", "neatty", "fff"]]
arr = ["2917", "m", "neatty", "fff", "46", "u", "28", "56", "hi", "%ya!"]
r = /\p{L}/
arr.each_with_object([[]]) { |s,a| s.match?(r) ? (a.last << s) : a << [] }.
reject { |a| a.size < 2 }
#=> [["m", "neatty", "fff"], ["hi", "%ya!"]]

ruby sorting array - moving matched elements to the beginning

If I have an array: array = ["ruby", "code", "library"]. How can I move matched /^library$/ elements to the beginning. So array will look like this: array = ["library", "ruby", "code"]
it could be done in a number of ways. This is one
array = ["ruby", "code", "library"]
array.partition { |element| element.match /^library$/ }.flatten
Just out of curiosity:
[:select, :reject].map do |m|
["ruby", "code", "library"].public_send(m, &(/^library/.method(:=~)))
end.reduce :|
def move_to_front(arr, pattern)
mi = matching_indices(arr, pattern)
return arr unless mi
a = arr.dup
mi.reverse_each.with_object([]) { |i,b| b.unshift(a.delete_at(i)) }.concat(a)
end
def matching_indices(arr, pattern)
arr.each_index.select do |i|
case pattern
when Regexp then arr[i] =~ pattern
when Proc then pattern[arr[i]]
else (arr[i] == pattern)
end
end
end
move_to_front ["ruby", "code", "library"], /\Alibrary\z/
#=> ["library", "ruby", "code"]
move_to_front ["ruby", "library", "code", "library"], "library"
#=> ["library", "library", "ruby", "code"]
move_to_front ["ruby", "libraries", "code", "library"], /librar(?:ies|y)/
#=> ["libraries", "library", "ruby", "code"]
move_to_front ["ruby", "libraries", "code", "library"], /\Alibrar/
#=> ["libraries", "library", "ruby", "code"]
move_to_front ["ruby", "libraries", "code", "library"],
->(str) { str =~ /librar(?:ies|y)/ }
#=> ["libraries", "library", "ruby", "code"]
move_to_front ("1".."9").to_a, /[13579]/
#=> ["1", "3", "5", "7", "9", "2", "4", "6", "8"]
move_to_front ("1".."9").to_a, ->(n) { n.to_i.odd? }
#=> ["1", "3", "5", "7", "9", "2", "4", "6", "8"]
move_to_front ("1".."9").to_a, ->(n) { false }
#=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
move_to_front ("1".."9").to_a, ->(n) { true }
#=> ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
Note:
matching_indices ["ruby", "libraries", "code", "library"], /librar(?:ies|y)/
#=> [1, 3]
The method move_to_front preserves the order of those elements that are moved and those that are not moved.
Three for one cent.
array.inject([]){|a,e| e[/^library/] ? a.unshift(e) : a<<e}
and
array & ["library"] | array
In case array contains the search element multiple times it becomes
array.find_all{ |e| e[/^library/] } + array.reject{ |e| e[/^library/] }
If you hate to use the array variable twice it can also like this
[array].map{|a| a & ["library"] | a}.flatten
The last one: using grep
array.grep(/library/) + array.grep( /^(?!library)/)

return keys from array having same values in ruby

How to find out elements of array having same value_entries. As code is in ruby, looking better approach.
Input
"block_device": {
"sda": {
"size": "83886080",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdb": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdc": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
}
}
Sample Code Block:
devicesForRaid = []
deviceHolder = []
node['block_device'].each do |deviceName,deviceProperty|
deviceHolder.push(deviceName,deviceProperty['size']) #['sda'=>'83886080','sdb'=>'16384','sdc'=>'16384']
end
deviceHolder.each do | deviceName,deviceSize|
# how to get deviceName who all having same size
if(deviceSize_match_found){
devicesForRaid.push(deviceName)
}
end
Expected Output:
devicesForRaid = ['sdb','sdc']
Trial way:
using stack,
push 1st element onto stack, and comparing with rest of array element.
if match found, push that element onto stack.
Sample code block completion or better code highly appreciated.
You can do this:
input_hash[:block_device].each_with_object({}) { |(k,g),h|
h.update(g[:size]=>[k]) { |_,o,n| o+n } }
#=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
This uses the form of Hash#update (aka merge!) that employs the block:
{ |_,o,n| o+n }
to determine the values of keys that are present in both hashes being merged.
res = Hash.new { |h, k| h[k] = [] }
node['block_device'].each{|k, v| res[v[:size]]<<k}
gives:
=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
I guess you want to look through res for values with length of 2 or more
res.to_a.select{|k, v| v.size > 1}
You can do it this way (assuming block_device is a key in your input data hash):
hash = input_data[:block_device]
new_hash = Hash.new{ |h, k| h[k] = [] }
hash.each do |k, v|
new_hash[v[:size]] << k
end
p new_hash
# => {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
From this new_hash, you can extract your required data easily.
e.g. if you want to extract the elements that has a size more than 1, you can do this:
p new_hash.select { |k,v| v.length > 1 }.values.flatten
# => [:sdb, :sdc]
How about using group_by?
node[:block_device]
.group_by {|device, attributes| attributes[:size] }
.map {|size, devices| devices.size > 1 ? devices.map(&:first) : nil }
.compact
.flatten
=> [:sdb, :sdc]
I think this way is easy to understand what you are doing.

Resources