Find a Duplicate in an array Ruby - arrays

I am trying to find the duplicate values in an array of strings between 1 to 1000000.
However, with the code I have, I get the output as all the entries that are doubled.
So for instance, if I have [1,2,3,4,3,4], it gives me the output of 3 4 3 4 instead of 3 4.
Here is my code:
array = [gets]
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}
end
Also, every time I test my code, I have to define the array as array = [1,2,3,4,5,3,5]. The puts works but it does not print when I use array [gets].
Can someone help me how to fix these two problems?

How I wish we had a built-in method Array#difference:
class Array
def difference(other)
h = other.tally
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
though #user123's answer is more straightforward. (Array#difference is probably the more efficient of the two, as it avoids the repeated invocations of count.) See my answer here for a description of the method and links to its use.
In a nutshell, it differs from Array#- as illustrated in the following example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1, 3, 2, 2]
For the present problem, if:
arr = [1,2,3,4,3,4]
the duplicate elements are given by:
arr.difference(arr.uniq).uniq
#=> [3, 4]

For your first problem, you need to uniq function like
array.select{ |x| array.count(x) > 1}.uniq
For your second problem, when you receive a value using array = [gets] it would receive your entire sequence of array numbers as a single string, so everything would be stored in a[0] like ["1, 2 3 4\n"].

puts "Enter array"
array = gets.chomp.split(",").map(&:to_i)
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}.uniq
end
copy this code in ruby file and try to run using
ruby file_name.rb

Coming to your 'gets' problem,
When you are doing a gets, your are basically getting a string as an input but not an array.
2.2.0 :001 > array = [gets]
1,2,1,4,1,2,3
=> ["1,2,1,4,1,2,3\n"]
See the above example, how the ruby interpreter took all your elements as a single string and put it in an array as a single array element. So you need to explicitly convert the input to an array with comma as a delimiter. The below will address both your questions.
array = gets.chomp
array = array.split(',').map(&:to_i)
if array.uniq.length == array.length
puts "array does not contain duplicates"
else
puts "array does contain duplicates"
print array.select{ |x| array.count(x) > 1}.uniq!
end

Related

ruby - How to make an array of arrays of letters (a-z) of varying lengths with maximum length five

So I'm trying to make an array of all possible permutations of the alphabet letters (all lowercase), in which the letters can repeat and vary in length from 1 to 5. So for example these are some possibilities that would be in the array:
['this','is','some','examp','le']
I tried this, and it gets all the variations of words 5 letters long, but I don't know how to find varying length.
("a".."z").to_a.repeated_permutation(5).map(&:join)
EDIT:
I'm trying to do this in order to crack a SHA1 encrypted string:
require 'digest'
def decrypt_string(hash)
("a".."z").to_a.repeated_permutation(5).map(&:join).find {|elem| Digest::SHA1.hexdigest(elem) == hash}
end
Hash being the SHA1 encryption of the word, such as 'e6fb06210fafc02fd7479ddbed2d042cc3a5155e'
You can modify your method slightly.
require 'digest'
def decrypt_string(hash)
arr = ("a".."z").to_a
(1..5).each do |n|
arr.repeated_permutation(n) do |a|
s = a.join
return s if Digest::SHA1.hexdigest(s) == hash
end
end
end
word = "cat"
hash = Digest::SHA1.hexdigest(word)
#=> "9d989e8d27dc9e0ec3389fc855f142c3d40f0c50"
decrypt_string(hash)
#=> "cat"
word = "zebra"
hash = Digest::SHA1.hexdigest(word)
#=> "38aa53de31c04bcfae9163cc23b7963ed9cf90f7"
decrypt_string(hash)
#=> "zebra"
Calculations for "cat" took well under one second on my 2020 Macbook Pro; those for "zebra" took about 15 seconds.
Note that join should be applied within repeated_permutation's block, as repeated_permutation(n).map(&:join) would create a temporary array having as many as 26**5 #=> 11,881,376 elements (for n = 5).
If you do not mind the possibility of repeating strings then
e = Enumerator.new do |y|
r = ('a'..'z').to_a * 5
loop do
y << r.shuffle.take(rand(4)+1).join
end
end
Should work. Then you can call as
e.take(10)
#=> ["bz", "tnld", "jv", "s", "ngrm", "phiy", "ar", "zq", "ajjn", "cn"]
This:
Creates an Array of a through z repeated 5 times
Continually shuffles said Array
Then takes the first 1 to 5 ("random number") elements from the shuffled Array and joins them together

