Is there a way to sort string lists by numbers inside of the strings? - flutter

Is there a way to sort something like:
List<String> hi = ['1hi', '2hi','5hi', '3hi', '4hi'];
to this?
['1hi', '2hi','3hi', '4hi', '5hi']

Just calling List<String>.sort() by itself will do a lexicographic sort. That is, your strings will be sorted in character code order, and '10' will be sorted before '2'. That usually isn't expected.
A lexicographic sort will work if your numbers have leading 0s to ensure that all numbers have the same number of digits. However, if the number of digits is variable, you will need to parse the values of the numbers for sorting. A more general approach is to provide a callback to .sort() to tell it how to determine the relative ordering of two items.
Luckily, package:collection has a compareNatural function that can do this for you:
import 'package:collection/collection.dart';
List<String> hi = ['1hi', '2hi','5hi', '3hi', '4hi'];
hi.sort(compareNatural);
If your situation is a bit more complicated and compareNatural doesn't do what you want, a more general approach is to make the .sort() callback do parsing itself, such as via a regular expression:
/// Returns the integer prefix from a string.
///
/// Returns null if no integer prefix is found.
int parseIntPrefix(String s) {
var re = RegExp(r'(-?[0-9]+).*');
var match = re.firstMatch(s);
if (match == null) {
return null;
}
return int.parse(match.group(1));
}
int compareIntPrefixes(String a, String b) {
var aValue = parseIntPrefix(a);
var bValue = parseIntPrefix(b);
if (aValue != null && bValue != null) {
return aValue - bValue;
}
if (aValue == null && bValue == null) {
// If neither string has an integer prefix, sort the strings lexically.
return a.compareTo(b);
}
// Sort strings with integer prefixes before strings without.
if (aValue == null) {
return 1;
} else {
return -1;
}
}
void main() {
List<String> hi = ['1hi', '2hi','5hi', '3hi', '4hi'];
hi.sort(compareIntPrefixes);
}

You can sort the list like this:
hi.sort();
(because numbers sort before letters in its implementation)

Related

Flutter Parse Method Explanation

I have a String date in format Month-Day-4DigitYear that I want to convert to DateTime in Flutter. I'm a novice coder, and I'm struggling to understand the api.flutter.dev Parse method example.
Below is the example. I just have a few issues. Android Studio throws multiple errors when I just create a class and put in this function. I think I understand the non-nullable issue, so I delete the ! and ? marks everywhere.
My issues are: what are _parseFormat, _brokenDownDateToValue, _withValue ?
All give errors and just declaring the first two and deleting the _withValue doesn't seem to do the trick, although removes all errors. It's like they've left out a key portion that I'm missing or there is a package I need to import the neither I nor Android Studio knows about. Can anyone decrypt this? I get very frustrated with flutter's documentation, as it always seems to give 80% of required info, assuming you already are clairvoyant on all other topics except this single one they are discussing. Gotta be a pro before reading the manual.
// TODO(lrn): restrict incorrect values like 2003-02-29T50:70:80.
// Or not, that may be a breaking change.
static DateTime parse(String formattedString) {
var re = _parseFormat;
Match? match = re.firstMatch(formattedString);
if (match != null) {
int parseIntOrZero(String? matched) {
if (matched == null) return 0;
return int.parse(matched);
}
// Parses fractional second digits of '.(\d+)' into the combined
// microseconds. We only use the first 6 digits because of DateTime
// precision of 999 milliseconds and 999 microseconds.
int parseMilliAndMicroseconds(String? matched) {
if (matched == null) return 0;
int length = matched.length;
assert(length >= 1);
int result = 0;
for (int i = 0; i < 6; i++) {
result *= 10;
if (i < matched.length) {
result += matched.codeUnitAt(i) ^ 0x30;
}
}
return result;
}
int years = int.parse(match[1]!);
int month = int.parse(match[2]!);
int day = int.parse(match[3]!);
int hour = parseIntOrZero(match[4]);
int minute = parseIntOrZero(match[5]);
int second = parseIntOrZero(match[6]);
int milliAndMicroseconds = parseMilliAndMicroseconds(match[7]);
int millisecond =
milliAndMicroseconds ~/ Duration.microsecondsPerMillisecond;
int microsecond = milliAndMicroseconds
.remainder(Duration.microsecondsPerMillisecond) as int;
bool isUtc = false;
if (match[8] != null) {
// timezone part
isUtc = true;
String? tzSign = match[9];
if (tzSign != null) {
// timezone other than 'Z' and 'z'.
int sign = (tzSign == '-') ? -1 : 1;
int hourDifference = int.parse(match[10]!);
int minuteDifference = parseIntOrZero(match[11]);
minuteDifference += 60 * hourDifference;
minute -= sign * minuteDifference;
}
}
int? value = _brokenDownDateToValue(years, month, day, hour, minute,
second, millisecond, microsecond, isUtc);
if (value == null) {
throw FormatException("Time out of range", formattedString);
}
return DateTime._withValue(value, isUtc: isUtc);
} else {
throw FormatException("Invalid date format", formattedString);
}
}
My issues are: what are _parseFormat, _brokenDownDateToValue, _withValue ?
These are objects or functions declared elsewhere in the lib which are private (the _ as the first character declares objects and functions as private) and therefore not shown in the documentation.
_parseFormat seems to be a regular expression.
_brokenDownDateToValue seems to be a function.
_withValue is a named constructor.
I think what you want to use is the following if you want to parse your date String to a DateTime object.
var date = "11-28-2020"; // Month-Day-4DigitYear
var dateTime = DateTime.parse(date.split('-').reversed.join());
See https://api.flutter.dev/flutter/dart-core/DateTime/parse.html for the accepted strings to be parsed.
I did find the full code example here.
It didn't use the name _parseFormat, instead just RegExp? And has _withValue and _brokenDownDateToValue declarations.
As I see it, there isn't a proper way to decode their example. The example is insufficient. A dictionary should not create definitions using words that can't be found elsewhere in the dictionary.

