How to create composable scopes for plain Ruby arrays - arrays

One of the things that I really like about Active Record is its named scopes and being able to chain scopes together to build expressive queries.
What would be a similar way to achieve this with plain Ruby Enumerables/Arrays, ideally without monkey-patching Enumerable or Array in any dangerous way?
For example:
### ActiveRecord Model
class User < ActiveRecord::Base
scope :customers, -> { where(:role => 'customer') }
scope :speaking, ->(lang) { where(:language => lang) }
end
# querying
User.customers.language('English') # all English customers
### Plain-Ruby Array
module User
class << self
def customers
users.select { |u| u[:role] == 'customer' }
end
def speaking(lang)
users.select { |u| u[:language] == lang }
end
private
def users
[
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
]
end
end
end
User.customers # all customers
User.language('English') # all English speakers
# how do I achieve something similar to User.customers.language('English') ...?
I know I can build a method customers_with_language inside the module, but I'm looking for a general way to solve this with any number of "scopes".

Here is a crude implementation of a ScopableArray, which inherits an Array:
class ScopableArray < Array
def method_missing(method_sym, *args)
ScopableArray.new(select { |u| u[method_sym] == args[0] } )
end
end
When this class receives a method it does not identify, it assumes you want to filter it according to a field of the method's name with the argument's value:
users = ScopableArray.new([
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
])
users.role('customer')
# => [{:name=>"John", :language=>"English", :role=>"customer"}, {:name=>"Jean", :language=>"French", :role=>"customer"}]
users.role('customer').language('English')
# => [{:name=>"John", :language=>"English", :role=>"customer"}]
You can also look at ActiveRecord's implementation pattern for a more elaborate scheme where you can define scopes by passing a name and a callable block, something like this:
class ScopableArray2 < Array
class << self
def scope(name, body)
unless body.respond_to?(:call)
raise ArgumentError, 'The scope body needs to be callable.'
end
define_method(name) do |*args|
dup.select! { |x| body.call(x, *args) }
end
end
end
end
Then you can do something like this:
class Users < ScopableArray2
scope :customers, ->(x) { x[:role] == 'customer' }
scope :speaking, ->(x, lang) { x[:language] == lang }
end
users = Users.new([
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
])
users.customers.speaking('English')
# => [{:name=>"John", :language=>"English", :role=>"customer"}]

Related

Perl: Can you use more than two arrays in the map function?

I've been learning Perl as of late, and ran into an interesting problem. How would I go about using two arrays in map like so?
use warnings;
use strict;
use Data::Dumper;
my $names->{name} = ['Bill', 'Smith'];
my $cars->{model} = ['Honda', 'Toyota'];
my $obj = {
'Students' => [
map {
'Name' => $_,
'Model' => $_
}, #{$names->{name}}, #{$cars->{model}}
]
};
print Dumper $obj;
This will print, it's producing two too many objects than I would like.
$VAR1 = {
'Students' => [
{
'Model' => 'Bill',
'Name' => 'Bill'
},
{
'Model' => 'Smith',
'Name' => 'Smith'
},
{
'Model' => 'Honda',
'Name' => 'Honda'
},
{
'Model' => 'Toyota',
'Name' => 'Toyota'
}
]
};
What I'd like to do is have map work in a way that it'll produce these results
$VAR1 = {
'Students' => [
{
'Model' => 'Honda',
'Name' => 'Bill'
},
{
'Model' => 'Toyota',
'Name' => 'Smith'
}
]
};
use warnings;
use strict;
use Data::Dumper;
my $names->{name} = [ qw(Bill Smith) ];
my $cars->{model} = [ qw(Honda Toyota) ];
my $obj = {
Students => [
map {
{ Name => $names->{name}[$_], Model => $cars->{model}[$_] }
}
0 .. $#{$names->{name}}
]
};
print Dumper $obj;
The body of the map uses both arrays so you only need to supply indices to it.
For this both arrayrefs must be of same length and items to be paired at same indices.
The syntax $#$arrayref is for the last index in the $arrayref, and since here we have to also dereference first there's an extra pair of {}.
What you have dereferences both arrayrefs, to build a single flat list for the map, producing
my $obj = {
'Students' => [
map {
'Name' => $_,
'Model' => $_
}, qw(Bill Smith Honda Toyota);
]
};
First, this makes no sense:
my $names->{name} = ['Bill', 'Smith'];
my $cars->{model} = ['Honda', 'Toyota'];
I'll use
my #names = ( 'Bill', 'Smith' );
my #models = ( 'Honda', 'Toyota' );
We could iterate over the indexes.
map { +{ Name => $names[$_], Model => $cars[$_] } }
0..$#names
Alternatively, we could enlist zip.
use List::Util qw( zip );
map { +{ Name => $_->[0], Model => $_->[1] } }
zip \#names, \#models
This is probably better written as a while loop:
my #names = qw( Bill Smith );
my #models = qw( Honda Toyota );
my #students;
push #students, { 'name' => shift #names, 'model' => shift #models } while #names;

Laravel method to add a value within a deeply nested array using "dot" notation