Count the number of elements of array that contain a digit

i am new to Ruby and stuck on this exercice. I have this array including string value and i am looking for the number of value including a figure ?
array = ["#jcunniet","#PaulLampon","#Aziliz31","#ssoumier","#marionsouzeau","#gaellombart","#bendarag","#AurelieLebelle","#julienduffe","#thomaspoupeau","#LilyRossignol","#ClairGuedon","#stephanieauguy","#claw_prolongeau","#_JulieMenard","#LColcomb","#Zlauwereys","#MeLonguet","#DorotheeLN","#NolwennCosson","#ADaboval","#Remibaldy","#bderveaux","#amandechap","#ELODIESOULIE","#nbongarcon","#HeloAb","#Panamorama","#gregplou","#BenoitBerthe","#LauraBruneau89","#Anthony_Lieures","#Sharonwaj","#mcsonkin","#pverduzier","#emiliel3","#Julien_MARION","#SophiFay","#bdelombre","#annalecerf","#AdriaBudry","#DejNikolic","#iJaffre","#CyrusleVirus","#GPardigon","#e_vallerey","#IsabelleSouquet","#AudeDavidRossi","#Yoann_Pa","#CeliaPenavaire","#perraultvincent","#cboulate","#JustineWeyl","#Paulinejacot","#juliechab","#aslechevallier","#phnou","#Seb_Pommier","#Alex_Bensaid","#GuillaumeGaven","#annelaurechouin","#Oliviader","#guerricp","#JMMarchaut","#cyceron","#gregory_raymond","#vhunsinger","#l_peillon","#fannyguinochet","#EAssayag","#KibweAdom","#YvonHerry","#JohanGarciajg","#saidlabidi","#lauranneprov","#LeaDavy","#francois_remy","#CGuicheteau","#FloMaillet","#m_perroud","#oBrunet_TSMF","#MoonVdc","#jc2taille","#NellyMoussu","#VirginK","#b_misa","#FabriceCouste","#barbara_prose","#lelia2m","#brunoaskenazi","#laurenechamp","#ysisbox","#juliengagliardi","#PierreLel","#kdeniau","#_TerraInc","#DominicArpin","#antoinfonteneau","#nanotousch","#jb_roch","#YaniKhezzar","#Anne_Bechet","#NCapart","#SamyBenNaceur","#Joumany","#Julietteraynal","#TGiraudet","#SaraTanit","#HappeFrederic","#antoinellorca","#michelpicot","#Sev_Ryne","#bobdobolino","#murdever","#YGrandmontagne","#Mnyo","#EdKOSCIANSKI","#tnoisette","#jankari","#delbello_rom","#rflechaux","#NadiaSorelli","#IT_Digital","#abarbaux","#PhilippeLeroy","#schaptal","#marionspee","#lisavignoli","#ChloeAeberhardt","#MartineJacot","#JuliaPascualita","#curieusedetout","#sgraveleau","#bif_o","#ElisaPineau","#zinebdryef","#apiquard","#pierrehaski","#StephanieDelmas","#Blandine_Garot","#vergara_i","#evan_lebastard","#SophieVclt","#OlivierLevrault","#alicedorgeval","#LouiseMalnoy","#alix_fx","#pierre_baudis","#LucMagoutier","#AgatheMuller","#SGianninelli","#PaulineBoyer33","#NaomiHalll","#romaindlx","#marionbr","#Burtschy","#JacobEtienne","#as_lizzani","#marie_simon","#LaureDaussy","#FabriceAmedeo","#LoubnaChlaikhy","#PlummerWilliam","#OlivierMarin1","#alaurefremont","#mwesfreid","#ChBaDe","#pmathon","#theobaldmarie","#Lnpagesy","#marclandre","#paoliniesther","#Feertchak","#JBLitzler","#GuillaumeErrard","#quentinperinel","#TristanQM","#mlbo","#constancejamet","#LoraTerrazas","#emiliegeffray","#Mathilde_Sd","#CaroPiquet","#DCanivez","#TIM_7375","#blandinelc","#ivanrioufol","#arthurberdah","#SarahLecoeuvre","#guillaume_gui","#DamienMercereau","#W_Chloe","#Assma_MD","#EugenieBastie","#HiTech_lexpress","#bcondominas","#Laurie_Z_","#jeanfrancgerard","#MathieuPagura","#BGUYF","#AlainPiffaretti","#AudreyKucinskas","#julienhory","#Pierrefalga","#TiphThuillier","#cdaniez","#LigerBaptiste","#D_Peras","#julie_dlb","#Fatiha_Temmouri","#julian2lamancha","#GaetaneDeljurie","#JulianMattei","#M_Vicuna","#DeBruynOlivier","#Nehed_Jendoubi","#antoine_grenapi","#ColonnaGen","#VictoriaGairin","#Clement_Lacombe","#TVigoureux","#MargauxObriot","#solinedelos","#RocheSabine","#dangerkens","#EdouardDutour","#MDondeyne","#DupuisNathalie1","#bouscarel","#Mathieu2jean","#Sophie_T_J","#laurentcalixte","#patrockwilliams","#PascaleKremer","#AlexJaquin","#LauraIsaaz","#cath_robin","#Del_Gautherin","#Isaduriez","#lucietuile","#AugeyBastien","#mcastagnet","#AminaKalache","#mvaudano","#CParrot","#ombelinetips","#_JoinLion","#BarbolosiRose","#ToiBruno1","#FloraClodic","#xjbdx","#AlexiaEy","#Emjy_STARK","#elcoco01","#rabilebon","#pflovens_","#FabriceFrossard","#MorganeSERRES","#MarjolaineKoch","#edgarsnow","#SRNLF","#CChassigneux","#KerinecMoran","#NassiraELM","#NewsRicard","#Sandreene","#Emilezrt","#Pierre_Do","#Micode","#CColumelli","#DavidAbiker","#ClementBergantz","#benjaminrabier","#celinekallmann","#edwyplenel","#C_Barbier","#JJBourdin_RMC","#LaurenceFerrari","#aslapix","#IsaMillet","#MaximeSwitek","#tomjoubert","#jszanchi","#roqueeva","#XavierBiseul","#florencesantrot","#AntoineCrochet","#freeman59","#MaudeML","#philippe_gulpi","#mathieum76","#kiouari","#imanemoustakir","#BenedicteMallet","#Emilie_Brouze","#antoinebarret","#_nicolasbocquet","#remibuhagiar","#CourretB","#AymericRobert","#miraelmartins","#pmaniere","#jesuisraphk","#David_Ingram","#pcelerier","#technomedia","#Geraldinedauver","#ThierryLabro","#Newsdusud","#nrauline","#gbregeras","#SCouasnonBFM","#actualites_nrv","#dimitrimoulins","#oli_aura","#FabieChiche","#Vincent_Raimblt","#ChristophGreuet","#PAlbuchay","#MarrauddesGrot","#vtalmon","#cedric","#olivierfrigara","#Julien_Jay","#LydiaBerroyer","#Shuua","#datisdaz","#Steuph","#ameliecharnay","#Bruno_LesNums","#LelloucheNico","#CciliaDiQuinzio","#Elodie_C","#SylvRolland","#Kocobe","#FL_Debes","#jdupontcalbo","#GarciaVictor_","#NicoRichaud","#RHoueix","#simottel","#DamienLicata","#annabelle_L","#Lea_Lejeune","#axel_deb","#marin_eben","#ptiberry","#MatthieuDelach","#sandrinecassini","#benjaminferran","#ppgarcia75","#NotPatrick","#ivalerio","#FabienneSchmitt","#alexgoude","#JeromeColombain","#manhack","#Capucine_Cousin","#Fsorel","#oliviertesquet","#marjoriepaillon","#ginades","#PierreTran","#DelphineCuny","#reesmarc","#lauratenoudji","#ldupin","#carolinedescham","#Lucile_Quillet","#cgabizon","#Allocab","#epenser","#JAGventeprivee","#frwrds","#Laure__Bourdon","#Xavier75","#maximeverner","#s_jourdain","#LoriHelloc"]
i = 0
array.each do |n|
if n.include?("1,2,3,4,5,6,7,8,9,10")
then i += 1
end
puts i
end
Do you mean something like this?
array.grep(/[1-9]/).count
Edit: Changed regEx from /[1-10]/ to /[1-9]/
Often code that is the direct translation of the question is best: “Count the number of elements of array that contain a digit”.
array.count { |s| s.match? /\d/ }
See Array#count and String#match?.