Swift: Identify the entered characters are in ascending/descending sequence

I have a textfield where i can't set the text which is ascending/descending sequence. like "abcdefgh" or "1234567". how to identify the entered string is not in ascending/descending order in swift.
This should do the trick:
enum Order {
case ascending
case descending
case none
}
func getOrder(of text: String) -> Order {
switch text {
case String(text.sorted()):
return .ascending
case String(text.sorted().reversed()):
return .descending
default:
return .none
}
}
Others have proposed sorting the string and checking to see if the array changes with sorting. That's not quite right, since the string "abcxyz" is in sorted order, but is not a sequence because it's missing some characters. Sorting is also relatively expensive, at least on larger arrays. (Not an issue if you're dealing with a few hundred characters, since a good sort algorithm's O(n•log n) performance is pretty much O(n) performance for small data-sets.)
How about this extension on String I came up with:
extension String {
func isSequence() -> Bool
{
let charValues = Array(self.unicodeScalars).map { $0.value }
guard let first = charValues.first,
let last = charValues.last else { return false }
return charValues == Array(first...last)
}
}
It maps the string into an array of UTF32 values, and then checks to see if the result is the same as a range from first...last of the values.
You could use that function to validate a user's text input to see if it is a continuous sequence of characters like "abcdefg".
The above should have O(n) time complexity.
You could do it this way:
func isOrderedSequence(_ string: String) -> Bool {
return string == String(string.sorted())
}
Here are some test cases:
isOrderedSequence("1234567") //true
isOrderedSequence("7836") //false
isOrderedSequence("abcdefg") //true
isOrderedSequence("Hello") //false
isOrderedSequence("123abc") //true
It uses sorted()

Swift Type 'string.index' has no subscript members

