Ada Sorting Composite Array - sorting

I'm pretty new at Ada, and I have a question. This Demo_Array_Sort from Rosetta Code uses the function "<" to determine how to sort the array. It sorts it by the name in alphabetical order. I understand this part. Where my question comes in is this:
If the array Data had multiple entries with the same name and I wanted to sort the list by value within Name, how would I do that? I've tried messing around with redefining the "<" function to no avail. Please help!
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Generic_Array_Sort;
procedure Demo_Array_Sort is
function "+" (S : String) return Unbounded_String renames To_Unbounded_String;
type A_Composite is
record
Name  : Unbounded_String;
Value : Unbounded_String;
end record;
function "<" (L, R : A_Composite) return Boolean is
begin
return L.Name < R.Name;
end "<";
procedure Put_Line (C : A_Composite) is
begin
Put_Line (To_String (C.Name) & " " & To_String (C.Value));
end Put_Line;
type An_Array is array (Natural range <>) of A_Composite;
procedure Sort is new Ada.Containers.Generic_Array_Sort (Natural, A_Composite, An_Array);
Data : An_Array := (1 => (Name => +"Joe", Value => +"5531"),
2 => (Name => +"Adam", Value => +"2341"),
3 => (Name => +"Bernie", Value => +"122"),
4 => (Name => +"Walter", Value => +"1234"),
5 => (Name => +"David", Value => +"19"));
begin
Sort (Data);
for I in Data'Range loop
Put_Line (Data (I));
end loop;
end Demo_Array_Sort;
Example Data:
Data : An_Array := (1 => (Name => +"Joe", Value => +"5531"),
2 => (Name => +"Adam", Value => +"2341"),
3 => (Name => +"Bernie", Value => +"122"),
4 => (Name => +"Walter", Value => +"1234"),
5 => (Name => +"David", Value => +"19")
6 => (Name => +"David", Value => +"42")
7 => (Name => +"David", Value => +"5"));
Would output:
Adam 2341
Bernie 122
David 5
David 19
David 42
Joe 5531
Walter 1234