Array interpreted as a Fixnum

I'm currently learning ruby and I wrote this piece of code :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr.first + rec_sum(num_arr.shift)
end
end
puts rec_sum(multi_gen)
That should return the sum of all 3 and 5 multiples up to 1000.
But I get an error :
myrbfile.rb:17:in `rec_sum': undefined method `first' for 3:Fixnum (NoMethodError)
from villani.rb:17:in `rec_sum'
from villani.rb:21:in `<main>'
But when I re-write it like this :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr[0] + rec_sum(num_arr[1..num_arr.last])
end
end
puts rec_sum(multi_gen)
I don't get the error.
So why is my first rec_sum functions interpretting my Array as a Fixnum in the first case?
The issue is in the recursive call:
rec_sum(num_arr.shift)
Array#shift returns the shifted element, not the remaining array. You should explicitly pass the array as an argument to recursive call:
rec_sum(num_arr[1..-1])
or
rec_sum(num_arr.tap(&:shift))
The latter would [likely] be looking too cumbersome for the beginner, but it’s a very common rubyish approach: Object#tap yields the receiver to the block, returning the receiver. Inside a block (num_arr.tap(&:shift) is a shorthand for num_arr.tap { |a| a.shift } we mutate the array by shifting the element out, and it’s being returned as a result.
mudasobwa already explained why using shift doesn't give the expected result. Apart from that, your code is somehow unidiomatic.
In multi_gen you are creating an empty array and append elements to it using a for loop. You rarely have to populate an array manually. Instead, you can usually use one of Ruby's Array or Enumerable methods to generate the array. select is a very common one – it returns an array containing the elements for which the given block returns true:
(1..1000).select { |i| i % 3 == 0 || i % 5 == 0 }
#=> [3, 5, 6, 9, 10, 12, ...]
In rec_sum, you check if num_arr == []. Although this works, you are creating an empty throw-away array. To determine whether an array is empty, you should call its empty?:
if num_arr.empty?
# ...
end
To get the remaining elements from the array, you use:
num_arr[1..num_arr.last]
which can be abbreviated by passing a negative index to []:
num_arr[1..-1]
There's also drop which might look a little nicer:
num_arr[0] + rec_sum(num_arr[1..-1])
# vs
num_arr.first + rec_sum(num_arr.drop(1))
Another option to get first and remaining elements from an array is Ruby's array decomposition feature (note the *):
def rec_sum(num_arr)
if num_arr.empty?
0
else
first, *remaining = num_arr
first + rec_sum(remaining)
end
end
You could also consider using a guard clause to return from the method early:
def rec_sum(num_arr)
return 0 if num_arr.empty?
first, *remaining = num_arr
first + rec_sum(remaining)
end
Writing recursive methods is great for learning purposed, but Ruby also has a built-in sum method:
multi_gen.sum #=> 234168
or – since you are using an older Ruby version – inject:
multi_gen.inject(0, :+) #=> 234168

