assigning mapped array directly - ruby

Trying to map stuff I read from a file into a
list of arrays that have an integer and a string
It doesn't seem to to work quite right, because I see
two strings per array, as opposed to an integer
and a string.
list_of_elems = []
File.foreach("line_counts.txt") do |line|
list_of_elems << arr = line.split(/\s+/).map! { |e, i| i == 0 ? e.to_i : e }
end
list_of_elems.each_with_index do |e, i|
if i > 10
break
end
p e
end

If I understand well, you want to take a file like this:
test 20 foo
7 1 bar 6
And get this:
[["test", 20, "foo"],
[7, 1, "bar", 6]]
Right?
Then you can use:
list_of_elems = []
File.foreach("line_counts.txt") do |line|
list_of_elems << line.split(/\s+/).map {|e| e =~ /^(?:+|-)?\d+$/ ? e.to_i : e }
end
Or:
list_of_elems = File.read("line_counts.txt").split("\n").map do |line|
line.split(/\s+/).map {|e| e =~ /^(?:+|-)?\d+$/ ? e.to_i : e }
end

This may not be too relevant, but
list_of_elems.each_with_index do |e, i|
if i > 10
break
end
p e
end
can be replaced with
list_of_elems[0..10].each {|e| p e}

Your problem is that map! only passes one argument to the block; hence i is always nil, i == 0 always fails, and to_i is never called. I think you want something more like this:
list_of_items = File.open('line_counts.txt').collect do |line|
line.split(/\s+/).inject([ ]) { |a, e| a.push(a.length == 0 ? e.to_i : e) }
end
The a.length == 0 essentially replaces your faulty i == 0 check and converts the first component of the line to an integer.
If linecounts.txt looks like this:
1 one
2 two
Then list_of_items ends up looking like this:
[[1, "one"], [2, "two"]]
and that seems to be what you're after.

This should work too:
list_of_elems = File.foreach("line_counts.txt").map |line|
line.split.map.with_index { |e, i| i == 0 ? e.to_i : e }
end
I use map instead of each for the output because you can hit tab twice in textmate and it builds the block for you.
list_of_elems.map { |e| puts e.to_s }

Related

Manipulating symbols in ruby

I am trying to take an array of symbols,
a = [:apple, :banana ,:grape, :black]
and add a string at the end of each symbol depending on the last letter. If the symbol ends with e, add "hello", otherwise "hi". I want to get:
[:applehello, :bananahi]
I did:
n = []
a.each do |b|
if (b[-1] == "e")
n.push b.to_s + "hello"
else
n.push b.to_s + "hi"
end
end
p n
I have to convert it into strings. How can I get the final output in symbols?
Did it using sub aswell-
a.each do |q|
if (q[-1]=="e")
then n.push q.to_s.sub(/e/,"ehello")
else
n.push q.to_s.sub(/\z/,"ahi")
end
end
p n
Use to_sym to have a symbol back
a = [:apple, :banana , :grape, :black]
a.map do |s|
(s.to_s + (s[-1] == 'e' ? 'hello' : 'hi')).to_sym
end
An alternative
a = [:apple, :banana , :grape, :black]
a.map do |s|
"#{s}#{s[-1] == 'e' ? 'hello' : 'hi'}".to_sym
end
Tried with following,
a.map { |x| "#{x}#{x.to_s.last == 'e' ? 'hello' : 'hi'}".to_sym }
# => [:applehello, :bananahi, :grapehello, :blackhi]
a.map{|sym| sym.to_s.sub(/.\z/) do
|c| case c; when "e" then "hello"; else "hi" end.to_sym
end}
# => [:applhello, :bananhi, :graphello, :blachi]

How to swapcase a string without using builtin functions