Arr::add doesn't work with dot notation. Arr::set will create a new element only if it does not exist, otherwise, it will overwrite the existing one. There is data_set, it works similar to Arr::set, but it accepts the overwrite flag which does not do what I need (if it's set to false instead of adding a new item, it will just skip setting new value).
My code:
$array = [['name' => 'Test', 'link' => 'test_link'], ['name' => 'Test1', 'link' => 'test1_link']];
$result = [];
$position = 1;
foreach($array as $element) {
Arr::set($result, 'itemListElement.type', 'ListItem');
Arr::set($result, 'itemListElement.position', $position);
Arr::set($result, 'itemListElement.item.name', $element['name']);
Arr::set($result, 'itemListElement.item.link', $element['link']);
$position++;
}
And I would like to have multiple list elements within the itemListElement parent, instead of having one list element which is being overwritten all the time.
Here is what it should look like:
[
"itemListElement" => [
[
"type" => "ListItem",
"position" => 1,
"item" => [
"name" => "Test",
"url" => "test_url",
]
],
[
"type" => "ListItem",
"position" => 1,
"item" => [
"name" => "Test1",
"url" => "test1_url",
]
],
]
]
Can you try this:
$array = [['name' => 'Test', 'link' => 'test_link'], ['name' => 'Test1', 'link' => 'test1_link']];
$result = [];
foreach($array as $key=>$element) {
Arr::set($result["itemListElement"][$key], 'type', 'ListItem');
Arr::set($result["itemListElement"][$key], 'position', $key + 1);
Arr::set($result["itemListElement"][$key], 'item.name', $element['name']);
Arr::set($result["itemListElement"][$key], 'item.link', $element['link']);
}
$result;

Ruby key value pairs not printing correctly

I am trying to match key value pairs in an array and print them in a clear format:
array = [
{
'name' => 'Tom',
'age' => '31',
'weight' => '180'
},
{
'name' => 'Jane',
'age' => '24',
'weight' => '110'
}
]
array.each do |key, value|
if #{key} == "name"
puts "Name_is=#{key}"
else
puts "#{key}=#{value}"
end
end
This results in:
Name_is={"name"=>"Tom", "age"=>"31", "weight"=>"180"}
{"name"=>"Tom", "age"=>"31", "weight"=>"180"}=
Name_is={"name"=>"Jane", "age"=>"24", "weight"=>"110"}
{"name"=>"Jane", "age"=>"24", "weight"=>"110"}=
Expected result is:
Name_is=Tom
age=31
weight=180
Name_is=Jane
age=24
weight=110
What am I not doing right?
You have an array of hashes, you need to make a nested loop which loops over the array and, for each hash in the array, loops on the key/value pairs:
array.each do |hash|
hash.each do |key, value|
if key == "name"
puts "Name_is=#{key}"
else
puts "#{key}=#{value}"
end
end
end
Also I'm not sure why you have if #{key} == "name" while you can simply have if key == "name".
I think this is a Ruby-like solution:
array = [
{
'name' => 'Tom',
'age' => '31',
'weight' => '180'
},
{
'name' => 'Jane',
'age' => '24',
'weight' => '110'
}
]
array.each do |hash|
hash['Name_is'] = hash.delete 'name'
hash.each do |key, value|
puts "#{key}=#{value}"
end
end

Perl: Filter AoH output

I'm trying to sort my AoH which looks like this:
$VAR1 = [
{
'Name' => 'John',
'Lastname' => 'Derp',
'Organization' => 'Finance',
'OfficeNR' => '23',
'ID' => '145'
},
{
'Name' => 'Kate',
'Lastname' => 'Herp',
'Organization' => 'HR',
'OfficeNR' => '78',
'ID' => '35'
},
{
'Name' => 'Jack',
'Lastname' => 'Serp',
'Organization' => 'Finance',
'OfficeNR' => '23',
'ID' => '98'
}
];
What I'm trying to do is to filter my output using keys from AoH, for example print out only those who have 'Organization' => 'Finance'.
I've tried to solve it using new array:
my #SortedAoH = sort { {Organization=>{'Finance'}} } #AoH;
But it doesn't work.
What you want is grep, not sort. You are getting the basic syntax of equivalence checking wrong as well.
Anyway, the filter is:
my #finance_orgs = grep { $_->{'Organization'} eq 'Finance' } #AoH;
The #finance_orgs variable will now only include the ones with Organization set to Finance.
Just an explanation of the pieces:
The $_ variable is the variable that gets assigned whenever the value is implied in a block, such as in grep or map or in a for loop without an explicitly named variable.
$_->{'Organization'} performs a hash lookup on the hash as it iterates through each entry in your array.
eq is the operator used to test for string equivalence (as opposed to == which tests for numeric equivalence).

PHP Display Array with Array_Slice

$array = array(
'name' => 'john',
'age' => '25',
'birthday' => '02-03-1988',
'gender' => 'male',
'telephone' => '98676878',
'location' => 'Australia'
);
$array_slice = array_slice($array, 0, 3);
foreach($array_slice as $key => $val) {
if($key !== 'age') {
echo $key.' => '.$val.'<br>';
}
}
Output:
name => john
birthday => 02-03-1988
How to display 3 values of array?
So, I want output to be like this:
name => john
birthday => 02-03-1988
gender => male
I am very beginner in programmer, thanks for help.
Update: new solution after exchanging some comments below
You must filter the array and get the result in a new array. Then you display the content of the second array.
To filter your original array, use either array_intersect_key or array_filter functions.

Resources