Array sort not sorting properly - ruby

I'm having a problem sorting an array. My expected result is:
#list = [
["2 1/8\"", "23 13/32\"", "stile", "2"],
["2 1/8\"", "11 5/32\"", "rail", "6"],
["2 1/8\"", "7 13/32\"", "stile", "4"]
]
This is just an example since the array is dynamic and data are always changing, but the one thing that is constant is that most if not all single digit measurements end up at the beginning.
And here's the code I'm using:
#list = #list.sort {|a,b| b[1]<=>a[1]}
Here's the actual output from the code.
#list
# => [
# ["2 1/8\"", "7 13/32\"", "stile", "2"],
# ["2 1/8\"", "23 13/32\"", "rail", "6"],
# ["2 1/8\"", "11 5/32\"", "stile", "4"]
# ]
Any ideas why?

If the goal is to sort by the first numeric value of the second value of each array (7, 23, 11) converting the string to integer in the sort could be a solution:
#list = #list.sort {|a,b| b[1].to_i <=> a[1].to_i }
You can also make it shorter by using sort!:
#list.sort! {|a,b| b[1].to_i <=> a[1].to_i }

Related

Looking to convert information from a file into a hash Ruby

Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.

Ruby bad array to Hash

I have a ugly array that looks like this
["advert 0", "[1404915231, 1404920520]", "advert 4", "[1404915231]", "advert 5", "[1404915231]", "advert 6", "[1404915231]", "advert 7", "[1404915231]", "advert 8", "[1404915231]", "advert 9", "[1404915231]"]
I've tried to get it to look like this but I have been unsuccessful.
{advert1: [1404915231, 1404920520], advert4: [1404915231]}
Thanks
Here is a way(if it helps you) :-
require 'yaml'
arr = [
"advert 0", "[1404915231, 1404920520]",
"advert 4", "[1404915231]", "advert 5",
"[1404915231]", "advert 6", "[1404915231]",
"advert 7", "[1404915231]", "advert 8",
"[1404915231]", "advert 9", "[1404915231]"
]
Hash[arr.each_slice(2).map { |a, b| [a.gsub(/\s+/,'').to_sym, YAML.load(b)] }]
# => {:advert0=>[1404915231, 1404920520],
# :advert4=>[1404915231],
# :advert5=>[1404915231],
# :advert6=>[1404915231],
# :advert7=>[1404915231],
# :advert8=>[1404915231],
# :advert9=>[1404915231]}
Enumerable#each_slice(2) - Is sending 2 items from the collection, to the Enumerable#map block. Now OP wants, string to be converted as symbols. Thus, I used first, to removes the white spaces in between strings. It is done by String#gsub method. I passed the regex, /\s+/ as an Argument to the method #gsub, which as per the regex, will find each white space and replace them with empty strings(''). That means
"advert 0".gsub(/\s+/,'') # => "advert0"
Now as OP wants, all keys to be symbols, I apply String#to_sym.
"advert0".to_sym # => :advert0
So finally, I need to covert all string arrays to array, thus YAML::load will be helpful.
YAML::load "[1404915231, 1404920520]" # => [1404915231, 1404920520]
Till now what I said, will give us -
arr.each_slice(2).map { |a, b| [a.gsub(/\s+/,'').to_sym, YAML.load(b)] }
# => [[:advert0, [1404915231, 1404920520]],
# [:advert4, [1404915231]],
# [:advert5, [1404915231]],
# [:advert6, [1404915231]],
# [:advert7, [1404915231]],
# [:advert8, [1404915231]],
# [:advert9, [1404915231]]]
Now, I will make it a Hash. Look this Hash[ key, value, ... ] → new_hash.
Hash[:a,[1],:b, [2,3]] # => {:a=>[1], :b=>[2, 3]}
Hope it helps.
Arup gives a great answer, but if you're looking for alternatives, here are some thoughts I had:
Break down the problem
Let's break down the problem. We have ugly_array, and we want to use its even elements ("advert 0", "advert 2") as keys in a Hash, and its odd elements as the corresponding values. We want to transform both the keys (into symbols) and the values (into arrays of numbers) on the way.
Create a (temporary) Hash
A useful method in Ruby is Hash[], which will create a Hash using its odd arguments as keys and even arguments as values. For example:
Hash[ :foo, 1, :bar, 2 ]
# => { :foo => 1, :bar => 2 }
In order to use the items in ugly_array as separate arguments, we put * ("splat") before it:
ugly_hash = Hash[ *ugly_array ]
This is equivalent to Hash[ "advert 0", "[1404915231, 1404920520]", "advert 4", ... ], and it gives us this Hash:
{ "advert 0" => "[1404915231, 1404920520]",
"advert 4" => "[1404915231]",
"advert 5" => "[1404915231]",
# ...
}
Transform the keys
Now we need to transform the keys and values. First, the keys. You want to turn the string "advert 0" into the symbol :advert0, which we can do like this:
"advert 0".gsub(" ", "").to_sym
# => :advert0
To clarify gsub(" ", "") replaces all spaces (" ") with nothing, which effectively removes them. We could also use a regular expression like /\s+/, but it looks like the extra flexibility isn't really needed here. Now we have the string "advert0", and to_sym converts it into the symbol :advert0.
Transform the values
We want to turn a string like "[1404915231, 1404920520]" into an array of numbers like [1404915231, 1404920520]. There are a lot of different ways to do this, but one way that doesn't require using a module like JSON or YAML is this:
"[1404915231, 1404920520]".scan(/\d+/).map(&:to_i)
# => [ 1404915231, 1404920520 ]
This does two things. First, scan(/\d+/) uses a regular expression to find sequences of consecutive digits (\d) in the string and returns them as an array, yielding an array of strings: ["1404915231", "1404920520"]. Then we use map(&:to_i) to call the to_i method on each of the strings, yielding an array of numbers.
Transform the temporary Hash
Now that we know how to transform the keys and values, we can take our temporary Hash and build a new, transformed Hash from it. There are other ways to do this, but I'm fond of Enumerable#each_with_object. Suppose we do this:
ugly_hash.each_with_object({}) do |(key, val), hsh|
hsh[key] = val
end
Inside the block, hsh is the new, empty Hash ({}) we gave as the argument to each_with_object, and in each iteration we add val to it with the key key. This yields:
{ "advert 0" => "[1404915231, 1404920520]",
"advert 4" => "[1404915231]",
# ...
}
It looks exactly the same! But you can probably see that since we have key and val inside the block, we can transform them before using them.
Bring it all together
pretty_hash = Hash[ *ugly_array ].each_with_object({}) do |(key, val), hsh|
key = key.tr(" ", "").to_sym
val = val.scan(/\d+/).map(&:to_i)
hsh[key] = val
end
As you can see, this is the same code, except inside the block we're transforming key and val before using them to add an element to the Hash. And it gives us just what we're looking for:
p pretty_hash
# => { :advert0 => [ 1404915231, 1404920520 ],
# :advert4 => [ 1404915231 ],
# :advert5 => [ 1404915231 ],
# ...
# }
Apart from being relatively readable and not requiring external libraries, this method has the distinct advantage of iterating over the original data exactly once and performing only one (composite) operation on each key and value.
If you're so inclined, you can reduce the inside of the block to a single line, but be aware of how it impacts readability:
pretty_hash = Hash[*ugly_array].each_with_object({}) do |(key, val), hsh|
hsh[ key.tr(" ", "").to_sym ] = val.scan(/\d+/).map(&:to_i)
end
I hope that's helpful!
I've assumed you want the result:
{ advert0: [1404915231, 1404920520], advert4: [1404915231] }
That is, I assume you meant advert0 where you wrote advert1; specifically, that you wish to select those pairs "advert x", arr (arr being an array) that correspond to given values of x (here 0 and 4) and convert the collection of those pairs to a hash. If this is not want you want to do, you need read no further.
You could do this as follows:
Code
def doit(arr, *vals_at)
arr.each_slice(2)
.map {|s1,s2|
[s1.split.last.to_i,[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]]}
.to_h
.values_at(*vals_at)
.to_h
end
Example
arr = ["advert 0", "[1404915231, 1404920520]",
"advert 4", "[1404915231]",
"advert 5", "[1404915231]"
]
(We don't need the rest of arr.)
doit(arr, 0, 4)
#=> {:advert0=>[1404915231, 1404920520], :advert4=>[1404915231]}
Explanation
For the value of arr above, let:
b = a.each_slice(2)
#=> #<Enumerator: ["advert 0", "[1404915231, 1404920520]",
# "advert 4", "[1404915231]",
# "advert 5", "[1404915231]"]:each_slice(2)>
c = b.map
#<Enumerator: #<Enumerator: ["advert 0", "[1404915231, 1404920520]",
# "advert 4", "[1404915231]",
# "advert 5", "[1404915231]"]:each_slice(2)>:map>
c may be thought of as a "compound" enumerator. When we add a block, each will pass each value of the following array into the block:
c.to_a
#=> [["advert 0", "[1404915231, 1404920520]"],
# ["advert 4", "[1404915231]"],
# ["advert 5", "[1404915231]"]]
Note
b.to_a == c.to_a #=> true
Next,
d = c.each { |s1,s2| [s1.split.last.to_i,
[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]] }
#=> [[0, [:advert0, [1404915231, 1404920520]]],
# [4, [:advert4, [1404915231]]],
# [5, [:advert5, [1404915231]]]]
Note that c.each is equivalent to b.map:
d == b.map { |s1,s2| [s1.split.last.to_i,
[s1.tr(' ','').to_sym, s2.scan(/\d+/).map(&:to_i)]] }
#=> true
Let's pause a moment to see what's going on here. The first value the enumerator c passes into the block (["advert 0", "[1404915231, 1404920520]"]) is split into the two block variables by disambiguation:
s1 #=> "advert 0"
s2 #=> "[1404915231, 1404920520]"
Then:
s1.split.last.to_i
#=> 0
r = s2.scan(/\d+/)
#=> ["1404915231", "1404920520"]
r.map(&:to_i)
#=> [1404915231, 1404920520]
Ergo, the first element passed into the block:
["advert 0", "[1404915231, 1404920520]"]
is mapped to:
[0,[1404915231, 1404920520]]
Similarly, the second and the third elements of c are respectively mapped to:
[4=>[:advert4, [1404915231]]
and
[5=>[:advert5, [1404915231]]
Now that we have d,
e = d.to_h # or Hash[d] for Ruby versions < 2.0
#=> {0=>[:advert0, [1404915231, 1404920520]],
# 4=>[:advert4, [1404915231]],
# 5=>[:advert5, [1404915231]]}
Aha! Now you can see why I constructed e as I did:
f = e.values_at(0,4)
#=> [[:advert0, [1404915231, 1404920520]], [:advert4, [1404915231]]]
f.to_h
#=> {:advert0=>[1404915231, 1404920520], :advert4=>[1404915231]}

Obtain a set of numbers from a sequence of arrays of strings in ruby

I have an array of strings which is as follows
b=["yesterday: 40", "today: 20", "change: +3", "changes_2: 10.00%", "high: 30", "low: 20"]
How would I obtain the set of values 40,20,+3, 10.00%, 30 and 20 from that array
I've done
c = b.map {|n| n.split(" ")}
That returns
[["yesterday:", "40"], ["today:", "20"], ["change:", "+3"], ["changes_2:", "10.00%"], ["high:", "30"], ["low:", "20"]]
Just index the split array before returning it in the block:
> c = b.map {|n| n.split(": ")[1]}
=> ["40", "20", "+3", "10.00%", "30", "20"]

How to join second or third dimension array items without affecting first dimension

I am trying to create an array that shows every digit permutation of a given number input. With a given input "123", the array should look like this:
["123", "132", "213", "231", "312", "321"]
I can get an array containing arrays of the separate digits:
a = []
"123".split('').each {|n| a.push(n) }
arraycombinations = a.permutation(a.length).to_a
# => [["1", "2", "3"], ["1", "3", "2"], ["2", "1", "3"], ["2", "3", "1"], ["3", "1", "2"], ["3", "2", "1"]]
but I cannot figure out how to join the second or third dimensions of arraycombinations while preserving the first dimension.
Each of these attempts failed:
arraycombinations.map {|x| print arraycombinations.join("") }
arraycombinations.map {|ar| ar.split(",") }
arraycombinations.each {|index| arraycombinations(index).join("") }
How can I isolate the join function to apply to only the second dimension within a multidimensional array?
Assuming you already have an array of arrays such as
a = [["1","2","3"],["1","3","2"],["2","1","3"],["2","3","1"], ["3","1","2"],["3","2","1"]]
a.map { |i| i.join}
#=>["123", "132", "213", "231", "312", "321"]
It's simple really
"123".split("").permutation.to_a.map { |x| x.join }
Let me explain a bit:
"123".split("") gives you an array ["1","2","3"]
permutation.to_a gives you array of arrays [["1","2","3"], ["2","1","3"] ... ]
then you must join each of those arrays inside with map { |x| x.join }
and you get the required end result.
Like this:
arraycombinations.map(&:join)
# => ["123", "132", "213", "231", "312", "321"]

Only push specific substring to array in Ruby

I have an array that I am looping through and pushing specific values to a separate array. EX:
first_array = ["Promoter: 8", "Passive: 7"]
I want to push every value that is an integer to a separate array, that would look like this in the end:
final_array = [8,7]
It would be nice for the values in the new array to be integers. I can't think of a way to push all numeric values within a string to a new array, but what would be the best option to do what I am wanting?
first_array.map{|s| s[/\d+/].to_i}
# => [8, 7]
first_array.map{|a| a.match(/\d+/)}.compact.map{|a| a[0].to_i }
Use a regex to grab the integers,
compact the blank spaces from the strings with no integers, and
convert them all to ints
And I have to add this super short but complicated one-liner solution:
a = ["Promoter: 8", "Passive: 7"]
p a.grep(/(\d+)/){$&.to_i} #=> [8,7]
Your question, as formulated, has an easy practical answer, already provided by others. But it seems to me, that your array of strings
a = ["Promoter: 8", "Passive: 7"]
envies being a Hash. So, from broader perspective, I would take freedom of converting it to a Hash first:
require 'pyper' # (type "gem install pyper" in your command line to install it)
hsh = Hash[ a.τBmm2dτ &/(\w+): *(\d+)/.method( :match ) ]
#=> {"Promoter"=>"8", "Passive"=>"7"}
# (The construction of #τBmm2dτ Pyper method will be explained in the appendix.)
Now, having your input data in a hash, you can do things with them more easily, eg.
hsh.τmbtiτ
#=> [8, 7]
APPENDIX: Explanation of the Pyper methods.
Pyper methods are similar to Lisp #car/#cdr methods in that, that a combination of
letters controls the method behavior. In the first method, #τBmm2dτ:
τ - opening and ending character
m - means #map
B - means take a block
2 - means first 3 elements of an array
d - means all elements except the first one (same meaning as in #cdr, btw.)
So, in #τBmm2dτ, Bm applies the block as follows:
x = ["Promoter: 8", "Passive: 7"].map &/(\w+): *(\d+)/.method( :match )
#=> [#<MatchData "Promoter: 8" 1:"Promoter" 2:"8">, #<MatchData "Passive: 7" 1:"Passive" 2:"7">]
# Which results in an array of 2 MatchData objects.
Then, m2d chars map (m) the MatchData objects using 2 and d chars. Character 2 gives
x = x.map { |e| e.to_a.take 3 }
#=> [["Promoter: 8", "Promoter", "8"], ["Passive: 7", "Passive", "7"]]
and d removes the first element from each:
x = x.map { |e| e.drop 1 }
#=> [["Promoter", "8"], ["Passive", "7"]]
In the secon method, #τmbtiτ, m means again #map, b means take the second element, and ti means convert it to Integer:
{"Promoter"=>"8", "Passive"=>"7"}.to_a.map { |e| Integer e[1] }
#=> [8, 7]
If the integer part of each string in (which look like members of a hash) is always preceded by at least one space, and there is no other whitespace (other than possibly at the beginning of the string), you could do this:
first_array = ["Promoter: 8", "Passive: 7"]
Hash[*first_array.map(&:split).flatten].values.map(&:to_i) # => [8,7]
map first_array => [["Promoter:", "8"], ["Passive:", "7"]]
flatten => ["Promoter:", "8", "Passive:", "7"]
convert to hash => {"Promoter:" => "8", "Passive:" => "7"}
get hash values => ["8", "7"]
convert to ints => [8, 7]
Note the need for the splat:
Hash[*["Promoter:", "8", "Passive:", "7"]]
=> Hash["Promoter:", "8", "Passive:", "7"]
=> {"Promoter:" => "8", "Passive:" => "7"}

Resources