I am trying to swapcase a string without using builtin functions like swapcase.
I came up with with the following code:
a = gets.split
b = ""
for i in a
if /[[:upper:]]/.match(i)
b += i.downcase
end
if /[[:lower:]]/.match(i)
b += i.upcase
end
end
puts b
But it's producing a wrong output. How can I do it in Ruby?
One approach to mimic swapcase with no argument.
p 'Hello'.chars.map { |c| c.upcase == c ? c.downcase : c.upcase }.join
#=> "hELLO"
"Hello World!".tr("a-zA-Z", "A-Za-z")
# => "hELLO wORLD!"
Similar to #sagarpandya82's answer but doesn't convert the string to an array and back.
"ComMMent maintEnaNt, vaChe tacHetée?".gsub(/./) do |c|
c == c.upcase ? c.downcase : c.upcase
end
#=> "cOMmmENT MAINTeNAnT, VAcHE TAChETÉE?"
def fnswapcase(a):
r = ' '
for i in a:
if ord(i) in range(97,123):
r+=chr(ord(i)-32)
elif ord(i) in range(65,91):
r+=chr(ord(i)+32)
else:
r+=i
return r
a=input("Enter a string:")
print(fnswapcase(a))

Ruby difference in array including duplicates

[1,2,3,3] - [1,2,3] produces the empty array []. Is it possible to retain duplicates so it returns [3]?
I am so glad you asked. I would like to see such a method added to the class Array in some future version of Ruby, as I have found many uses for it:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
A description of the method and links to some of its applications are given here.
By way of example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1,2,3,2]
Ruby v2.7 gave us the method Enumerable#tally, allowing us to replace the first line of the method with
h = other.tally
As far as I know, you can't do this with a built-in operation. Can't see anything in the ruby docs either. Simplest way to do this would be to extend the array class like this:
class Array
def difference(array2)
final_array = []
self.each do |item|
if array2.include?(item)
array2.delete_at(array2.find_index(item))
else
final_array << item
end
end
end
end
For all I know there's a more efficient way to do this, also
EDIT:
As suggested by user2864740 in question comments, using Array#slice! is a much more elegant solution
def arr_sub(a,b)
a = a.dup #if you want to preserve the original array
b.each {|del| a.slice!(a.index(del)) if a.include?(del) }
return a
end
Credit:
My original answer
def arr_sub(a,b)
b = b.each_with_object(Hash.new(0)){ |v,h| h[v] += 1 }
a = a.each_with_object([]) do |v, arr|
arr << v if b[v] < 1
b[v] -= 1
end
end
arr_sub([1,2,3,3],[1,2,3]) # a => [3]
arr_sub([1,2,3,3,4,4,4],[1,2,3,4,4]) # => [3, 4]
arr_sub([4,4,4,5,5,5,5],[4,4,5,5,5,5,6,6]) # => [4]

How to know that we're on "the last round" of a .each_slice