I'm currently converting C++ code to Swift and I've gotten stuck on one part. The parameter passed into the function is a string and the area where I'm stuck is when attempting to set a variable based on the second to last character of a string to check for a certain character.
The error shows up on this line:
line[i-1]
I've tried casting this value to an Int but this didn't work:
Int(line[i - 1])
I've also tried to see if the string's startIndex function which takes a Int would work but it didn't:
line.startIndex[i - 1]
Here is the full function:
func scanStringForSpecificCharacters(line: String){
var maxOpen: Int = 0;
var minOpen: Int = 0;
minOpen = 0;
maxOpen = 0;
var i = 0
while i < line.characters.count {
for character in line.characters {
//var c: Character = line[i];
if character == "(" {
maxOpen += 1;
if i == 0 || line[i - 1] != ":" {
minOpen += 1;
}
}
else if character == ")"{
minOpen = max(0,minOpen-1);
if i == 0 || line[i-1] != ":"{
maxOpen -= 1;
}
if maxOpen < 0{
break;
}
}
}
if maxOpen >= 0 && minOpen == 0{
print("YES")
}else{
print("NO")
}
}
}
Strings in Swift aren't indexed collections and instead you can access one of four different views: characters, UTF8, UTF16, or unicodescalars.
This is because Swift supports unicode, where an individual characters may actually be composed of multiple unicode scalars.
Here's a post that really helped me wrap my head around this: https://oleb.net/blog/2016/08/swift-3-strings/
Anyway, to answer you question you'll need to create an index using index(after:), index(before:), or index(_, offsetBy:).
In your case you'd want to do something like this:
line.index(line.endIndex, offsetBy: -2) // second to last character
Also, you'll probably find it easier to iterate directly using a String.Index type rather than Int:
let line = "hello"
var i = line.startIndex
while i < line.endIndex {
print(line[i])
i = line.index(after: i)
}
// prints ->
// h
// e
// l
// l
// o
Working with Strings in Swift was changed several times during it's evolution and it doesn't look like C++ at all. You cannot subscript string to obtain individual characters, you should use index class for that. I recommend you read this article:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html
As already pointed out in the other answers, the compiler error
is caused by the problem that you cannot index a Swift String with
integers.
Another problem in your code is that you have a nested loop which is
probably not intended.
Actually I would try to avoid string indexing at all and only
enumerate the characters, if possible. In your case, you can
easily keep track of the preceding character in a separate variable:
var lastChar: Character = " " // Anything except ":"
for char in line.characters {
if char == "(" {
maxOpen += 1;
if lastChar != ":" {
minOpen += 1;
}
}
// ...
lastChar = char
}
Or, since you only need to know if the preceding character is
a colon:
var lastIsColon = false
for char in string.characters {
if char == "(" {
maxOpen += 1;
if !lastIsColon {
minOpen += 1;
}
}
// ...
lastIsColon = char == ":"
}
Another possible approach is to iterate over the string and a shifted
view of the string in parallel:
for (lastChar, char) in zip([" ".characters, line.characters].joined(), line.characters) {
// ...
}
As others have already explained, trying to index into Swift strings is a pain.
As a minimal change to your code, I would recommend that you just create an array of the characters in your line up front:
let linechars = Array(line.characters)
And then anywhere you need to index into the line, use linechars:
This:
if i == 0 || line[i-1] != ":" {
becomes:
if i == 0 || linechars[i-1] != ":" {

Javascript create a function that returns a boolean value based on certain parameters

Thanks for taking the time to look at my problem. What I'm trying to do is create a javascript function that tests whether a sting is a particular length and also whether each element of that string can be found in another string. The function then needs to return a boolean value of either true or false depending on whether the string is valid.
Here's what I have:
N_ALPHA = 6;
N_CHOICES = 4;
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var alphabet = ALPHABET.substring(0, N_ALPHA);
function isValidGuess(inStr)
{ var valid;
var Str = inStr;
for (i=0; i<Str.length; i++)
{ if (Str.charAt(i) === alphabet.charAt(i) && Str.length == N_CHOICES.length)
{ valid = true;
}
else
{ valid = false;
}
}
return valid;
}
This code is not working at all. It only returns false every time. Any help you could provide would be greatly appreciated. Thank you.
N_CHOICES.length return undefined, because variable N_CHOICES is number.
you have to change your condition to
if (Str.charAt(i) === alphabet.charAt(i) && Str.length == N_CHOICES)

Lambda casting error

I'm using lambda expression and trying convert into uint while adding into the Hashset. Here what I'm doing:
HashSet<uint> LeadsInSession = new HashSet<uint>();
if (HttpContext.Current.Session["Category_SelectedLeadIds"] != null)
{
Dictionary<LeadPurchase.CategoryLeadSearch, List<string>> toRemoveLeadsIDs =
(Dictionary<LeadPurchase.CategoryLeadSearch, List<string>>)HttpContext.Current.Session["Category_SelectedLeadIds"];
LeadPurchase.CategoryLeadSearch searches = (LeadPurchase.CategoryLeadSearch)HttpContext.Current.Session["searches"];
var toAdd = toRemoveLeadsIDs.Where(Pair => Pair.Key.VerticalID == searches.VerticalID)
.Select(Pair => Pair.Value)
.ToList();
foreach (var lead in toAdd)
LeadsInSession.Add(lead);// I need to convert lead into uint. Convert.ToUInt32() didn't work here.
}
Any way around?
Your problem is that toRemoveLoasIOs is a dictionary with a value type of List<string>. Therefore toAdd will be IEnumerable<List<string>>, and thus lead is List<string> which, quite reasonably will not convert to uint.
You need to iterate over both toAdd and, an inner loop, over lead and then you have the individual strings to convert. Something like:
foreach (var lead in toAdd) {
foreach (string value in lead) {
uint u;
if (UInt32.TryParse(value, out u)) {
LeadsInSession.Add(u);
}
}
}
Did you Try ?
LeadPurchase.CategoryLeadSearch searches
= (LeadPurchase.CategoryLeadSearch) HttpContext.Current.Session["searches"];
var toAdd = toRemoveLeadsIDs.Where(Pair => Pair.Key.VerticalID == searches.VerticalID)
.Select(Pair => (uint)Pair.Value)
.ToList<uint>();
foreach (var lead in toAdd)
LeadsInSession.Add(lead);
}