I have just gotten deeper into functional aspects of ruby and are fiddling with map/reduce and some filtering.
I have now gotten to a point where I have a list of items of the following structure:
{:price=>100.0, :size=>'small', :description=>'some description'}
The value for :size may be one of ['small', 'medium', 'large'].
Is there a way to partition the whole list into sublists with only those elements which are of size small, medium and large without setting up a filter function for each of these values?
Basically I am asking wether there is some multiclass Array.partition.
Thanks for any help!
I believe you are looking for Enumberable#group_by:
list = [
{:price=>100.0, :size=>'small', :description=>'some description'},
{:price=>123.0, :size=>'small', :description=>'some description 2'},
{:price=>456.0, :size=>'medium', :description=>'some description 3'}
]
list.group_by {|item| item[:size]}
# => {
# "small" => [
# {:price=>100.0, :size=>"small", :description=>"some description"},
# {:price=>123.0, :size=>"small", :description=>"some description 2"}
# ],
# "medium" => [
# {:price=>456.0, :size=>"medium", :description=>"some description 3"}
# ]
# }
input = [
{:price=>100.0, :size=>'small', :description=>'some description 1'},
{:price=>100.0, :size=>'large', :description=>'some description 2'},
{:price=>100.0, :size=>'small', :description=>'some description 3'},
{:price=>100.0, :size=>'large', :description=>'some description 4'},
{:price=>100.0, :size=>'small', :description=>'some description 5'},
{:price=>100.0, :size=>'small', :description=>'some description 6'}
]
input.group_by { |e| e[:size] }
Whether you consider the result should not contain the size in hashes, use Hash#delete to mutate elements:
input.group_by { |e| e.delete :size }
#⇒ {
# "large" => [
# [0] {
# :description => "some description 2",
# :price => 100.0
# },
# [1] {
# :description => "some description 4",
# :price => 100.0
# }
# ],
# "small" => [
# [0] {
# :description => "some description 1",
# :price => 100.0
# },
# [1] {
# :description => "some description 3",
# :price => 100.0
# },
# [2] {
# :description => "some description 5",
# :price => 100.0
# },
# [3] {
# :description => "some description 6",
# :price => 100.0
# }
# ]
# }
I have these parameters:
Parameters: {transactions"=>{
"2"=>{"amount"=>"10", "finance_id"=>"4", "payee_id"=>"5", "category_id"=>"14", "payee_type"=>"Student", "transaction_date"=>"2015-08-10", "title"=>"Receipt No.. (Multiple Fees) F4", "finance_type"=>"FinanceFee", "payment_mode"=>"Cash", "payment_note"=>""},
"1"=>{"amount"=>"10", "finance_id"=>"4", "payee_id"=>"2", "category_id"=>"14", "payee_type"=>"Student", "transaction_date"=>"2015-08-10", "title"=>"Receipt No.. (Multiple Fees) F4", "finance_type"=>"FinanceFee", "payment_mode"=>"Cash", "payment_note"=>""}
}}
I need to set a condition to delete the transaction with amount = 0.I tried this:
params[:transactions].each do |trans|
trans.delete_if {|amount, value| value == 0 || value.nil? || value.empty?}
end
but this doesn't delet the transaction with amount 0.
Just try this
transactions = {
"transactions" =>
{
"2" => {
"amount" => "10",
"finance_id" => "4",
"payee_id" => "5",
"category_id" => "14",
"payee_type" => "Student",
"transaction_date" => "2015-08-10",
"title" => "Receipt No.. (Multiple Fees) F4",
"finance_type" => "FinanceFee",
"payment_mode" => "Cash",
"payment_note" => ""
},
"1" => {
"amount" => "10",
"finance_id" => "4",
"payee_id" => "2",
"category_id" => "14",
"payee_type" => "Student",
"transaction_date" => "2015-08-10",
"title" => "Receipt No.. (Multiple Fees) F4",
"finance_type" => "FinanceFee",
"payment_mode" => "Cash",
"payment_note" => ""
},
"3" => {
"amount" => "0",
"finance_id" => "4",
"payee_id" => "2",
"category_id" => "14",
"payee_type" => "Student",
"transaction_date" => "2015-08-10",
"title" => "Receipt No.. (Multiple Fees) F4",
"finance_type" => "FinanceFee",
"payment_mode" => "Cash",
"payment_note" => ""
},
"4" => {
"amount" => nil,
"finance_id" => "4",
"payee_id" => "2",
"category_id" => "14",
"payee_type" => "Student",
"transaction_date" => "2015-08-10",
"title" => "Receipt No.. (Multiple Fees) F4",
"finance_type" => "FinanceFee",
"payment_mode" => "Cash",
"payment_note" => ""
},
"4" => {
"amount" => "",
"finance_id" => "4",
"payee_id" => "2",
"category_id" => "14",
"payee_type" => "Student",
"transaction_date" => "2015-08-10",
"title" => "Receipt No.. (Multiple Fees) F4",
"finance_type" => "FinanceFee",
"payment_mode" => "Cash",
"payment_note" => ""
}
}
}
transactions["transactions"].delete_if{|_, v| v["amount"].to_s.strip.to_i == 0}
Your value is an string convert this to integer
value.to_i == 0
or
value == 0.to_s
or
value.to_i.zero?
Hey I have an array of hash values as follows.
[{"group" => "1", "message" => "hey", "weight" => 1}, {"group" => "1", "message"
=> "hey1", "weight" => 2}, {"group" => "2", "message" => "hey3", "weight" => 4}]
I want to group_by group and format it so that I get the following:
[{"group" => 1, "messages" => {"hey","hey1"}, "weights" => {1,2}}, {"group" => 2,
"messages" => {"hey3"}, "weights" => {4}}]
Is there a nice ruby way to achieve this?
Edit: Now I have:
[
{"group" => "1", "message" => {"hey" => "1"}},
{"group" => "1", "message" => {"hey1" => "2"}}
]
I'd like to have
{"group" => "1", "messages" => {"hey1" => "1", "hey2" => "2"} }
Based on your revised question:
groups = [
{"group" => "1", "message" => {"hey" => "1"}},
{"group" => "1", "message" => {"hey1" => "2"}}
]
merged = groups.inject do |h1,h2|
h1.merge(h2) do |k,v1,v2|
if v1==v2
v1
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
v1.merge(v2)
else
[*v1,*v2]
end
end
end
p merged
#=> {"group"=>"1", "message"=>{"hey"=>"1", "hey1"=>"2"}}
I think the output you want is:
[{"messages"=>["hey", "hey1"], "weights"=>[1, 2], "group"=>"1"}, {"messages"=>["hey3"], "weights"=>[4], "group"=>"2"}]
If this is the case, this code does what you want:
h.group_by { |item| item["group"] }.values.map do |item|
item.inject({"messages" => [], "weights" => []}) do |result, subitem|
result["group"] = subitem["group"]
result["messages"] << subitem["message"]
result["weights"] << subitem["weight"]
result
end
end
You should be able to improve it knowing more about your specific problem, but it should be a good starting point.
I have a website and I want to make it identify the user's location and according to that move the user to the correct subdomain.
An example:
If I go to the website from France, I want it to redirect me to www.website.com/France
and throw on...
How can I do that please?
At the application server level you can find out which country the user is from by looking it up in a ip-2-country database. They are usually products you buy from a software company specializing in this with different databases for different needs.
Here is one: ip2country
You can also do this on a dns-level if you have a dns provider that can do this for you. This is probably what makes most sense for you. Then your visitors would at once be sent to the nearest server without having to contact a "dispatcher" server first (the dns server would be the router) and after that the correct ip would be cached at some way between the dns server responsible for the domain and dns server along the way all the way to the user.
Here is the solution I'm using on my site [www.vladonai.com][1], it's 100% free (using daily updated [www.zaigadgets.com/geoip][2] source), very fast (possibly much faster than MySQL could do), and requires no database. The database is stored in a binary file, so all what you need is any version of PHP.
The function to convert IP to Country name looks like this:
function GetCountryFromIP($ip = "")
{
if ($ip == "")
$ip = $_SERVER['REMOTE_ADDR'];
//the db obtained from ZaiGadgets.com and converted using convert_ip2country_db_from_csv_to_binary_file_format function
$db_file_path = ip2Country_binary_db_filename;
if (!file_exists($db_file_path))
return "";
$db_file_handle = fopen($db_file_path, "rb");
if (!$db_file_handle)
return "";
$one_record_size = 10; //bytes
$low = 0;
$high = (filesize($db_file_path) / $one_record_size) - 1;
$mid = 0;
$ipFrom = 0;
$ipTo = 0;
$ipArrParts = explode('.', $ip);
$ipLong = ($ipArrParts[0] * 0x1000000) + ($ipArrParts[1] * 0x10000) + ($ipArrParts[2] * 0x100) + ($ipArrParts[3]);
$country = ""; //result
while($low <= $high)
{
$mid = (int)(($low + $high) / 2);
$file_pos = $mid * $one_record_size;
fseek($db_file_handle, $file_pos, SEEK_SET);
$ipFrom = ipdbv2_read32($db_file_handle);
$ipTo = ipdbv2_read32($db_file_handle);
if ($ipFrom < 0)
$ipFrom += pow(2, 32);
if ($ipTo < 0)
$ipTo += pow(2, 32);
if (($ipLong >= $ipFrom) && ($ipLong < $ipTo))
{
//found IP range :)
//echo "Found!!!!!<br>";
$country = fread($db_file_handle, 2);
fclose($db_file_handle);
$country = GetCountryNameFromCountryCode($country);
return $country;
} else
{
if($ipLong <$ipFrom)
{
$high = $mid - 1;
} else
{
$low = $mid + 1;
}
}
}
fclose($db_file_handle);
return "";
}
function ipdbv2_read32($db_file_handle)
{
$data = fread($db_file_handle, 4);
$output = unpack('V', $data);
$val_32_bit = $output[1];
if ($val_32_bit < 0)
$val_32_bit += 4294967296; //correct signed/unsigned issue on 32-bit platforms
return $val_32_bit;
}
Before using the function above you'll need to compile the datbase file using function below:
define( "ip2Country_binary_db_filename", "ip2country_zaigadgets.bin" );
function convert_ip2country_db_from_csv_to_binary_file_format($input_file_path = "")
{
//convert dataf from ZaiGadgets.com to binary db format
if ($input_file_path == "")
$input_file_path = "db.csv";
$output_file_path = ip2Country_binary_db_filename;
$input_file_content = file_get_contents($input_file_path); //read_text_file_content($input_file_path);
if ($input_file_content == "")
{
echo "Error: empty input db - " . $input_file_path . "<br>";
return; //nothing to do
}
unlink($output_file_path); //delete file content
$out_fileHandle = fopen($output_file_path, "cb");
if (!$out_fileHandle)
{
echo "ERROR: Cannot Create file! " . $output_file_path . "<br>";
return;
}
$records_count = 0;
$input_file_lines = explode("\n", $input_file_content); //split it to lines
echo "Text lines count: " . count($input_file_lines) . "<br>";
//for ($input_line_n = 0; $input_line_n < 10; $input_line_n ++)
for ($input_line_n = 0; $input_line_n < count($input_file_lines); $input_line_n ++)
{
$sub_line = $input_file_lines[$input_line_n];
if (strlen($sub_line) > 0 && is_numeric($sub_line[0]))
{
$sub_values = explode(",", $input_file_lines[$input_line_n]);
if (count($sub_values) == 4)
{
$start_ip_addr = $sub_values[0];
$end_ip_addr = $sub_values[1];
$country_code = $sub_values[2];
if (strlen($country_code) == 2)
{
fwrite($out_fileHandle, pack("V", (float)$start_ip_addr), 4);
fwrite($out_fileHandle, pack("V", (float)$end_ip_addr), 4);
fwrite($out_fileHandle, $country_code, strlen($country_code));
$records_count ++;
} else
{
echo "Invalid line " . $input_line_n . " [wrong country len]: " . $sub_line . "<br>";
}
} else
{
echo "Invalid line " . $input_line_n . ": " . $sub_line . "<br>";
}
} else
if ($sub_line != "" && $sub_line[0] != '#' && $sub_line != "startip,endip,countrycode,countryname")
{
echo "Invalid line " . $input_line_n . ": " . $sub_line . "<br>";
}
}
fclose($out_fileHandle);
echo "Data size verification result: " . ($records_count * 10 == filesize($output_file_path) ? "OK" : "Error") . "<br>";
echo "Exported records (blocks) count: " . $records_count . "<br>";
echo "Exporting complete.<br>";
}
Now, if you like it, you can use function below to convert 2-letter country code to readable country name:
function GetCountryNameFromCountryCode($country_code, $b_very_strict = false)
{
$countries = array (
"ac" => "ASCENSION ISLAND",
"ad" => "ANDORRA",
"ae" => "UNITED ARAB EMIRATES",
"af" => "AFGHANISTAN",
"ag" => "ANTIGUA AND BARBUDA",
"ai" => "ANGUILLA",
"al" => "ALBANIA",
"am" => "ARMENIA",
"an" => "NETHERLANDS ANTILLES",
"ao" => "ANGOLA",
"aq" => "ANTARCTICA",
"ar" => "ARGENTINA",
"as" => "AMERICAN SAMOA",
"at" => "AUSTRIA",
"au" => "AUSTRALIA",
"aw" => "ARUBA",
"ax" => "ALAND",
"az" => "AZERBAIJAN",
"ba" => "BOSNIA AND HERZEGOVINA",
"bb" => "BARBADOS",
"bd" => "BANGLADESH",
"be" => "BELGIUM",
"bf" => "BURKINA FASO",
"bg" => "BULGARIA",
"bh" => "BAHRAIN",
"bi" => "BURUNDI",
"bj" => "BENIN",
"bl" => "SAINT BARTHELEMY",
"bm" => "BERMUDA",
"bn" => "BRUNEI DARUSSALAM",
"bo" => "BOLIVIA",
"bu" => "BONAIRE, SAINT EUSTATIUS AND SABA",
"br" => "BRAZIL",
"bs" => "BAHAMAS",
"bt" => "BHUTAN",
"bv" => "BOUVET ISLAND",
"bw" => "BOTSWANA",
"by" => "BELARUS",
"bz" => "BELIZE",
"ca" => "CANADA",
"cc" => "COCOS (KEELING) ISLANDS",
"cd" => "CONGO",
"cf" => "CENTRAL AFRICAN REPUBLIC",
"cg" => "REPUBLIC OF THE CONGO",
"ch" => "SWITZERLAND",
"ci" => "COTE D?VOIRE",
"ck" => "COOK ISLANDS",
"cl" => "CHILE",
"cm" => "CAMEROON",
"cn" => "CHINA",
"co" => "COLOMBIA",
"cr" => "COSTA RICA",
"cu" => "CUBA",
"cv" => "CAPE VERDE",
"cw" => "CURACAO",
"cx" => "CHRISTMAS ISLAND",
"cy" => "CYPRUS",
"cz" => "CZECH REPUBLIC",
"de" => "GERMANY",
"dj" => "DJIBOUTI",
"dk" => "DENMARK",
"dm" => "DOMINICA",
"do" => "DOMINICAN REPUBLIC",
"dz" => "ALGERIA",
"ec" => "ECUADOR",
"ee" => "ESTONIA",
"eg" => "EGYPT",
"eh" => "WESTERN SAHARA",
"er" => "ERITREA",
"eu" => "EUROPEAN UNION",
"es" => "SPAIN",
"et" => "ETHIOPIA",
"fi" => "FINLAND",
"fj" => "FIJI",
"fk" => "FALKLAND ISLANDS",
"fm" => "MICRONESIA",
"fo" => "FAROE ISLANDS",
"fr" => "FRANCE",
"ga" => "GABON",
"gb" => "UNITED KINGDOM",
"gd" => "GRENADA",
"ge" => "GEORGIA",
"gf" => "FRENCH GUIANA",
"gg" => "GUERNSEY",
"gh" => "GHANA",
"gi" => "GIBRALTAR",
"gl" => "GREENLAND",
"gm" => "THE GAMBIA",
"gn" => "GUINEA",
"gp" => "GUADELOUPE",
"gq" => "EQUATORIAL GUINEA",
"gr" => "GREECE",
"gs" => "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS",
"gt" => "GUATEMALA",
"gu" => "GUAM",
"gw" => "GUINEA-BISSAU",
"gy" => "GUYANA",
"hk" => "HONG KONG",
"hn" => "HONDURAS",
"hr" => "CROATIA",
"ht" => "HAITI",
"hu" => "HUNGARY",
"id" => "INDONESIA",
"ie" => "IRELAND",
"il" => "ISRAEL",
"im" => "ISLE OF MAN",
"in" => "INDIA",
"io" => "BRITISH INDIAN OCEAN TERRITORY",
"iq" => "IRAQ",
"ir" => "IRAN",
"is" => "ICELAND",
"it" => "ITALY",
"je" => "JERSEY",
"jm" => "JAMAICA",
"jo" => "JORDAN",
"jp" => "JAPAN",
"ke" => "KENYA",
"kg" => "KYRGYZSTAN",
"kh" => "CAMBODIA",
"ki" => "KIRIBATI",
"km" => "COMOROS",
"kn" => "SAINT KITTS AND NEVIS",
"kp" => "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF",
"kr" => "SOUTH KOREA",
"kw" => "KUWAIT",
"ky" => "CAYMAN ISLANDS",
"kz" => "KAZAKHSTAN",
"la" => "LAOS",
"lb" => "LEBANON",
"lc" => "SAINT LUCIA",
"li" => "LIECHTENSTEIN",
"lk" => "SRI LANKA",
"lr" => "LIBERIA",
"ls" => "LESOTHO",
"lt" => "LITHUANIA",
"lu" => "LUXEMBOURG",
"lv" => "LATVIA",
"ly" => "LIBYA",
"ma" => "MOROCCO",
"mc" => "MONACO",
"md" => "MOLDOVA",
"me" => "MONTENEGRO",
"mf" => "SAINT MARTIN (FRENCH PART)",
"mg" => "MADAGASCAR",
"mh" => "MARSHALL ISLANDS",
"mk" => "REPUBLIC OF MACEDONIA",
"ml" => "MALI",
"mm" => "MYANMAR",
"mn" => "MONGOLIA",
"mo" => "MACAO",
"mp" => "NORTHERN MARIANA ISLANDS",
"mq" => "MARTINIQUE",
"mr" => "MAURITANIA",
"ms" => "MONTSERRAT",
"mt" => "MALTA",
"mu" => "MAURITIUS",
"mv" => "MALDIVES",
"mw" => "MALAWI",
"mx" => "MEXICO",
"my" => "MALAYSIA",
"mz" => "MOZAMBIQUE",
"na" => "NAMIBIA",
"nc" => "NEW CALEDONIA",
"ne" => "NIGER",
"nf" => "NORFOLK ISLAND",
"ng" => "NIGERIA",
"ni" => "NICARAGUA",
"nl" => "NETHERLANDS",
"no" => "NORWAY",
"np" => "NEPAL",
"nr" => "NAURU",
"nu" => "NIUE",
"nz" => "NEW ZEALAND",
"om" => "OMAN",
"pa" => "PANAMA",
"pe" => "PERU",
"pf" => "FRENCH POLYNESIA",
"pg" => "PAPUA NEW GUINEA",
"ph" => "PHILIPPINES",
"pk" => "PAKISTAN",
"pl" => "POLAND",
"pm" => "SAINT PIERRE AND MIQUELON",
"pn" => "PITCAIRN ISLANDS",
"pr" => "PUERTO RICO",
"ps" => "PALESTINIAN TERRITORY, OCCUPIED",
"pt" => "PORTUGAL",
"pw" => "PALAU",
"py" => "PARAGUAY",
"qa" => "QATAR",
"re" => "REUNION",
"ro" => "ROMANIA",
"rs" => "SERBIA",
"ru" => "RUSSIA",
"rw" => "RWANDA",
"sa" => "SAUDI ARABIA",
"sb" => "SOLOMON ISLANDS",
"sc" => "SEYCHELLES",
"sd" => "SUDAN",
"se" => "SWEDEN",
"sg" => "SINGAPORE",
"sh" => "SAINT HELENA",
"si" => "SLOVENIA",
"sj" => "SVALBARD AND JAN MAYEN",
"sk" => "SLOVAKIA",
"sl" => "SIERRA LEONE",
"sm" => "SAN MARINO",
"sn" => "SENEGAL",
"so" => "SOMALIA",
"sr" => "SURINAME",
"st" => "SAO TOME AND PRINCIPE",
"su" => "RUSSIA", //ex-USSR, let's presume it's russia
"sv" => "EL SALVADOR",
"sx" => "SINT MAARTEN (DUTCH PART)",
"sy" => "SYRIA",
"sz" => "SWAZILAN",
"tc" => "TURKS AND CAICOS ISLANDS",
"td" => "CHAD",
"tf" => "FRENCH SOUTHERN TERRITORIES",
"tg" => "TOGO",
"th" => "THAILAND",
"tj" => "TAJIKISTAN",
"tk" => "TOKELAU",
"tl" => "EAST TIMOR",
"tm" => "TURKMENISTAN",
"tn" => "TUNISIA",
"to" => "TONGA",
"tp" => "EAST TIMOR",
"tr" => "TURKEY",
"tt" => "TRINIDAD AND TOBAGO",
//"tv" => "TUVALU", don't include it here - it conflicts with .tv domain!
"tw" => "TAIWAN",
"tz" => "TANZANIA",
"ua" => "UKRAINE",
"ug" => "UGANDA",
"uk" => "UNITED KINGDOM",
"um" => "UNITED STATES MINOR OUTLYING ISLAND",
"us" => "UNITED STATES",
"uy" => "URUGUAY",
"uz" => "UZBEKISTAN",
"va" => "VATICAN CITY STATE",
"vc" => "SAINT VINCENT AND THE GRENADINES",
"ve" => "VENEZUELA",
"vg" => "BRITISH VIRGIN ISLANDS",
"vi" => "U.S. VIRGIN ISLANDS",
"vn" => "VIETNAM",
"vu" => "VANUATU",
"wf" => "WALLIS AND FUTUNA",
"ws" => "SAMOA",
"ye" => "YEMEN",
"yt" => "MAYOTTE",
"yu" => "YUGOSLAVIA",
"za" => "SOUTH AFRICA",
"zm" => "ZAMBIA",
"zw" => "ZIMBABWE"
);
$country = $countries[strtolower($country_code)];
if ($country == "" && !$b_very_strict)
$country = $country_code; //return at least something
return $country;
}
Enjoy!