In outline,
Change the Value component of A_Composite to a scalar subtype for which "<" is already suitably defined; I've chosen Natural:
type A_Composite is
record
Name : Unbounded_String;
Value : Natural;
end record;
Now it's easy to write a "<" that handles L.Name = R.Name:
function "<" (L, R : A_Composite) return Boolean is
begin
if L.Name < R.Name then return True;
elsif L.Name = R.Name then return L.Value < R.Value;
else return False;
end if;
end "<";
Update Put_Line accordingly:
Put_Line (To_String (C.Name) & Natural'Image(C.Value));
Data:
Data : An_Array := (
1 => (Name => +"Joe", Value => 5531 ),
2 => (Name => +"Adam", Value => 2341),
3 => (Name => +"Bernie", Value => 122),
4 => (Name => +"Walter", Value => 1234),
5 => (Name => +"David", Value => 19),
6 => (Name => +"David", Value => 42),
7 => (Name => +"David", Value => 5));
Console:
Adam 2341
Bernie 122
David 5
David 19
David 42
Joe 5531
Walter 1234

Following is an example using the Vector container.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Containers.Vectors;
procedure Main is
type Surname is (Smith, Jones, Chen, Chavez);
type Name is (John, Francis, Leslie, Margaret, George, Walter);
type Person is record
First : Name;
Last : Surname;
end record;
function Less(P1: Person; P2 : Person) return boolean is
begin
if P1.Last < P2.Last then
return true;
elsif
P1.Last = P2.Last then
return P1.First < P2.First;
else
return false;
end if;
end Less;
procedure Print(P : Person) is
begin
Put_Line(P.Last'Image & ", " & P.First'Image);
end Print;
package Person_Vector is new Ada.Containers.Vectors(Index_Type => Natural,
Element_Type => Person);
use Person_Vector;
package Person_Sort is new Generic_Sorting(Less);
use Person_Sort;
V : Vector;
Temp : Person;
begin
for N in Name loop
for S in Surname loop
Temp.First := N;
Temp.Last := S;
V.Append(Temp);
end loop;
end loop;
Put_Line("Unsorted list:");
for P of V loop
Print(P);
end loop;
New_Line;
Sort(V);
Put_Line("Sorted list:");
for P of V loop
Print(P);
end loop;
end Main;
The output of this program is:
Unsorted list:
SMITH, JOHN
JONES, JOHN
CHEN, JOHN
CHAVEZ, JOHN
SMITH, FRANCIS
JONES, FRANCIS
CHEN, FRANCIS
CHAVEZ, FRANCIS
SMITH, LESLIE
JONES, LESLIE
CHEN, LESLIE
CHAVEZ, LESLIE
SMITH, MARGARET
JONES, MARGARET
CHEN, MARGARET
CHAVEZ, MARGARET
SMITH, GEORGE
JONES, GEORGE
CHEN, GEORGE
CHAVEZ, GEORGE
SMITH, WALTER
JONES, WALTER
CHEN, WALTER
CHAVEZ, WALTER
Sorted list:
SMITH, JOHN
SMITH, FRANCIS
SMITH, LESLIE
SMITH, MARGARET
SMITH, GEORGE
SMITH, WALTER
JONES, JOHN
JONES, FRANCIS
JONES, LESLIE
JONES, MARGARET
JONES, GEORGE
JONES, WALTER
CHEN, JOHN
CHEN, FRANCIS
CHEN, LESLIE
CHEN, MARGARET
CHEN, GEORGE
CHEN, WALTER
CHAVEZ, JOHN
CHAVEZ, FRANCIS
CHAVEZ, LESLIE
CHAVEZ, MARGARET
CHAVEZ, GEORGE
CHAVEZ, WALTER
Note that the fields are sorted in the order their enumerations are specified in the program.

Related

How do I count unique multiple words in a Ruby string?

Trying to write a Ruby code that will count unique words and return their total occurrences.
So suppose I want to find number of occurrences for Sally, Marina and Tina in the following sentence "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner."
I tried the following but this defeats the dry principal. Is there a better way?
string = "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner. Sally will then take Tina out for a late night party."
puts "Marina appears #{string.split.count("brown").to_i} times."
puts "Tina appears #{string.split.count("grey").to_i} times."
puts "Sally appears #{string.split.count("blue").to_i} times."
Expected result: program looks through the text for unique words and returns them.
Actual: I had to hard code each unique word on its own PUTS line and do string.split.count(for that unique word)
Note:
I tried the following but this gives me EVERY word. I need to refine it to give me just the ones I ask for. This is where I am struggling.
def cw(string)
w = string.split(' ')
freq = Hash.new(0)
w.each { |w| freq[w.downcase] += 1 }
return freq
end
puts cw(string)
def count_em(str, who)
str.gsub(/\b(?:#{who.join('|')})\b/i).
each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
end
str = "Monday Tina will meet Sally and Harris. Then Tina will visit her " +
"mom Marina. Marina and Tina will meet David for dinner. Sally will " +
"then take Tina out for a late night party."
who = %w| Sally Marina Tina |
count_em(str, who)
#> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
The first steps are as follows.
r = /\b(?:#{who.join('|')})\b/i
#=> /\b(?:Sally|Marina|Tina)\b/i
enum = str.gsub(r)
#=> #<Enumerator: "Monday Tina will meet Sally and Harris. Then
# ...
# for a late night party.":gsub(/\b(?:Sally|Marina|Tina)\b/i)>
We can convert this to an array to see the values that will be passed to each_with_object.
enum.to_a
#=> ["Tina", "Sally", "Tina", "Marina", "Marina", "Tina", "Sally", "Tina"]
We then simply count the number of instances of the unique values generated by enum.
enum.each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
#=> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
See String#gsub, in particular the case when there is one argument and no block. This is admittedly an unusual use of gsub, as it is making no substitutions, but here I prefer it to String#scan because gsub returns an enumerator whereas scan produces a temporary array.
See also Hash::new, the case where new takes an argument and no block. The argument is called the default value. If h is the hash so-defined, the default value is returned by h[k] if h does not have a key k. The hash is not altered.
Here the default value is zero. When the expression h[person] += 1 it is parsed it is converted to:
h[person] = h[person] + 1
If person equals "Tina", and it is the first time "Tina" is generated by the enumerator and passed to the block, h will not have a key "Tina", so the expression becomes:
h["Tina"] = 0 + 1
as 0 is the default value. The next time "Tina" is passed to the block the hash has a key "Tina" (with value 1), so the following calculation is performed.
h["Tina"] = h["Tina"] + 1 #=> 1 + 1 #=> 2
To get only the required people name:
people = ['Marina', 'Tina', 'Sally', 'Dory']
tmp = string.scan(/\w+/).keep_if{ |w| people.include? w }
counts people.map{ |name| [name, tmp.count{|n| n == name }] }.to_h
counts #=> {"Marina"=>2, "Tina"=>4, "Sally"=>2, "Dory"=>0}
This maps the peopole array against tmp to a nested array containing [name, count], then converted to a hash.
The good is that it returns 0 if people doesn't appear, see 'Dory'.
To get the total count, two ways:
tmp.size #=> 8
counts.values.sum #=> 8

How to write single line blocks

I find myself trying to use single line blocks but end up having to break up into multiple lines anyway. My most recent example is that I'm trying to get initials from the name field of an active record object.
#employee.name = "John Doe"
and I want to return "JD".
The only way I knew how to do it was to initialize a string, then split the name, then add to the initialized string. At the very least how can I avoid having to initialize the empty string?
def initials # In model
intials = ''
name_array = self.name.split(" ")
name_array.each { |name| initials += name[0].capitalize }
return initials
end
Let me play with some proof of concept
class Employee
attr_accessor :name
def initials
#name.split(' ').map { |name| name[0] }.join
end
end
e = Employee.new
e.name = "Foo Baz"
p e.initials # FB
I'd try things like:
'John Doe'.scan(/\b[A-Z]/).join # => "JD"
'John Doe'.scan(/\b[a-z]/i).join # => "JD"
Any of these sort of expressions can break down with names with sufficient complexity:
'John Doe-Smith'.scan(/\b[a-z]/i).join # => "JDS"
'John Doe Jr. III'.scan(/\b[a-z]/i).join # => "JDJI"
'Dr. John Doe MD'.scan(/\b[a-z]/i).join # => "DJDM"
Then it becomes a case of needing to strip out the parts that you don't want.

Output an Array of objects to terminal, as a table with attributes in fixed-width columns

I am attempting to output a 2D array to the console; I want the information in the array to be nicely formatted as shown in my desired output at the end of this question. My array is created as follows (instances are created of the FamilyMember class):
#family_members.rb
class FamilyMember
attr_accessor :name, :sex, :status, :age
def initialize (name, sex, type, role, age)
#name = name
#sex = sex
#type = type
#role = role
#age = age
end
end
# Below, an array is created called fm; instances of the class are then instantiated within the array elements
fm = {}
fm[1] = FamilyMember.new('Andrew','Male', 'Child', 'Son' , '27' )
fm[2] = FamilyMember.new('Bill','Male', 'Parent', 'Father' , '63' )
fm[3] = FamilyMember.new('Samantha','Female', 'Parent', 'Mother' , '62' )
fm[4] = FamilyMember.new('Thomas','Male', 'Child', 'Dog' , '10' )
fm[5] = FamilyMember.new('Samantha', 'Female', 'Child', 'Dog' , '4' )
I want to be able to output the contents of the array to the console formatted as a string. I need to be able to do this two ways - using each and seperately by using do.
What I have attempted (inspired by a previous SO question):
def eacharray(an_array)
an_array.each do |inner|
puts inner.join(" ")
end
end
eacharray(fm)
However the output from the above is as follows:
1 #<FamilyMember:0x000000027e7d48>
2 #<FamilyMember:0x000000027e7c58>
3 #<FamilyMember:0x000000027e7b68>
4 #<FamilyMember:0x000000027e7a78>
5 #<FamilyMember:0x000000027e7988>
How do I output the 2D array elements nicely formatted using each and do?. Any help appreciated. Thanks.
Ideally, my output would be like this:
Family Member Name Sex Type Role Age
1 Andrew Male Child Son 27
2 Bill Male Parent Father 63
3 Samantha Female Parent Mother 62
4 Thomas Male Child Dog 10
5 Samantha Female Child Dog 4
If you're happy with a fixed format (you could build the format dynamically depending on the maximum data width in each field) you could write something like this.
class FamilyMember
attr_accessor :name, :sex, :type, :role, :age
def initialize (*args)
#name, #sex, #type, #role, #age = args
end
end
fm = []
fm << FamilyMember.new( 'Andrew', 'Male', 'Child', 'Son' , '27' )
fm << FamilyMember.new( 'Bill', 'Male', 'Parent', 'Father', '63' )
fm << FamilyMember.new( 'Samantha', 'Female', 'Parent', 'Mother', '62' )
fm << FamilyMember.new( 'Thomas', 'Male', 'Child', 'Dog' , '10' )
fm << FamilyMember.new( 'Samantha', 'Female', 'Child', 'Dog' , '4' )
format = '%-15s %-8s %-7s %-7s %-7s %s'
puts format % ['Family Member', 'Name', 'Sex', 'Type', 'Role', 'Age']
fm.each_with_index do |member, i|
puts format % [ i+1, member.name, member.sex, member.type, member.role, member.age ]
end
output
Family Member Name Sex Type Role Age
1 Andrew Male Child Son 27
2 Bill Male Parent Father 63
3 Samantha Female Parent Mother 62
4 Thomas Male Child Dog 10
5 Samantha Female Child Dog 4
You can also use for ... in, which actually compiles to pretty much the same loop, using the each iterator.
i = 0
for member in fm do
i += 1
puts format % [ i, member.name, member.sex, member.type, member.role, member.age ]
end
or you can use the primitive while or until loop constructs, which most Ruby programmers forget about. Ruby is much more expressive using its iterators.
i = 0
while member = fm[i] do
i += 1
puts format % [ i, member.name, member.sex, member.type, member.role, member.age ]
end
Note that you can omit the do from both of these last examples. As long as you have a newline (or a semicolon) after the while expression Ruby will understand just fine.
A class like your FamilyMember is most easily constructed with a Struct. The result is just a class, but with some extra features.
FamilyMember = Struct.new(:name, :sex, :type, :status, :age)
fm = []
fm << FamilyMember.new('Andrew','Male', 'Child', 'Son' , '27' )
fm << FamilyMember.new('Bill','Male', 'Parent', 'Father' , '63' )
puts FamilyMember.members.map(&:capitalize).join("\t") #members gives you the names of all methods
fm.each{|fam_member|puts fam_member.values.join("\t")} #another feature.
Output:
Name Sex Type Status Age
Andrew Male Child Son 27
Bill Male Parent Father 63
Only lined out...
I understand that, This may be not relevant now. But Just sharing this so someone can take advantage.
Try This Gem - terminal-table
terminal-table

Correct way to use Ruby's case statement with 'and' keyword

What is the correct way to use the 'and' keyword within the case statement in ruby?
Here is an example:
Write a program that asks the user for age and prints the following string based on input:
0 to 2 => "baby"
3 to 6 => "little child"
7 to 12 => "child"
13 to 18 => "youth"
18+ => "adult"
Example 1
INPUT
Enter the age:
3
OUTPUT
little child*
puts "Enter the age:"
age = gets.chomp.to_i
#Write your code here
case (age)
when age >= 0 and <= 2 then puts("baby")
when age > 2 and < 7 then puts("little child")
when age > 6 and < 13 then puts("child")
when age > 12 and < 18 then puts("youth")
when age > 18 then puts("adult")
end
#
1) <, <= etc needs 'objects' like numbers or strings on both sides.
2) With help from Chuck's answer from other question (age>=0 and age<=2) will evaluate to true THEN this true will be compared to age: age === true which gives you false.
You can use ranges in your case statement:
case age
when 0..2 then puts 'baby' #with 2 dots, it will check 0, 1, 2
when 3...7 then puts 'little child' #with 3 dots, it will check 3, 4, 5, 6[no 7!]
when 7...13 then puts 'child'
when 13...18 then puts 'youth'
when 19..120 then puts 'adult'

Ruby combinatorics

I want to generate a soccer football fixture with a list of clubs. Each game is played on Sundays in a random time included in the match_starts_at array. Each club plays only one game each Sunday.
Example:
Having these clubs:
Club Atlético All Boys
Asociación Atlética Argentinos Juniors
Arsenal Fútbol Club
Club Atlético Banfield
Club Atlético Belgrano
Club Atlético Boca Juniors
Club Atlético Colón
Club Estudiantes de La Plata
Club Deportivo Godoy Cruz Antonio Tomba
Asociación Mutual Social y Deportiva Atlético de Rafaela
Club Atlético Independiente
Club Atlético Lanús
Club Atlético Newell's Old Boys
Club Olimpo
Racing Club
Club Atlético San Martín
Club Atlético San Lorenzo de Almagro
Club Atlético Tigre
Club Atlético Unión
Club Atlético Vélez Sarsfield
Result should be similar to what is seen here: http://www.afa.org.ar/index.php?option=com_content&view=article&id=16780%3Afixture-del-torneo-de-primera-division&Itemid=100
Club structure example:
=> # Club #id=1 #name="Example Name"
=> # Club #id=2 #name="Example2 Name"
Fixture structure example:
=> # Fixture #id=1 #datetime='2011-11-19 19:12:49' #home_id=1 #away_id=2
A Fixture object needs the following to be saved to the database:
a home club (:home)
an away club (:away)
and the time of the match (:datetime)
Each club should play only one time with the other clubs and all clubs should play one match at home, the other away , the other at home, etc. There should be 10 matches in a date. How can I create the list of matches?
This is what I've done so far.
competition = Competition.get(1)
clubs = Club.all #20 clubs
#time = Time.now
#count = 0
until #time.sunday? do
#time += (24*60*60) # add 1 day until it's sunday
end
#first_time = #time
#fixture = {1 => []}
clubs.combination(2).each_with_index do |(club1, club2), idx|
Fixture.create(
:home => idx.even? ? club1 : club2,
:away => idx.even? ? club2 : club1,
:datetime => available_fixture_date(club1,club2)
).save
end
def getFecha(club1, club2)
#fixture.keys.each do |fecha|
if (!#fixture[fecha].include?(club1.name) && !#fixture[fecha].include?(club2.name))
#fixture[fecha] << club1.name
#fixture[fecha] << club2.name
#fixture[#fixture.keys.last + 1] = []
return fecha
end
end
end
def available_fixture_date(club1, club2)
fecha = getFecha(club1, club2)
match_starts_at = ['16:00', '17:30', '18:10', '22:00']
match_time = match_starts_at.shuffle.first
#time = #first_time + (24*60*60) * fecha * 7
Time.new(#time.year, #time.month, #time.day, match_time[0,2], match_time[3,2])
end
With my code I get more than 19 dates and I should get 19 dates with 10 matches per date.
You won't get a nice one-liner for that, like you did for the pairing of teams, since it requires consulting the existing data to find out what dates are already taken. But this should work fine. Note that I've used ActiveSupport's time helpers, but you could use something like Chronic if you don't have ActiveSupport available and don't want to include it.
def available_fixture_date(club1, club2)
last_played = (club1.fixtures | club2.fixtures).max(:datetime)
last_played.nil? ? DateTime.now.sunday : last_played + 1.week
end
def create_fixtures(clubs)
clubs.combination(2).each_with_index do |(club1, club2), idx|
Fixture.create(
:home => idx.even? ? club1 : club2,
:away => idx.even? ? club2 : club1,
:datetime => available_fixture_date(club1, club2)
)
end
end
I believe the general algorithm you're looking for here is the Round-Robin. The following gets the dates correctly for me, ending up with 19 dates total, 10 matches per date:
DAY = 24 * 60 * 60
MATCH_START_TIMES = ['16:00', '17:30', '18:10', '22:00']
def fixture_date(fecha)
# select a random start time
match_time = MATCH_START_TIMES.sample
#time = #first_time + DAY * fecha * 7
Time.new(#time.year, #time.month, #time.day, match_time[0,2].to_i, match_time[3,2].to_i)
end
# uses round-robin indexing algorithm described on
# http://en.wikipedia.org/wiki/Round-robin%5Ftournament#Scheduling_algorithm
def round_robin(n, round)
arr = [*0...n]
arr.insert 1, *arr.pop(round)
[arr.slice(0, n/2), arr.slice(n/2, n).reverse]
end
def find_club_combination(clubs, round, pair)
indexes = round_robin(clubs.size, round)
index_a, index_b = indexes.first[pair], indexes.last[pair]
[clubs[index_a], clubs[index_b]]
end
competition = Competition.get(1)
clubs = Club.all #20 clubs
#time = Time.now
#count = 0
#time += DAY until #time.sunday?
#first_time = #time
num_rounds = clubs.size - 1
matches_per_day = clubs.size / 2
(0...num_rounds).collect do |round|
matches_per_day.times do |pair|
club1, club2 = find_club_combination(clubs, round, pair)
Fixture.create(
:home => round.even? ? club1 : club2,
:away => round.even? ? club2 : club1,
:datetime => fixture_date(round)
).save
end
end
Not sure exactly what you are after here, but you seem to want an easier way to do time calculations?
If so, Chronic is pretty cool.
Chronic.parse('next sunday at 4pm')
#=> Sun Nov 20 16:00:00 -0800 2011
match_starts_at = ['16:00', '17:30', '18:10', '22:00']
Chronic.parse("sunday at #{match_starts_at[0]}")
#=> Sun Nov 20 16:00:00 -0800 2011

Resources