I am using
ps -l -u user
to get the running processes of a given user.
Now, when I want to split the information into arrays in PHP I am in trouble because ps outputs the data for humans to read without fixed delimiters. So you can't split with space or tab as regex.
So far I can only detect the columns by character positions.
Is there any way in php to split a string into an array at certain positions? Something like:
$array=split_columns($string, $positions=array(1, 10, 14))
to cut a string into pieces at positions 1, 10 and 14?
I decided to try a regex approach with dynamic pattern building. Not sure it is the best way, but you can give it a try:
function split_columns ($string, $indices) {
$pat = "";
foreach ($indices as $key => $id) {
if ($key==0) {
$pat .= "(.{" . $id . "})";
} else if ($key<count($indices)) {
$pat .= "(.{" . ($id-$indices[$key-1]) . "})";
}
}
$pats = '~^'.$pat.'(.*)$~m';
preg_match_all($pats, $string, $arr);
return array_slice($arr, 1);
}
$string = "11234567891234567\n11234567891234567"; // 1: '1', 2: '123456789', 3: '1234', 4: '567'
print_r (split_columns($string, $positions=array(1, 10, 14)));
See the PHP demo
The point is:
Build the pattern dynamically, by checkign the indices, subtracting the previous index value from each subsequent one, and append the (.*)$ at the end to match the rest of the line.
The m modifier is necessary for ^ to match the start of the line and $ the end of the line.
The array_slice($arr, 1); will remove the full match from the resulting array.
A sample regex (meeting OP requirements)) will look like ^(.{1})(.{9})(.{4})(.*)$
I modified Wiktor's solution as I don't need that many information.
function split_columns ($string, $indices) {
$pat = "";
foreach ($indices as $key => $id) {
if ($key==0) {
$pat .= "(.{" . $id . "})";
} else if ($key<count($indices)) {
$pat .= "(.{" . ($id-$indices[$key-1]) . "})";
}
}
$pats = '~^'.$pat.'(.*)$~m';
preg_match_all($pats, $string, $arr, PREG_SET_ORDER);
$arr=$arr[0];
return array_slice($arr, 1);
}
In PHP preg_split will help you here. You can split by a number of whitespaces e.g.:
<?
$text = '501 309 1 4004 0 4 0 2480080 10092 - S 0 ?? 0:36.77 /usr/sbin/cfpref
501 310 1 40004004 0 37 0 2498132 33588 - S 0 ?? 0:23.86 /usr/libexec/Use
501 312 1 4004 0 37 0 2471032 8008 - S 0 ?? 19:06.48 /usr/sbin/distno';
$split = preg_split ( '/\s+/', $text);
print_r($split);
If you know the number of columns you can then go through the array and take that number of columns as one row.
Related
Old question name: How to effectively split a binary string in a groups of 10, 0, 11?
I have some strings as an input, which are binary representation of a number.
For example:
10011
100111
0111111
11111011101
I need to split these strings (or arrays) into groups of 10, 0, and 11 in order to replace them.
10 => 11
0 => 0
11 => 10
How to do it? I have tried these options but don't work.
preg_match('/([10]{2})(0{1})([11]{2})/', $S, $matches);
It should be [10] [0], [11] for 10011 input.
And it should be 11010 when replaced.
UPD1.
Actually, I'm trying to do a negation algorithm for converting a positive number in a base -2 to a negative one in a base -2.
It could be done with an algorithm from Wikipedia with a loop. But byte groups replacing is a much faster. I have implemented it already and just trying to optimize it.
For this case 0111111 it's possible to add 0 in the end. Then rules will be applied. And we could remove leading zeros in a result. The output will be 101010.
UPD2.
#Wiktor Stribiżew proposed an idea how to do a replace immediately, without splitting bytes into groups first.
But I have a faster solution already.
$S = strtr($S, $rules);
The meaning of this question isn't do a replacement, but get an array of desired groups [11] [0] [10].
UPD3.
This is a solution which I reached with an idea of converting binary groups. It's faster than one with a loop.
function solution2($A)
{
$S = implode('', $A);
//we could add leading 0
if (substr($S, strlen($S) - 1, 1) == 1) {
$S .= '0';
}
$rules = [
'10' => '11',
'0' => '0',
'11' => '10',
];
$S = strtr($S, $rules);
$arr = str_split($S);
//remove leading 0
while ($arr[count($arr) - 1] == 0) {
array_pop($arr);
}
return $arr;
}
But the solution in #Alex Blex answer is faster.
You may use a simple /11|10/ regex with a preg_replace_callback:
$s = '10011';
echo preg_replace_callback("/11|10/", function($m) {
return $m[0] == "11" ? "10" : "11"; // if 11 is matched, replace with 10 or vice versa
}, $s);
// => 11010
See the online PHP demo.
Answering the question
algorithm for converting a positive number in a base -2 to a negative one in a base -2
I believe following function is more efficient than a regex:
function negate($negabin)
{
$mask = 0xAAAAAAAAAAAAAAA;
return decbin((($mask<<1)-($mask^bindec($negabin)))^$mask);
}
Parameter is a positive int60 in a base -2 notation, e.g. 11111011101.
The function converts the parameter to base 10, negate it, and convert it back to base -2 as described in the wiki: https://en.wikipedia.org/wiki/Negative_base#To_negabinary
Works on 64bit system, but can easily adopted to work on 32bit.
I'm trying to print the possible words that can be formed from a phone number in php. My general strategy is to map each digit to an array of possible characters. I then iterate through each number, recursively calling the function to iterate over each possible character.
Here's what my code looks like so far, but it's not working out just yet. Any syntax corrections I can make to get it to work?
$pad = array(
array('0'), array('1'), array('abc'), array('def'), array('ghi'),
array('jkl'), array('mno'), array('pqr'), array('stuv'), array('wxyz')
);
function convertNumberToAlpha($number, $next, $alpha){
global $pad;
for($i =0; $i<count($pad[$number[$next]][0]); $i++){
$alpha[$next] = $pad[$next][0][$i];
if($i<strlen($number) -1){
convertNumberToAlpha($number, $next++, $alpha);
}else{
print_r($alpha);
}
}
}
$alpha = array();
convertNumberToAlpha('22', 0, $alpha);
How is this going to be used? This is not a job for a simple recursive algorithm such as what you have suggested, nor even an iterative approach. An average 10-digit number will yield 59,049 (3^10) possibilities, each of which will have to be evaluated against a dictionary if you want to determine actual words.
Many times, the best approach to this is to pre-compile a dictionary which maps 10-digit numbers to various words. Then, your look-up is a constant O(1) algorithm, just selecting by a 10 digit number which is mapped to an array of possible words.
In fact, pre-compiled dictionaries were the way that T9 worked, mapping dictionaries to trees with logarithmic look-up functions.
The following code should do it. Fairly straight forward: it uses recursion, each level processes one character of input, a copy of current combination is built/passed at each recursive call, recursion stops at the level where last character of input is processed.
function alphaGenerator($input, &$output, $current = "") {
static $lookup = array(
1 => "1", 2 => "abc", 3 => "def",
4 => "ghi", 5 => "jkl", 6 => "mno",
7 => "pqrs", 8 => "tuv", 9 => "wxyz",
0 => "0"
);
$digit = substr($input, 0, 1); // e.g. "4"
$other = substr($input, 1); // e.g. "3556"
$chars = str_split($lookup[$digit], 1); // e.g. "ghi"
foreach ($chars as $char) { // e.g. g, h, i
if ($other === false) { // base case
$output[] = $current . $char;
} else { // recursive case
alphaGenerator($other, $output, $current . $char);
}
}
}
$output = array();
alphaGenerator("43556", $output);
var_dump($output);
Output:
array(243) {
[0]=>string(5) "gdjjm"
[1]=>string(5) "gdjjn"
...
[133]=>string(5) "helln"
[134]=>string(5) "hello"
[135]=>string(5) "hfjjm"
...
[241]=>string(5) "iflln"
[242]=>string(5) "ifllo"
}
You should read Norvigs article on writing a spellchecker in Python http://norvig.com/spell-correct.html . Although its a spellchecker and in python not php, it is the same concept around finding words with possible variations, might give u some good ideas.
I want to format the credit cards like below when i display it,
Eg:
1234 4567 9874 1222
as
1xxx xxxx xxx 1222
Is there any formatting function like this in Yii ?
No - but there's nothing wrong with using straight PHP.
If you always want the 1st and the last 4 chars you can do something like this:
$last4 = substr($cardNum, -4);
$first = substr($cardNum, 0, 1);
$output = $first.'xxx xxxx xxxx '.$last4;
There are many ways to do this, nothing Yii specific
You could do it using str_split (untested):
$string = "1234 4567 1234 456";
$character_array = str_split($string);
for ($i = 1; $i < count($character_array) - 4; $i++) {
if ($character_array[$i] != " "){
$character_array[$i] = "x";
}
}
echo implode($character_array);
So we are creating an array of characters from the string called
$character_array.
We are then looping thru the characters (starting from position 1,
not 0, so the first character is visible).
We loop until the number of entries in the array minus 4 (so the last
4 characters are not replaced) We replace each character in the loop
with an 'x' (if it's not equal to a space)
We the implode the array back into a string
And you could also use preg_replace :
$card='1234 4567 9874 1222';
$xcard = preg_replace('/^([0-9])([- 0-9]+)([0-9]{4})$/', '${1}xxx xxxx xxxx ${3}', $card);
This regex will also take care of hyphens.
There is no in-built function in Yii.
I would like to ask for help with this, I wanted to check for a string
matching a decimal followed by a letter (15M, 15.3M, 15T or 15.3T) and
replace it with zeroes.
Like these:
15M -> 15000000
15.3M -> 15300000
15T -> 15000
15.3T -> 15300
I tried doing this with str_replace but can't get it right. Hope
someone can help me.
"T" could be "thousand" or "trillion", you know.
$s = "15.7T";
$factors = Array('T' => 1000, 'M' => 1000000, 'B' => 1000000000.);
$matches = Array();
if (0 < preg_match('/([0-9]+(?:\.[0-9]*)?)([TMB])/', $s, $matches)) {
print("$s -> " . $matches[1] * $factors[$matches[2]]);
}
prints:
15.7T -> 15700
edit:
Anchoring (makes any garbage on the front or back mean no match):
'/^(...)$/'
You may want to make whitespace allowed, however:
'/^\s*(...)\s*$/'
You can also use "\d" in place of "[0-9]:
'/(\d+(?:\.\d*)?)([TMB])/'
Case insensitivity:
'/.../i'
...
print("$s -> " . $matches[1] * $factors[strtoupper($matches[2])]);
Optional factor:
'/([0-9]+(?:\.[0-9]*)?)([TMB])?/'
$value = $matches[1];
$factor = isset($matches[2]) ? $factors[$matches[2]] : 1;
$output = $value * $factor;
Use printf to control output formatting:
print($value) -> 1.57E+10
printf("%f", $value); -> 15700000000.000000
printf("%.0f", $value); -> 15700000000
Stephan202 recommended a clever trick, put the "?" (optional) within the parens and you're guaranteed a match string, it just might be blank. Then you can use the blank as an array key to get the default value without having to test whether it matched or not.
Putting all that together:
$factors = Array('' => 1, 'T' => 1e3, 'M' => 1e6, 'B' => 1e9);
if (0 < preg_match('/^\s*(\d+(?:\.\d*)?)([TMB]?)\s*$/i', $s, $matches)) {
$value = $matches[1];
$factor = $factors[$matches[2]];
printf("'%s' -> %.0f", $s, $value * $factor);
}
Alternative version which performs a conversion on all prices found:
$multiply = array('' => 1, 'T' => 1000, 'M' => 1000000);
function convert($matches) {
global $multiply;
return $matches[1] * $multiply[$matches[3]];
}
$text = '15M 15.3M 15.3T 15';
print(preg_replace_callback('/(\d+(\.\d+)?)(\w?)/', 'convert', $text));
In Perl:
#!/usr/bin/perl
use strict;
use warnings;
my %factors = (
B => 1_000_000_000,
M => 1_000_000,
T => 1_000,
);
while ( <DATA> ) {
next unless m{
^
(?<number> -? [0-9]+ (?:\.[0-9]+)? )
(?<factor>B|M|T)?
$
}x;
my $number = $+{factor}
? sprintf( '%.0f', $+{number} * $factors{ $+{factor} } )
: $+{number}
;
print $number, "\n";
}
__DATA__
15M
15B
15T
15.3M
15.3B
15.3T
15
15.3
Output:
C:\Temp> z
15000000
15000000000
15000
15300000
15300000000
15300
15
15.3
Have no idea how to make it in one regular expression, here is my try in Ruby.
#!/usr/bin/ruby
def trans s
h={"M"=>10**6 ,"T"=>10**3}
a = s.split(/[MT]/) + [s.split("").last]
(a[0].to_f * h[a[1]]).to_i
end
puts trans "15M"
puts trans "15.3M"
puts trans "15T"
puts trans "15.3T"
I have sets of 5, 6 and 7 digit numbers. I need them to be displayed in the 000/000/000 format. So for example:
12345 would be displayed as 000/012/345
and
9876543 would be displayed as 009/876/543
I know how to do this in a messy way, involving a series of if/else statements, and strlen functions, but there has to be a cleaner way involving regex that Im not seeing.
sprintf and modulo is one option
function formatMyNumber($num)
{
return sprintf('%03d/%03d/%03d',
$num / 1000000,
($num / 1000) % 1000,
$num % 1000);
}
$padded = str_pad($number, 9, '0', STR_PAD_LEFT);
$split = str_split($padded, 3);
$formatted = implode('/', $split);
You asked for a regex solution, and I love playing with them, so here is a regex solution!
I show it for educational (and fun) purpose only, just use Adam's solution, clean, readable and fast.
function FormatWithSlashes($number)
{
return substr(preg_replace('/(\d{3})?(\d{3})?(\d{3})$/', '$1/$2/$3',
'0000' . $number),
-11, 11);
}
$numbers = Array(12345, 345678, 9876543);
foreach ($numbers as $val)
{
$r = FormatWithSlashes($val);
echo "<p>$r</p>";
}
OK, people are throwing stuff out, so I will too!
number_format would be great, because it accepts a thousands separator, but it doesn't do padding zeroes like sprintf and the like. So here's what I came up with for a one-liner:
function fmt($x) {
return substr(number_format($x+1000000000, 0, ".", "/"), 2);
}
Minor improvement to PhiLho's suggestion:
You can avoid the substr by changing the regex to:
function FormatWithSlashes($number)
{
return preg_replace('/^0*(\d{3})(\d{3})(\d{3})$/', '$1/$2/$3',
'0000' . $number);
}
I also removed the ? after each of the first two capture groups because, when given a 5, 6, or 7 digit number (as specified in the question), this will always have at least 9 digits to work with. If you want to guard against the possibility of receiving a smaller input number, run the regex against '000000000' . $number instead.
Alternately, you could use
substr('0000' . $number, -9, 9);
and then splice the slashes in at the appropriate places with substr_replace, which I suspect may be the fastest way to do this (no need to run regexes or do division), but that's really just getting into pointless optimization, as any of the solutions presented will still be much faster than establishing a network connection to the server.
This would be how I would write it if using Perl 5.10 .
use 5.010;
sub myformat(_;$){
# prepend with zeros
my $_ = 0 x ( 9-length($_[0]) ) . $_[0];
my $join = $_[1] // '/'; # using the 'defined or' operator `//`
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Tested with:
$_ = 11111;
say myformat;
say myformat(2222);
say myformat(33333,';');
say $_;
returns:
000/011/111
000/002/222
000;033;333
11111
Back-ported to Perl 5.8 :
sub myformat(;$$){
local $_ = #_ ? $_[0] : $_
# prepend with zeros
$_ = 0 x ( 9-length($_) ) . $_;
my $join = defined($_[1]) ? $_[1] :'/';
# m// in a list context returns ($1,$2,$3,...)
join $join, m/ ^ (\d{3}) (\d{3}) (\d{3}) $ /x;
}
Here's how I'd do it in python (sorry I don't know PHP as well). I'm sure you can convert it.
def convert(num): #num is an integer
a = str(num)
s = "0"*(9-len(a)) + a
return "%s/%s/%s" % (s[:3], s[3:6], s[6:9])
This just pads the number to have length 9, then splits the substrings.
That being said, it seems the modulo answer is a bit better.