I have a random amount of days that I'm iterating through like this:
#days.each_slice(7) {|week|}
and would like to know when I'm on the last set of days (i.e. the last week). What would be an efficient way to do this?
The simplest way I can think of is by defining this algorithm to detect the last slice of an array:
def last_slice( array, i )
last_slice = (array.count % i == 0) ? i : array.count % i
array.last( last_slice )
end
and then compare it like:
if ( week == last_slice( #days, 7 ) )
#days.each_slice(7).with_index do |week,i|
if i == (#days.size-1)/7
# last one
end
...
end
Alternatively, if your code in the block is highly divergent for the last week:
weeks = #days.each_slice(7).to_a
weeks[0...-1].each {|week| ... }
weeks[-1].tap {|last_week| ... }
It's a hint,hope will help you:
ar = [1,2,3,1,2,3,"a","b","c"]
e = ar.each_slice(3)
e.size.times do |i|
begin
i = e.next
e.peek
rescue StopIteration
p "reached last iteration : #{i}"
end
end
#=> "reached last iteration : [\"a\", \"b\", \"c\"]"
or You could do as below:
e = ar.each_slice(3)
e.with_index{|i,ind| p i if ind == e.size - 1 }
#=> ["a", "b", "c"]
or You could do as below too:
ar = [1,2,3,1,5,3,"a","b","c"]
e = ar.each_slice(3)
e.size.times{|i| i = e.next;p i if !(e.peek rescue nil) }
#=> ["a", "b", "c"]
#days.each_slice(7) { |week| if (week.last == #days.last) then <your code here> end }
I don't have knowledge of ruby but did you try this in your loop ?
#days.each_slice(7) {|week|
if week.last # do stuff...
}
Here is the Ruby Doc for the each_slice method :
http://ruby-doc.org/core-2.0/Enumerable.html#method-i-each_slice

Map an array modifying only elements matching a certain condition

In Ruby, what is the most expressive way to map an array in such a way that certain elements are modified and the others left untouched?
This is a straight-forward way to do it:
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
Omitting the "leave-alone" case of course if not enough:
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
What I would like is something like this:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Is there some nice way to do this in Ruby (or maybe Rails has some kind of convenience method that I haven't found yet)?
Thanks everybody for replying. While you collectively convinced me that it's best to just use map with the ternary operator, some of you posted very interesting answers!
Because arrays are pointers, this also works:
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
In the loop, make sure you use a method that alters the object rather than creating a new object. E.g. upcase! compared to upcase.
The exact procedure depends on what exactly you are trying to achieve. It's hard to nail a definite answer with foo-bar examples.
old_a.map! { |a| a == "b" ? a + "!" : a }
gives
=> ["a", "b!", "c"]
map! modifies the receiver in place, so old_a is now that returned array.
I agree that the map statement is good as it is. It's clear and simple,, and would easy
for anyone to maintain.
If you want something more complex, how about this?
module Enumerable
def enum_filter(&filter)
FilteredEnumerator.new(self, &filter)
end
alias :on :enum_filter
class FilteredEnumerator
include Enumerable
def initialize(enum, &filter)
#enum, #filter = enum, filter
if enum.respond_to?(:map!)
def self.map!
#enum.map! { |elt| #filter[elt] ? yield(elt) : elt }
end
end
end
def each
#enum.each { |elt| yield(elt) if #filter[elt] }
end
def each_with_index
#enum.each_with_index { |elt,index| yield(elt, index) if #filter[elt] }
end
def map
#enum.map { |elt| #filter[elt] ? yield(elt) : elt }
end
alias :and :enum_filter
def or
FilteredEnumerator.new(#enum) { |elt| #filter[elt] || yield(elt) }
end
end
end
%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ]
require 'set'
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}>
('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy"
Your map solution is the best one. I'm not sure why you think map_modifying_only_elements_where is somehow better. Using map is cleaner, more concise, and doesn't require multiple blocks.
One liner:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
In the code above, you start with [] "cumulative". As you enumerate through an Enumerator (in our case the array, ["a", "b", "c"]), cumulative as well as "the current" item get passed to our block (|cumulative, i|) and the result of our block's execution is assigned to cumulative. What I do above is keep cumulative unchanged when the item isn't "b" and append "b!" to cumulative array and return it when it is a b.
There is an answer above that uses select, which is the easiest way to do (and remember) it.
You can combine select with map in order to achieve what you're looking for:
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
Inside the select block, you specify the conditions for an element to be "selected". This will return an array. You can call "map" on the resulting array to append the exclamation mark to it.
Ruby 2.7+
As of 2.7 there's a definitive answer.
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Here's a good read on the subject.
Hope that's useful to someone!
If you don't need the old array, I prefer map! in this case because you can use the ! method to represent you are changing the array in place.
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
I prefer this over:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
It's a few lines long, but here's an alternative for the hell of it:
oa = %w| a b c |
na = oa.partition { |a| a == 'b' }
na.first.collect! { |a| a+'!' }
na.flatten! #Add .sort! here if you wish
p na
# >> ["b!", "a", "c"]
The collect with ternary seems best in my opinion.
I've found that the best way to accomplish this is by using tap
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end

Resources