Ruby modify array items and return full array

I have this code here
string.split(/(\w{1,}=)/).each_slice(1).map { |i| items << i }
items.map! do |i|
i = i << str if i.to_s =~ /\w{1,}=/
end
puts items*''
And I want to modify certain items in the array based on regex, then return the full array with the modified items in it. This only returns the modified items. How do I achieve what I'm looking for?
EDIT: Ok, so say I'm trying to split a link using this regex:
page.php?site=blah&id=1
The link is split and added to the array which now contains
page.php?
site=
blah&
id=
1
What I want to do is append some value to the end of the elements ending with a =. This way, when I return the modified array as a string it would output like this:
page.php?site=(newval)&id=(newval)
You have several undefined variables in your example, which is very sloppy.
each_slice(1) is equivalent to each(), so it's not clear why you are using each_slice(1). In any case, both each() and map() step through the items in an Array one by one, but each() returns the original Array unchanged. On the other hand, you use map() when you want to create a new Array that contains changes to the items.
In the regex /\w{1,}/, there is a shortcut for the quantifier {1, }, and it's: +, so most people would write the regex as /\w+/, where + means 1 or more.
I want to modify certain items in the array based on regex, then
return the full array with the modified items in it.
Here is an example:
results = [1, 2, 3].map do |num|
if num == 2
num + 4
else
num - 1
end
end
p results
--output:--
[0, 6, 2]
Your current attempt with map() doesn't return anything if the conditional fails. Note how the example above returns something both when the condition fails AND when the condition succeeds. map() replaces an item with whatever is returned for that item.
Now look at this example:
results = [1, 2, 3].map do |num|
if num == 2
num + 4
end
end
p results
--output:--
[nil, 6, nil]
If you don't return something for an item, then map() will use nil for that item. In the example, if the condition num == 2 is true then num+4 is returned--but if num == 2 is false, nothing is returned.
Edit:
words = %w[
page.php?
site=
blah&
id=
1
] #=> words = ["page.php?", "site=", "blah&", "id=", "1"]
suffix = 'hello'
results = words.map do |word|
if word.end_with?('=')
"#{word}#{suffix}"
else
word
end
end
p results
--output:--
["page.php?", "site=hello", "blah&", "id=hello", "1"]
Instead of parsing a URL with a regex, have you considered using the addressable gem?
require 'addressable/uri'
uri = Addressable::URI.parse('page.php?site=blah&id=1&bar')
uri.query_values = uri.query_values.map do |k, v|
[k, v.is_a?(String) ? v << 'foo' : v]
end
puts uri.to_s # => page.php?site=blahfoo&id=1foo&bar
This won't handle very complex query parameters (it will just pass them through).
You can use respond_to? :sub! and v.sub! /$/, 'foo' instead of checking types if that makes you uneasy. (I wouldn't use :<< or :concat because those are valid methods for Arrays.)

Ruby: Sorting an Array, skipping the first element

I want to sort an Array of Arrays of Strings by the first String skipping the first Array but I just don't have an idea how to do it using the build-in sort method. I could copy the whole array without the first element and sort the resutling Array then but isn't there a more elegant way to do this?
ar = [["zzzz", "skip", "this"], ["EFP3","eins","eins"], ["EFP10","zwei","zwei"], ["EFP1","drei","drei"]]
ar.sort!{ |a,b|
if a == ar.first # why doesn't
next # this
end # work ?
# compare length, otherwise it would be e.g. 10 < 3
if a[0].length == b[0].length
a[0] <=> b[0]
else
a[0].length <=> b[0].length
end
}
I want to have the result like this:
["zzzz", "skip", "this"], ["EFP1","drei","drei"], ["EFP3","eins","eins"], ["EFP10","zwei","zwei"]
sortet by "EFP#"
edit: I'm using Ruby 1.8, if it matters.
ar[1..-1].sort { whatever you want }
You can do it this way:
[ar.first] + ar[1..-1].sort{ |a,b| a[0] <=> b[0] }
# => [["zzzz", "skip", "this"], ["EFP1", "drei", "drei"], ["EFP10", "zwei", "zwei"], ["EFP3", "eins", "eins"]]
but isn't there a more elegant way to do this?
You could sort the other elements and re-assign them:
ar = [5, 4, 3, 2, 1]
ar[1..-1] = ar[1..-1].sort
ar #=> [5, 1, 2, 3, 4]
I want to have the result [...] sortet by "EFP#"
sort_by looks like the right tool:
ar = [["zzzz", "skip"], ["EFP3", "eins"], ["EFP10", "zwei"], ["EFP1", "drei"]]
ar[1..-1] = ar[1..-1].sort_by { |s, _| s[/\d+/].to_i }
ar #=> [["zzzz", "skip"], ["EFP1", "drei"], ["EFP3", "eins"], ["EFP10", "zwei"]]
s[/\d+/].to_i extracts the digits from s and converts it to an integer:
"EFP1"[/\d+/].to_i #=> 1
"EFP3"[/\d+/].to_i #=> 3
"EFP10"[/\d+/].to_i #=> 10
Others have explained how to get the right answer.
As for why it doesn't work, sort simply doesn't expect "next". "next" is a language construct intended for a normal loop. sort, however, is a function that repeatedly asks another function for a result. As a normal Ruby function, it can't detect if you returned "next" because that's the equivalent of returning nil (or leaving the body empty). And so, it can't have, and doesn't have, any conventions about how to handle a "next" instance.
It causes an error because nil is not a valid number to return from the |a,b| comparison.
The comparison returns -1, 0 or 1, so if you return 1 for the first it is sorted as the first element, as 1 it would become the last element.
ar.sort!{ |a,b|
if a == ar.first
-1
elsif a[0].length == b[0].length # compare length, otherwise it would be e.g. 10 < 3
a[0] <=> b[0]
else
a[0].length <=> b[0].length
end
}
#=>[["zzzz", "skip", "this"], ["EFP1", "drei", "drei"], ["EFP3", "eins", "eins"], ["EFP10", "zwei", "zwei"]]

Resources