What is the right way to localize a list of strings? I know that the separator can be localized to a comma or a semi-colon but does the conjunction get localized? If so, what would my format string for an arbitrary length list look like?
Example
"Bat, Cat and Dog". I could use the separator as per locale and construct the LIST as the following:
LIST := UNIT
LISTMID := UNIT SEPARATOR UNIT
LISTMID := LISTMID SEPARATOR UNIT
LIST := UNIT CONJUNCTION UNIT
LIST := LISTMID CONJUNCTION UNIT
Would I have to craft this rule per language? Any libraries available to help with this?
I came here looking for an answer to the same question, and ended up doing more googling, which found this: http://icu-project.org/apiref/icu4j/com/ibm/icu/text/ListFormatter.html
The class takes Parameters two, start, middle, and end:
two - string for two items, containing {0} for the first, and {1} for the second.
start - string for the start of a list items, containing {0} for the first, and {1} for the rest.
middle - string for the start of a list items, containing {0} for the first part of the list, and {1} for the rest of the list.
end - string for the end of a list items, containing {0} for the first part of the list, and {1} for the last item.
So, for English, that would be:
- TWO := "{0} and {1}"
- START := "{0}, {1}"
- MIDDLE := "{0}, {1}"
- END := "{0} and {1}"
I wrote a quick Lua demonstration for how I imagine this works:
function list_format(words, templates)
local length = #words
if length == 1 then return words[1] end
if length == 2 then
return replace(replace(templates['two'], '{0}', words[1]),
'{1}', words[2])
end
local result = replace(templates['end'], '{1}', words[length])
while length > 3 do
length = length - 1
local mid = replace(templates['middle'], '{1}', words[length])
result = replace(result, '{0}', mid)
end
result = replace(result, '{0}', words[2])
result = replace(templates['start'], '{1}', result)
result = replace(result, '{0}', words[1])
return result
end
function replace(template, index, text)
str, _ = string.gsub(template, index, text)
return str
end
local english = {
["two"] = "{0} and {1}",
["start"] = "{0}, {1}",
["middle"] = "{0}, {1}",
["end"] = "{0} and {1}"
}
print(list_format({"banana"}, english))
print(list_format({"banana", "apple"}, english))
print(list_format({"banana", "apple", "mango"}, english))
print(list_format({"banana", "apple", "mango", "pineapple"}, english))
It should be trivial to adapt this for other languages.
Related
My task is to generate a random string with following parameters:
At least one Uppercase
At least one lower
At least one digit
No repeated chars/digits allowed ( e.g. aa not allowed, aba is allowed, Aa is allowed)
I'm able to generate a random string with 1,2,3 parameters but parameter 4 logic is missing.
inputChars = [('a'..'z'), ('A'..'Z'),(0..9)].map(&:to_a).flatten
string = (0...16).map { inputChars[rand(inputChars.length)] }.join
require 'set'
inputChars = [('a'..'z'), ('A'..'Z'),(0..9)].map(&:to_a).flatten
set_string = Set.new
loop do
break if set_string.size == 16
cr = inputChars[rand(inputChars.length)]
set_string << cr
end
output = set_string.to_a.join
i just change your map operation to loop operation and add Set data structure to store the character from random inputChars operation. Using Set will not allow same character
Let's begin by defining two constants.
CHARS_BY_TYPE = {
lower: ('a'..'z').to_a.freeze,
upper: ('A'..'Z').to_a.freeze,
digit: ('0'..'9').to_a.freeze
}.freeze
ALL = (CHARS_BY_TYPE[:lower] + CHARS_BY_TYPE[:upper] + CHARS_BY_TYPE[:digit]).freeze
#=> [["a", "b",..., "z", "A", "B",..., "Z", "0", "1",..., "9"]
I will initially build a string of a specified length by randomly selecting one character at a time from the array ALL, ensuring that no two consecutive characters are the same. There is no assurance, however, that the resulting string will contain at least one uppercase letter, one lowercase letter and one digit.
def append_random_char(last_char)
loop do
ch = ALL.sample
break ch unless ch == last_char
end
end
Our main method will begin as follows:
def random_string(str_len)
raise ArgumentError if str_len < 3
(str_len - 2).times.with_object('') { |_,s| s << append_random_char(s[-1]) }
# ...
end
For example:
s = random_string(40)
#=> "arN64kDw6ClzcNMj8WAkj1NJC2B5oFoRlcXl5S"
str_len is the required string length, 40 in the example. Observe that s contains 38 characters of which no two successive characters are equal. We will need to add 2 characters later. If the string contained no digits, for example, at least one of those two characters added (at a random location) will be a (randomly-selected) digit. If the string were shorter and contained, for example, digits only, the two characters added will be an uppercase letter and a lowercase letter.
Next we need to see if the string is lacking an uppercase letter, a lowercase letter and/or a digit. (It cannot be missing all three, as the string must contain at least three characters.)
require 'set'
def types_to_add(str)
[:lower, :upper, :digit].select do |type|
st = CHARS_BY_TYPE[type].to_set
str.each_char.none? { |ch| st.include?(ch) }
end
end
For the random string generated above we obtain:
types_to_add(s)
#=> []
meaning that the string contains at least one uppercase letter, one lowercase letter and one digit. Try this:
types_to_add(s.gsub(/\d|[A-Z]/, '')
#=> [:upper, :digit]
See Enumerable#none?. CHARS_BY_TYPE[type] is converted to a set merely to speed look-ups.
Suppose now we need to insert an uppercase letter, lowercase letter or digit to satisfy the requirement that there is at least one of each in the string. Specifically, we wish to insert a randomly-drawn character (from CHARS_BY_TYPE[:lower], CHARS_BY_TYPE[:upper] or CHARS_BY_TYPE[:digit]) at a random location in the string we are constructing, with the restriction that neither the preceding nor following character is the same character.
def insert_in_string(str, ch)
i = loop do
i = rand(str.size + 1)
next if ch == str[i]
break i if i.zero? || ch != str[i-1]
end
str.insert(i, ch)
end
For example, if we were to insert the character '0' into (a copy of) our string s (which is not needed):
insert_in_string(s.dup, '0')
#=> "arN64kDw6ClzcN0Mj8WAkj1NJC2B5oFoRlcXl5S"
s #=> "arN64kDw6ClzcNMj8WAkj1NJC2B5oFoRlcXl5S"
^
This inserts the character ch before the character in str at index i. If rand(str.size + 1) returns str.size ch is inserted after the last character of str.
Following this operation the final step is to use the method append_random_char to build the string out to the desired length.
The completed main method is as follows.
def random_string(str_len)
raise ArgumentError if str_len < 3
s = (str_len - 2).times.with_object('') { |_,s| s << append_random_char(s[-1]) }
types_to_add(s).each { |type| insert_in_string(s, CHARS_BY_TYPE[type].sample) }
(str_len - s.size).times { s << append_random_char(s[-1]) }
s
end
s = random_string(40)
#=> "PtQrVFZWUYFwiwRy3ySfAy42G1NT98J6cMVMaWeT"
s.match?(/[a-z]/)
#=> true
s.match?(/[A-Z]/)
#=> true
s.match?(/\d/)
#=> true
s.size
#=> 40
This is how I would do it (warning: Not tested. Just want to present the idea
for my algorithm). I first take a random number for the length of the resulting random string (the length will be between 4 and 16 characters). Then I determine
randomly, how many of them are upper case / lower case / digits, and based on
these decision, I generate the string, ensuring that I don't get any duplicates
in succession.
uchars=('A'..'Z').to_a
lchars=('a'..'z').to_a
dchars=('0'..'9').to_a
charmap = { u: uchars, l: lchars, d: dchars }
total_length=rand(13)+4 # Total length of string to be generated
total_u=rand(total_length-3)+1 # Total number of uchars to be generated
total_l=rand(total_length-total_u-2)+1 # Total number of lchars
total_d=total_length-total_u-total_l # Total number of digits
# Array of types to generate
chartypes=([:u]*total_u + [:l]*total_l + [:d]*total_d).shuffle
# chartypes is an array similar to [:u,:d,:d,:l,:u], where the
# symbols designate the kind of character to be generated.
# outstr : random string to be generated
outstr = charmap[chartypes.first].sample
last_char = outstr.dup
total_length.times do |index|
loop do
nextchar = charmap[chartypes[index]].sample
if nextchar != last_char
outstr << nextchar
last_char = nextchar
break
end
end
end
**It takes Input as a string such as this - 'Nice one'
And Output gives - 4,3 (which is no. Of words in sentence or string)
**
function countx(str)
local count = {}
for i = 1, string.len(str) do
s = ''
while (i<=string.len(str) and string.sub(str, i, i) ~= ' ' ) do
s = s .. string.sub(str, i, i)
i = i+1
end
if (string.len(s)>0) then
table.insert(count,string.len(s))
end
end
return table.concat(count, ',')
end
You can find a simple alternative with your new requirements:
function CountWordLength (String)
local Results = { }
local Continue = true
local Position = 1
local SpacePosition
while Continue do
SpacePosition = string.find(String, " ", Position)
if SpacePosition then
Results[#Results + 1] = SpacePosition - Position
Position = SpacePosition + 1
-- if needed to print the string
-- local SubString = String:sub(Position, SpacePosition)
-- print(SubString)
else
Continue = false
end
end
Results[#Results + 1] = #String - Position + 1
return Results
end
Results = CountWordLength('I am a boy')
for Index, Value in ipairs(Results) do
print(Value)
end
Which gives the following results:
1
2
1
3
def countLenWords(s):
s=s.split(" ")
s=map(len,s)
s=map(str,s)
s=list(s)
return s
The above functions returns a list containing number of characters in each word
s=s.split(" ") splits string with delimiter " " (space)
s=map(len,s) maps the words into length of the words in int
s=map(str,s) maps the values into string
s=list(s) converts map object to list
Short version of above function (all in one line)
def countLenWords(s):
return list(map(str,map(len,s.split(" "))))
-- Localise for performance.
local insert = table.insert
local text = 'I am a poor boy straight. I do not need sympathy'
local function word_lengths (text)
local lengths = {}
for word in text:gmatch '[%l%u]+' do
insert (lengths, word:len())
end
return lengths
end
print ('{' .. table.concat (word_lengths (text), ', ') .. '}')
gmatch returns an iterator over matches of a pattern in a string.
[%l%u]+ is a Lua regular expression (see http://lua-users.org/wiki/PatternsTutorial) matching at least one lowercase or uppercase letter:
[] is a character class: a set of characters. It matches anything inside brackets, e.g. [ab] will match both a and b,
%l is any lowercase Latin letter,
%u is any uppercase Latin letter,
+ means one or more repeats.
Therefore, text:gmatch '[%l%u]+' will return an iterator that will produce words, consisting of Latin letters, one by one, until text is over. This iterator is used in generic for (see https://www.lua.org/pil/4.3.5.html); and on any iteration word will contain a full match of the regular expression.
I have such string:
"k1=v1; k2=v2; k3=v3"
Is there any simple way to make a map[string]string from it?
You will need to use a couple of calls to strings.Split():
s := "k1=v1; k2=v2; k3=v3"
entries := strings.Split(s, "; ")
m := make(map[string]string)
for _, e := range entries {
parts := strings.Split(e, "=")
m[parts[0]] = parts[1]
}
fmt.Println(m)
The first call will separate the different entries in the supplied string while the second will split the key/values apart. A working example can be found here.
I have a string that contains 8 to 12 characters (alphanumeric). I would like to use the Format function to format it such that after first 4 characters a hyphen to be inserted and after next 3 characters another hyphen to be inserted:
cccc-ccc-c
if string has 8 chrs
cccc-ccc-cc
if string has 9 chrs
cccc-ccc-ccc
if string has 10 chrs
cccc-ccc-cccc
if string has 11 chrs
cccc-ccc-ccccc
if string has 12 chrs
Is it possible to use a single lined Format function to acquire the effect? I admit that the usage of Format function is beyond my grasp.
The function you are looking for is FormatMaskText located in System.MaskUtils. The Mask to be used is 'cccc-ccc-ccccc;0;'.
Use Insert instead of Format:
Insert(s, '-', 5);
Insert(s, '-', 9);
There is no built-in format specifier (or combination of them) that will do the formatting you're looking to do.
You can, of course, write your own function to do so (name it, of course, with something meaningful to the values you're formatting):
function MyFormat(Value: string): String;
begin
Assert(Length(Value) >= 8);
Result := System.Insert(Value, '-', 5);
Result := System.Insert(Result,'-', 9);
end;
Use it:
Value := MyFormat('12345678'); // Returns '1234-567-8'
Value := MyFormat('123456789'); // Returns '1234-567-89'
Value := MyFormat('1234567890'); // Returns '1234-567-890'
If you insist on trying to do it with Format, you need multiple calls to Copy (although you can skip the first one by using a width specifier). These can be done, of course, on a single line; I've spread it across multiple just for formatting here to eliminate horizontal scrolling.
Str := '12345678';
Value := Format('%.4s-%s-%s',
[Str,
Copy(Str, 5, 3),
Copy(Str, 8, MaxInt)]); // Return '1234-567-8'
Str := '1234567890';
Value := Format('%.4s-%s-%s',
[Str,
Copy(Str, 5, 3),
Copy(Str, 8, MaxInt)]); // Return '1234-567-890'
There is no way to use a "width specifer" type method to extract substrings within a string, though. (You can extract the first n characters using %.ns, but you can't do the first n characters starting at the fourth with any combination of specifiers.)
I'm creating an application which will create a large number of folders on a web server, with files inside of them.
I need the folder name to be unique. I can easily do this with a GUID, but I want something more user friendly. It doesn't need to be speakable by users, but should be short and standard characters (alphas is best).
In short: i'm looking to do something like Bit.ly does with their unique names:
www.mydomain.com/ABCDEF
Is there a good reference on how to do this? My platform will be .NET/C#, but ok with any help, references, links, etc on the general concept, or any overall advice to solve this task.
Start at 1. Increment to 2, 3, 4, 5, 6, 7,
8, 9, a, b...
A, B, C...
X, Y, Z, 10, 11, 12, ... 1a, 1b,
You get the idea.
You have a synchronized global int/long "next id" and represent it in base 62 (numbers, lowercase, caps) or base 36 or something.
I'm assuming that you know how to use your web server's redirect capabilities. If you need help, just comment :).
The way I would do it would be generating a random integer (between the integer values of 'a' and 'z'); converting it into a char; appending it to a string; and repeating until we reach the needed length. If it generates a value already in the database, repeat the process. If it was unique, store it in the database with the name of the actual location and the name of the alias.
This is a bit hack-like because it assumes that 'a' through 'z' are actually in sequence in their integer values.
Best I could think of :(.
In Perl, without modules so you can translate more easly.
sub convert_to_base {
my ($n, $b) = #_;
my #digits;
while ($n) {
my $digits = $n % $b;
unshift #digits, $digit;
$n = ($n - $digit) / $b;
}
unshift #digits, 0 if !#digits;
return #digits;
}
# Whatever characters you want to use.
my #digit_set = ( '0'..'9', 'a'..'z', 'A'..'Z' );
# The id of the record in the database,
# or one more than the last id you generated.
my $id = 1;
my $converted =
join '',
map { $digit_set[$_] }
convert_to_base($id, 0+#digits_set);
I needed something similar to what you're trying to accomplish. I retooled my code to generate folders so try this. It's setup for a console app, but you can use it in a website also.
private static void genRandomFolders()
{
string basepath = "C:\\Users\\{username here}\\Desktop\\";
int count = 5;
int length = 8;
List<string> codes = new List<string>();
int total = 0;
int i = count;
Random rnd = new Random();
while (i-- > 0)
{
string code = RandomString(rnd, length);
if (!codes.Exists(delegate(string c) { return c.ToLower() == code.ToLower(); }))
{
//Create directory here
System.IO.Directory.CreateDirectory(basepath + code);
}
total++;
if (total % 100 == 0)
Console.WriteLine("Generated " + total.ToString() + " random folders...");
}
Console.WriteLine();
Console.WriteLine("Generated " + total.ToString() + " total random folders.");
}
public static string RandomString(Random r, int len)
{
//string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; //uppercase only
//string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; //All
string str = "abcdefghjkmnpqrstuvwxyz123456789"; //Lowercase only
StringBuilder sb = new StringBuilder();
while ((len--) > 0)
sb.Append(str[(int)(r.NextDouble() * str.Length)]);
return sb.ToString();
}