I'm quite new to programming and I'd like help/guidance on this concept please - "separating the user interface from logic". I'm assigned a simple task of designing a currency converter (it's pretty much a very basic code as it asks for the conversion rate - but that's the given spec) and below is the code I've used:
public class CurrencyConverter
{
public static void main(String[] args)
{
System.out.println("Welcome to Currency converter!" + "\n");
System.out.println("Please select an option below: ");
System.out.println("1 >> Pound to Euro " +
"\n2 >> Euro to Pound");
Scanner s = new Scanner(System.in);
String selection = s.next();
switch (selection)
{
case "1":
System.out.println("Enter the conversion rate from Pound to Euro");
break;
case "2":
System.out.println("Enter the conversion rate from Euro to Pound");
break;
}
Scanner rate = new Scanner(System.in);
double conversionRate = rate.nextDouble();
System.out.println("Now enter the amount to be converted: ");
Scanner input = new Scanner(System.in);
double amount = input.nextDouble();
double totalValue = conversionRate * amount;
System.out.println(totalValue);
}
There's a specific instruction to not do it this way (i.e. just one class) and also to not use GUI. Two classes are to be used, one for the user interface and the other for the conversion (logic). I've tried putting the code below in a second class "Conversion" but it doesn't work.
double totalValue = conversionRate * amount;
Any tip/help would be much appreciated! By the way, if you know how to do it using GUI, that'd be helpful too. But of course my main problem is the one above.
Thanks.
Separating ui from logic allows you to increase scalability and readability of your code. In your case one class should provide methods to support interaction with user in order to provide necessary data and other that will handle convertion process. Both classes should not be aware of each other.
For example ui can be implemented as follows:
public class ConverterUI {
public Double askForConversionRate() {
System.out.println("Welcome to Currency converter!" + "\n");
System.out.println("Please select an option below: ");
System.out.println("1 >> Pound to Euro "
+ "\n2 >> Euro to Pound");
Scanner s = new Scanner(System.in);
String selection = s.next();
switch (selection) {
case "1":
System.out.println("Enter the conversion rate from Pound to Euro");
break;
case "2":
System.out.println("Enter the conversion rate from Euro to Pound");
break;
}
Scanner rate = new Scanner(System.in);
return rate.nextDouble();
}
public Double askForAmountToConvert() {
System.out.println("Now enter the amount to be converted: ");
Scanner input = new Scanner(System.in);
return input.nextDouble();
}
public void showResult(Double result) {
System.out.println("Convertion result is: " + result);
}
}
It has three simple methods that you can use to get data from user but there is no information about what to do with it. Converter class is responsible for this part:
public class Converter {
private Double conversionRate;
private Double amount;
public Double convert() {
return conversionRate * amount;
}
public void setConversionRate(Double conversionRate) {
this.conversionRate = conversionRate;
}
public void setAmount(Double amount) {
this.amount = amount;
}
}
It allows you to set convertion rate and amount values and make some calculations with convert() method. It doesn't need to know what is the source of data - you should support valid arguments and it will provide response.
In this situation main class can look like this:
public class Main {
public static void main(String[] args) {
ConverterUI ui = new ConverterUI();
Converter converter = new Converter();
Double convertionRate = ui.askForConversionRate();
converter.setConversionRate(convertionRate);
Double amount = ui.askForAmountToConvert();
converter.setAmount(amount);
Double result = converter.convert();
ui.showResult(result);
}
}
We create two objects - ui and converter. Calling ui methods provides data which are set to converter, and result of convertion is passed back to ui to inform user.
This approach allows you to add new UI (defining an interfaces would be a nice idea) and converter implementations without need of editing existing ones.
If you make another class called Conversion, it will not have access to the conversionRate and amount variables, since those variables only exist within the main method of the CurrencyConverter object.
You would have to create an object of the Conversion class and pass conversionRate and amount as method parameters. You might think about passing conversionRate to the constructor so that you could use the same rate to convert many different amounts, only passing the amount each time:
// Construct an object of the Conversion class with
// conversionRate as a parameter.
Conversion c = new Conversion(conversionRate);
// Call the convert method of the Conversion object using amount as a param.
System.out.println("Value of " + amountOne +
" Pounds in Euros: " + c.convert(amountOne));
System.out.println("Value of " + amountTwo +
" Pounds in Euros: " + c.convert(amountTwo));
You could later just assign c to a new Conversion object if you wanted to change the rate:
c = new Conversion(aDifferentRate);
The same goes for your Scanner objects. You can reuse the same variable over and over again. In your example, I don't think you even need to assign it to a new object, but you could:
Scanner in = new Scanner(System.in);
String str = in.next();
in = new Scanner(3.14159);
double d = in.nextDouble();
Related
In Protobuf3 zero is the default value for numeric types, and so they are filtered out when serialized.
I have an application where I need to send a value only when it has changed. For example, x was 1, now x is 0, send this value.
It isn't possible to send only the delta, eg -1, because some of these values are floats or doubles, and we do not want to accrue errors.
There are over 200 different variables in some classes I need to serialize, so solutions like "add a boolean to flag which fields have changed" are possible but not fun. Other suggestions that have a large amount of per-field work or processing are undesirable too.
Is there a simple mechanism to tell protobuf3 to explicitly keep a value even though it is the default value?
Possible solutions:
Send the entire class each time. The main downside here is some fields may have a lot of data.
Use a boolean "has changed" in the schema to indicate if a variable has changed, even if it is 0
Use a magic value. Terrible idea, but possible. Not going to do this.
If you need to distinguish 0 and null then you can use proto3 wrapper types: https://developers.google.com/protocol-buffers/docs/reference/csharp-generated#wrapper_types
There are special wrapper types for such case: StringWrapper, Int32Wrapper and etc. All of the wrapper types that correspond to C# value types (Int32Wrapper, DoubleWrapper, BoolWrapper etc) are mapped to Nullable<T> where T is the corresponding non-nullable type.
Since you tagged protobuf-net, you can do this at the field level:
[ProtoMember(..., IsRequired = true)]
// your field
or globally (here I'm assuming you are using the default model, which is a pretty safe assumption usually):
RuntimeTypeModel.Default.ImplicitZeroDefault = false;
and you're done;
Note: if you're interested in deltas, you can also do this conditionally - for a property Foo, you can add:
private bool ShouldSerializeFoo() { /* your rules here */ }
(this is a name-based pattern used by many serializers and other tools; in some scenarios, it needs to be public, but protobuf-net is usually happy with it non-public)
As a non-trivial example of an object that tracks delta state internally:
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
static class P
{
static void Main()
{
var obj = new MyType
{
Foo = 42,
Bar = "abc",
Blap = DateTime.Now
};
ShowPayloadSize("original", obj);
obj.MarkClean();
ShowPayloadSize("clean", obj);
obj.Foo = 42;
obj.Bar = "abc";
ShowPayloadSize("set property to same", obj);
obj.Foo = 45;
obj.Bar = "new value";
ShowPayloadSize("set property to different", obj);
obj.MarkClean();
ShowPayloadSize("clean again", obj);
}
static void ShowPayloadSize<T>(string caption, T obj)
{
using var ms = new MemoryStream();
Serializer.Serialize(ms, obj);
Console.WriteLine($"{caption}: {ms.Length} bytes");
}
}
[ProtoContract]
public class MyType
{
private int _dirty = -1; // treat everything as dirty by default
public void MarkClean() => _dirty = 0;
public bool IsDirty => _dirty != 0;
private bool ShouldSerialize(int flag) => (_dirty & flag) != 0;
private void Set<T>(ref T field, T value, int flag)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
_dirty |= flag;
}
}
[ProtoMember(1)]
public int Foo
{
get => _foo;
set => Set(ref _foo, value, 1 << 0);
}
public bool ShouldSerializeFoo() => ShouldSerialize(1 << 0);
private int _foo;
[ProtoMember(2)]
public string Bar
{
get => _bar;
set => Set(ref _bar, value, 1 << 1);
}
public bool ShouldSerializeBar() => ShouldSerialize(1 << 1);
private string _bar;
[ProtoMember(3)]
public DateTime Blap
{
get => _blap;
set => Set(ref _blap, value, 1 << 2);
}
public bool ShouldSerializeBlap() => ShouldSerialize(1 << 2);
private DateTime _blap;
}
I am trying to find the text position in PDF page?
What I have tried is to get the text in the PDF page by PDF Text Extractor using simple text extraction strategy. I am looping each word to check if my word exists. split the words using:
var Words = pdftextextractor.Split(new char[] { ' ', '\n' });
What I wasn't able to do is to find the text position. The problem is I wasn't able to find the location of the text. All I need to find is the y co-ordinates of the word in the PDF file.
I was able to manipulate it with my previous version for Itext5. I don't know if you are looking for C# but that is what the below code is written in.
using iText.Kernel.Geom;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Data;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iTextSharp.text.pdf.parser;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class TextLocationStrategy : LocationTextExtractionStrategy
{
private List<textChunk> objectResult = new List<textChunk>();
public override void EventOccurred(IEventData data, EventType type)
{
if (!type.Equals(EventType.RENDER_TEXT))
return;
TextRenderInfo renderInfo = (TextRenderInfo)data;
string curFont = renderInfo.GetFont().GetFontProgram().ToString();
float curFontSize = renderInfo.GetFontSize();
IList<TextRenderInfo> text = renderInfo.GetCharacterRenderInfos();
foreach (TextRenderInfo t in text)
{
string letter = t.GetText();
Vector letterStart = t.GetBaseline().GetStartPoint();
Vector letterEnd = t.GetAscentLine().GetEndPoint();
Rectangle letterRect = new Rectangle(letterStart.Get(0), letterStart.Get(1), letterEnd.Get(0) - letterStart.Get(0), letterEnd.Get(1) - letterStart.Get(1));
if (letter != " " && !letter.Contains(' '))
{
textChunk chunk = new textChunk();
chunk.text = letter;
chunk.rect = letterRect;
chunk.fontFamily = curFont;
chunk.fontSize = curFontSize;
chunk.spaceWidth = t.GetSingleSpaceWidth() / 2f;
objectResult.Add(chunk);
}
}
}
}
public class textChunk
{
public string text { get; set; }
public Rectangle rect { get; set; }
public string fontFamily { get; set; }
public int fontSize { get; set; }
public float spaceWidth { get; set; }
}
I also get down to each individual character because it works better for my process. You can manipulate the names, and of course the objects, but I created the textchunk to hold what I wanted, rather than have a bunch of renderInfo objects.
You can implement this by adding a few lines to grab the data from your pdf.
PdfDocument reader = new PdfDocument(new PdfReader(filepath));
FilteredEventListener listener = new FilteredEventListener();
var strat = listener.AttachEventListener(new TextExtractionStrat());
PdfCanvasProcessor processor = new PdfCanvasProcessor(listener);
processor.ProcessPageContent(reader.GetPage(1));
Once you are this far, you can pull the objectResult from the strat by making it public or creating a method within your class to grab the objectResult and do something with it.
#Joris' answer explains how to implement a completely new extraction strategy / event listener for the task. Alternatively one can try and tweak an existing text extraction strategy to do what you required.
This answer demonstrates how to tweak the existing LocationTextExtractionStrategy to return both the text and its characters' respective y coordinates.
Beware, this is but a proof-of-concept which in particular assumes text to be written horizontally, i.e. using an effective transformation matrix (ctm and text matrix combined) with b and c equal to 0.
Furthermore the character and coordinate retrieval methods of TextPlusY are not at all optimized and might take long to execute.
As the OP did not express a language preference, here a solution for iText7 for Java:
TextPlusY
For the task at hand one needs to be able to retrieve character and y coordinates side by side. To make this easier I use a class representing both text its characters' respective y coordinates. It is derived from CharSequence, a generalization of String, which allows it to be used in many String related functions:
public class TextPlusY implements CharSequence
{
final List<String> texts = new ArrayList<>();
final List<Float> yCoords = new ArrayList<>();
//
// CharSequence implementation
//
#Override
public int length()
{
int length = 0;
for (String text : texts)
{
length += text.length();
}
return length;
}
#Override
public char charAt(int index)
{
for (String text : texts)
{
if (index < text.length())
{
return text.charAt(index);
}
index -= text.length();
}
throw new IndexOutOfBoundsException();
}
#Override
public CharSequence subSequence(int start, int end)
{
TextPlusY result = new TextPlusY();
int length = end - start;
for (int i = 0; i < yCoords.size(); i++)
{
String text = texts.get(i);
if (start < text.length())
{
float yCoord = yCoords.get(i);
if (start > 0)
{
text = text.substring(start);
start = 0;
}
if (length > text.length())
{
result.add(text, yCoord);
}
else
{
result.add(text.substring(0, length), yCoord);
break;
}
}
else
{
start -= text.length();
}
}
return result;
}
//
// Object overrides
//
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
for (String text : texts)
{
builder.append(text);
}
return builder.toString();
}
//
// y coordinate support
//
public TextPlusY add(String text, float y)
{
if (text != null)
{
texts.add(text);
yCoords.add(y);
}
return this;
}
public float yCoordAt(int index)
{
for (int i = 0; i < yCoords.size(); i++)
{
String text = texts.get(i);
if (index < text.length())
{
return yCoords.get(i);
}
index -= text.length();
}
throw new IndexOutOfBoundsException();
}
}
(TextPlusY.java)
TextPlusYExtractionStrategy
Now we extend the LocationTextExtractionStrategy to extract a TextPlusY instead of a String. All we need for that is to generalize the method getResultantText.
Unfortunately the LocationTextExtractionStrategy has hidden some methods and members (private or package protected) which need to be accessed here; thus, some reflection magic is required. If your framework does not allow this, you'll have to copy the whole strategy and manipulate it accordingly.
public class TextPlusYExtractionStrategy extends LocationTextExtractionStrategy
{
static Field locationalResultField;
static Method sortWithMarksMethod;
static Method startsWithSpaceMethod;
static Method endsWithSpaceMethod;
static Method textChunkSameLineMethod;
static
{
try
{
locationalResultField = LocationTextExtractionStrategy.class.getDeclaredField("locationalResult");
locationalResultField.setAccessible(true);
sortWithMarksMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("sortWithMarks", List.class);
sortWithMarksMethod.setAccessible(true);
startsWithSpaceMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("startsWithSpace", String.class);
startsWithSpaceMethod.setAccessible(true);
endsWithSpaceMethod = LocationTextExtractionStrategy.class.getDeclaredMethod("endsWithSpace", String.class);
endsWithSpaceMethod.setAccessible(true);
textChunkSameLineMethod = TextChunk.class.getDeclaredMethod("sameLine", TextChunk.class);
textChunkSameLineMethod.setAccessible(true);
}
catch(NoSuchFieldException | NoSuchMethodException | SecurityException e)
{
// Reflection failed
}
}
//
// constructors
//
public TextPlusYExtractionStrategy()
{
super();
}
public TextPlusYExtractionStrategy(ITextChunkLocationStrategy strat)
{
super(strat);
}
#Override
public String getResultantText()
{
return getResultantTextPlusY().toString();
}
public TextPlusY getResultantTextPlusY()
{
try
{
List<TextChunk> textChunks = new ArrayList<>((List<TextChunk>)locationalResultField.get(this));
sortWithMarksMethod.invoke(this, textChunks);
TextPlusY textPlusY = new TextPlusY();
TextChunk lastChunk = null;
for (TextChunk chunk : textChunks)
{
float chunkY = chunk.getLocation().getStartLocation().get(Vector.I2);
if (lastChunk == null)
{
textPlusY.add(chunk.getText(), chunkY);
}
else if ((Boolean)textChunkSameLineMethod.invoke(chunk, lastChunk))
{
// we only insert a blank space if the trailing character of the previous string wasn't a space, and the leading character of the current string isn't a space
if (isChunkAtWordBoundary(chunk, lastChunk) &&
!(Boolean)startsWithSpaceMethod.invoke(this, chunk.getText()) &&
!(Boolean)endsWithSpaceMethod.invoke(this, lastChunk.getText()))
{
textPlusY.add(" ", chunkY);
}
textPlusY.add(chunk.getText(), chunkY);
}
else
{
textPlusY.add("\n", lastChunk.getLocation().getStartLocation().get(Vector.I2));
textPlusY.add(chunk.getText(), chunkY);
}
lastChunk = chunk;
}
return textPlusY;
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new RuntimeException("Reflection failed", e);
}
}
}
(TextPlusYExtractionStrategy.java)
Usage
Using these two classes you can extract text with coordinates and search therein like this:
try ( PdfReader reader = new PdfReader(YOUR_PDF);
PdfDocument document = new PdfDocument(reader) )
{
TextPlusYExtractionStrategy extractionStrategy = new TextPlusYExtractionStrategy();
PdfPage page = document.getFirstPage();
PdfCanvasProcessor parser = new PdfCanvasProcessor(extractionStrategy);
parser.processPageContent(page);
TextPlusY textPlusY = extractionStrategy.getResultantTextPlusY();
System.out.printf("\nText from test.pdf\n=====\n%s\n=====\n", textPlusY);
System.out.print("\nText with y from test.pdf\n=====\n");
int length = textPlusY.length();
float lastY = Float.MIN_NORMAL;
for (int i = 0; i < length; i++)
{
float y = textPlusY.yCoordAt(i);
if (y != lastY)
{
System.out.printf("\n(%4.1f) ", y);
lastY = y;
}
System.out.print(textPlusY.charAt(i));
}
System.out.print("\n=====\n");
System.out.print("\nMatches of 'est' with y from test.pdf\n=====\n");
Matcher matcher = Pattern.compile("est").matcher(textPlusY);
while (matcher.find())
{
System.out.printf("from character %s to %s at y position (%4.1f)\n", matcher.start(), matcher.end(), textPlusY.yCoordAt(matcher.start()));
}
System.out.print("\n=====\n");
}
(ExtractTextPlusY test method testExtractTextPlusYFromTest)
For my test document
the output of the test code above is
Text from test.pdf
=====
Ein Dokumen t mit einigen
T estdaten
T esttest T est test test
=====
Text with y from test.pdf
=====
(691,8) Ein Dokumen t mit einigen
(666,9) T estdaten
(642,0) T esttest T est test test
=====
Matches of 'est' with y from test.pdf
=====
from character 28 to 31 at y position (666,9)
from character 39 to 42 at y position (642,0)
from character 43 to 46 at y position (642,0)
from character 49 to 52 at y position (642,0)
from character 54 to 57 at y position (642,0)
from character 59 to 62 at y position (642,0)
=====
My locale uses the comma as decimal separator, you might see 666.9 instead of 666,9.
The extra spaces you see can be removed by fine-tuning the base LocationTextExtractionStrategy functionality further. But that is the focus of other questions...
First, SimpleTextExtractionStrategy is not exactly the 'smartest' strategy (as the name would suggest.
Second, if you want the position you're going to have to do a lot more work. TextExtractionStrategy assumes you are only interested in the text.
Possible implementation:
implement IEventListener
get notified for all events that render text, and store the corresponding TextRenderInfo object
once you're finished with the document, sort these objects based on their position in the page
loop over this list of TextRenderInfo objects, they offer both the text being rendered and the coordinates
how to:
implement ITextExtractionStrategy (or extend an existing
implementation)
use PdfTextExtractor.getTextFromPage(doc.getPage(pageNr), strategy), where strategy denotes the strategy you created in step 1
your strategy should be set up to keep track of locations for the text it processed
ITextExtractionStrategy has the following method in its interface:
#Override
public void eventOccurred(IEventData data, EventType type) {
// you can first check the type of the event
if (!type.equals(EventType.RENDER_TEXT))
return;
// now it is safe to cast
TextRenderInfo renderInfo = (TextRenderInfo) data;
}
Important to keep in mind is that rendering instructions in a pdf do not need to appear in order.
The text "Lorem Ipsum Dolor Sit Amet" could be rendered with instructions similar to:
render "Ipsum Do"
render "Lorem "
render "lor Sit Amet"
You will have to do some clever merging (depending on how far apart two TextRenderInfo objects are), and sorting (to get all the TextRenderInfo objects in the proper reading order.
Once that's done, it should be easy.
For anyone looking for a simple Rectangle object this worked for me. I made these two classes, and call the static method "GetTextCoordinates" with your page and desired text.
public class PdfTextLocator : LocationTextExtractionStrategy
{
public string TextToSearchFor { get; set; }
public List<TextChunk> ResultCoordinates { get; set; }
/// <summary>
/// Returns a rectangle with a given location of text on a page. Returns null if not found.
/// </summary>
/// <param name="page">Page to Search</param>
/// <param name="s">String to be found</param>
/// <returns></returns>
public static Rectangle GetTextCoordinates(PdfPage page, string s)
{
PdfTextLocator strat = new PdfTextLocator(s);
PdfTextExtractor.GetTextFromPage(page, strat);
foreach (TextChunk c in strat.ResultCoordinates)
{
if (c.Text == s)
return c.ResultCoordinates;
}
return null;
}
public PdfTextLocator(string textToSearchFor)
{
this.TextToSearchFor = textToSearchFor;
ResultCoordinates = new List<TextChunk>();
}
public override void EventOccurred(IEventData data, EventType type)
{
if (!type.Equals(EventType.RENDER_TEXT))
return;
TextRenderInfo renderInfo = (TextRenderInfo)data;
IList<TextRenderInfo> text = renderInfo.GetCharacterRenderInfos();
for (int i = 0; i < text.Count; i++)
{
if (text[i].GetText() == TextToSearchFor[0].ToString())
{
string word = "";
for (int j = i; j < i + TextToSearchFor.Length && j < text.Count; j++)
{
word = word + text[j].GetText();
}
float startX = text[i].GetBaseline().GetStartPoint().Get(0);
float startY = text[i].GetBaseline().GetStartPoint().Get(1);
ResultCoordinates.Add(new TextChunk(word, new Rectangle(startX, startY, text[i].GetAscentLine().GetEndPoint().Get(0) - startX, text[i].GetAscentLine().GetEndPoint().Get(0) - startY)));
}
}
}
}
public class TextChunk
{
public string Text { get; set; }
public Rectangle ResultCoordinates { get; set; }
public TextChunk(string s, Rectangle r)
{
Text = s;
ResultCoordinates = r;
}
}
I have an error response model in my API project:
public class ErrorResponse
{
public string ErrorId { get; set;}
public string Message {get; set;}
}
I need to generate a random ErrorId. I saw the use of Random class but just wondering what is the best way to do this. Some consideration, do I need to create the ErrorId in the constructor of ErrorResponse class and make ErrorId as read-only (no setter) or do I let the ErrorId set by the calling class.
You can create a new Guid and assign it to ErrorId in the constructor:
public ErrorResponse()
{
ErrorId = Guid.NewGuid().ToString();
}
Alternatively, you may want to give the client an http response and include the ErrorId:
return Content(HttpStatusCode.BadRequest, "ErrorId");
You can use the Random class or generate a Guid. But I just want to expand on what I think you are trying to do. Instead of your own custom error response - perhaps considering using the HttpResponseException or the HttpResponseMesssage. You can include a custom / random error identifier in Content / reason or in the message.
public IEnumerable<string> Get()
{
try
{
SomeMethod();
return new string[] { "xxx", "yyy" };
}
catch (Exception e)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("An error occurred."),
ReasonPhrase = "your custom error id"
});
// log
}
}
And:
public HttpResponseMessage Get(int id)
{
try
{
// your code
}
catch (Exception ex)
{
// Log exception code goes here
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "your custom error id.”);
}
}
Best way to create Random number is to use Random class in C#... Here is the example
Random rnd = new Random();
int first = rnd.Next(1, 40); //Number between 1(inclusive) and 40(exclusive)
int second = rnd.Next(10); //Number between 0 and 9
Note: If you are going to create more than one random numbers then you should use the same instance of the Random class. If you create new instance too close in time, there are chances that they both will produce the same random numbers as Random class is based(seeded)on System clock.
I use Random but quite not sure if there are consequences in an API application(due to thread-safety, etc). So in the constructor, I generate the ErrorId.
public ErrorResponse()
{
var random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890.!+-?";
ErrorId = new string(Enumerable.Repeat(chars, 10)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
I'm pretty sure that I have to use Collections, but I'm honestly super lost on how to implement it! My assignment needs me to
"Allow the user to view the plants sorted by price (lowest to highest), scientific name (alphabetized by genus), or common name (alphabetized by first letter of first word)."
this is my code, I don't know where to exactly put the sorting and how to write it in code :(
package plant.nursery.program;
import java.util.Scanner;
import java.util.ArrayList;
public class PlantNurseryProgram {
private double maxHeightInFt;
private String commonName;
private String scientificName;
private double price;
boolean isFragile;
public PlantNurseryProgram(String commonName,
String scientificName, double maxHeightInFt, double price,
boolean isFragile) {
this.maxHeightInFt = maxHeightInFt;
this.commonName = commonName;
this.scientificName = scientificName;
this.price = price;
this.isFragile = isFragile;
}
#Override
public String toString() {
return commonName + " (" + scientificName + ") " + "is " +
maxHeightInFt + " ft tall " + "it costs $" + price + " and "
+ "isFragile = " + isFragile;
}
public String setName(String newName){
commonName = newName;
return newName;
}
public String setSName(String newSName)
{
scientificName = newSName;
return scientificName;
}
public double setHeight(double newHeight){
maxHeightInFt = newHeight;
return maxHeightInFt;
}
public double setPrice(double newPrice){
price = newPrice;
return price;
}
public boolean setFragile(boolean newFragile){
isFragile = newFragile;
return isFragile;
}
public static void main(String[] args) {
Scanner plant = new Scanner(System.in);
boolean toContinue = true;
ArrayList<PlantNurseryProgram> plantNurseryAL = new ArrayList<>();
do
{
System.out.println("What's the name of your plant");
String commonName = plant.next();
System.out.println("What's the scientific name for your plant?");
String scientificName = plant.next();
System.out.println("How tall is your plant?");
double maxHeightInFt = plant.nextDouble();
System.out.println("What's the price of your plant?");
double price = plant.nextDouble();
System.out.println("Is the plant fragile? If yes type(true), if no "
+ "type (false)");
boolean isFragile = plant.nextBoolean();
System.out.println("Do you wish to continue?");
toContinue = plant.nextBoolean();
PlantNurseryProgram userPlant = new PlantNurseryProgram(
commonName, scientificName, maxHeightInFt, price, isFragile);
plantNurseryAL.add(userPlant);
System.out.println("Is all the information you entered correct?");
boolean corrections = plant.nextBoolean();
if(corrections == false)
{
System.out.println("What would you like to correct?"
+ " 1. Name, 2. Scientific name, 3. Height, 4. Price"
+ "5. Fragility?" );
int userChoice = plant.nextInt();
if(userChoice == 1)
{
System.out.println("Please enter the correct name");
String newName = plant.next();
userPlant.setName(newName);
System.out.println(userPlant);
}
else if(userChoice == 2)
{
System.out.println("Please enter the correct scientific name");
String newSName = plant.next();
userPlant.setSName(newSName);
System.out.println(userPlant);
}
else if(userChoice == 3)
{
System.out.println("Please enter the correct height");
double newHeight = plant.nextDouble();
userPlant.setHeight(newHeight);
System.out.println(userPlant);
}
else if(userChoice == 4)
{
System.out.println("Please enter the correct price");
double newPrice = plant.nextDouble();
userPlant.setPrice(newPrice);
System.out.println(userPlant);
}
else if(userChoice == 5)
{
System.out.println("Please enter if the plant is fragile or not");
boolean newFragile = plant.nextBoolean();
userPlant.setFragile(newFragile);
System.out.println(userPlant);
}
}
} while(toContinue == true);
for (PlantNurseryProgram plantNurseryProgram : plantNurseryAL) {
System.out.println(plantNurseryProgram);
} // end of for loop
} //end of main
} // end of class
You're posting too much here. To figure out what to do, you don't need a main method or a user interface. By the way, conventionally, setters are void methods.
You should become familiar with the available methods for collections in the java.util package. Or you can use the index and look up sort. You will see that the Collections class has two sort methods. One is for the case when the class is inherently Comparable:
public class TemperatureGauge implements Comparable<TemperatureGauge> {...}
The other is when a class may be sorted many different ways, so that there is no natural way to define a standard comparison. Then, you create a Comparator.
public class CommonNameComparator implements Comparator<PlantNurseryProgram> {
public int compare(PlantNurseryProgram left, PlantNurseryProgram right) {
// Do something with left.getCommonName() and right.getCommonName().
// It must return a negative, zero, or positive depending on whether
// left should come before, in the same place, or after right.
return /*anInteger*/;
}
}
Repeat the process for the scientific name and the price.
For extra credit, write JUnit tests for each comparator and for the sorting process.
You can use set interface of collection class.Before that first you should override the equals and hashcode method in your code with the field according to which you want to short.
After this you can create object of PlantNurseryProgram and this in one of the subclass of set interface.As per your requirement you can use Treeset class.
Reemember there are several other ways to ddo this.Using set you can not duplicate item.
If your purpose is to sort after inserting the data in data structure the use sort method provided in java collection api.
See the below link.
http://www.roseindia.net/java/jdk6/set-interface.shtml
I realize that a lot of questions have been asked relating to full text search and Entity Framework, but I hope this question is a bit different.
I am using Entity Framework, Code First and need to do a full text search. When I need to perform the full text search, I will typically have other criteria/restrictions as well - like skip the first 500 rows, or filter on another column, etc.
I see that this has been handled using table valued functions - see http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx. And this seems like the right idea.
Unfortunately, table valued functions are not supported until Entity Framework 5.0 (and even then, I believe, they are not supported for Code First).
My real question is what are the suggestions for the best way to handle this, both for Entity Framework 4.3 and Entity Framework 5.0. But to be specific:
Other than dynamic SQL (via System.Data.Entity.DbSet.SqlQuery, for example), are there any options available for Entity Framework 4.3?
If I upgrade to Entity Framework 5.0, is there a way I can use table valued functions with code first?
Thanks,
Eric
Using interceptors introduced in EF6, you could mark the full text search in linq and then replace it in dbcommand as described in http://www.entityframework.info/Home/FullTextSearch:
public class FtsInterceptor : IDbCommandInterceptor
{
private const string FullTextPrefix = "-FTSPREFIX-";
public static string Fts(string search)
{
return string.Format("({0}{1})", FullTextPrefix, search);
}
public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}
public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
}
public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
RewriteFullTextQuery(command);
}
public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
}
public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
RewriteFullTextQuery(command);
}
public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
}
public static void RewriteFullTextQuery(DbCommand cmd)
{
string text = cmd.CommandText;
for (int i = 0; i < cmd.Parameters.Count; i++)
{
DbParameter parameter = cmd.Parameters[i];
if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength))
{
if (parameter.Value == DBNull.Value)
continue;
var value = (string)parameter.Value;
if (value.IndexOf(FullTextPrefix) >= 0)
{
parameter.Size = 4096;
parameter.DbType = DbType.AnsiStringFixedLength;
value = value.Replace(FullTextPrefix, ""); // remove prefix we added n linq query
value = value.Substring(1, value.Length - 2);
// remove %% escaping by linq translator from string.Contains to sql LIKE
parameter.Value = value;
cmd.CommandText = Regex.Replace(text,
string.Format(
#"\[(\w*)\].\[(\w*)\]\s*LIKE\s*#{0}\s?(?:ESCAPE N?'~')",
parameter.ParameterName),
string.Format(#"contains([$1].[$2], #{0})",
parameter.ParameterName));
if (text == cmd.CommandText)
throw new Exception("FTS was not replaced on: " + text);
text = cmd.CommandText;
}
}
}
}
}
static class LanguageExtensions
{
public static bool In<T>(this T source, params T[] list)
{
return (list as IList<T>).Contains(source);
}
}
For example, if you have class Note with FTS-indexed field NoteText:
public class Note
{
public int NoteId { get; set; }
public string NoteText { get; set; }
}
and EF map for it
public class NoteMap : EntityTypeConfiguration<Note>
{
public NoteMap()
{
// Primary Key
HasKey(t => t.NoteId);
}
}
and context for it:
public class MyContext : DbContext
{
static MyContext()
{
DbInterception.Add(new FtsInterceptor());
}
public MyContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
}
public DbSet<Note> Notes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new NoteMap());
}
}
you can have quite simple syntax to FTS query:
class Program
{
static void Main(string[] args)
{
var s = FtsInterceptor.Fts("john");
using (var db = new MyContext("CONNSTRING"))
{
var q = db.Notes.Where(n => n.NoteText.Contains(s));
var result = q.Take(10).ToList();
}
}
}
That will generate SQL like
exec sp_executesql N'SELECT TOP (10)
[Extent1].[NoteId] AS [NoteId],
[Extent1].[NoteText] AS [NoteText]
FROM [NS].[NOTES] AS [Extent1]
WHERE contains([Extent1].[NoteText], #p__linq__0)',N'#p__linq__0 char(4096)',#p__linq__0='(john)
Please notice that you should use local variable and cannot move FTS wrapper inside expression like
var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john")));
I have found that the easiest way to implement this is to setup and configure full-text-search in SQL Server and then use a stored procedure. Pass your arguments to SQL, allow the DB to do its job and return either a complex object or map the results to an entity. You don't necessarily have to have dynamic SQL, but it may be optimal. For example, if you need paging, you could pass in PageNumber and PageSize on every request without the need for dynamic SQL. However, if the number of arguments fluctuates per query, it will be the optimal solution.
As the other guys mentioned, I would say start using Lucene.NET
Lucene has a pretty high learning curve, but I found an wrapper for it called "SimpleLucene", that can be found on CodePlex
Let me quote a couple of codeblocks from the blog to show you how easy it is to use. I've just started to use it, but got the hang of it really fast.
First, get some entities from your repository, or in your case, use Entity Framework
public class Repository
{
public IList<Product> Products {
get {
return new List<Product> {
new Product { Id = 1, Name = "Football" },
new Product { Id = 2, Name = "Coffee Cup"},
new Product { Id = 3, Name = "Nike Trainers"},
new Product { Id = 4, Name = "Apple iPod Nano"},
new Product { Id = 5, Name = "Asus eeePC"},
};
}
}
}
The next thing you want to do is create an index-definition
public class ProductIndexDefinition : IIndexDefinition<Product> {
public Document Convert(Product p) {
var document = new Document();
document.Add(new Field("id", p.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
document.Add(new Field("name", p.Name, Field.Store.YES, Field.Index.ANALYZED));
return document;
}
public Term GetIndex(Product p) {
return new Term("id", p.Id.ToString());
}
}
and create an search index for it.
var writer = new DirectoryIndexWriter(
new DirectoryInfo(#"c:\index"), true);
var service = new IndexService();
service.IndexEntities(writer, Repository().Products, ProductIndexDefinition());
So, you now have an search-able index. The only remaining thing to do is.., searching! You can do pretty amazing things, but it can be as easy as this: (for greater examples see the blog or the documentation on codeplex)
var searcher = new DirectoryIndexSearcher(
new DirectoryInfo(#"c:\index"), true);
var query = new TermQuery(new Term("name", "Football"));
var searchService = new SearchService();
Func<Document, ProductSearchResult> converter = (doc) => {
return new ProductSearchResult {
Id = int.Parse(doc.GetValues("id")[0]),
Name = doc.GetValues("name")[0]
};
};
IList<Product> results = searchService.SearchIndex(searcher, query, converter);
The example here http://www.entityframework.info/Home/FullTextSearch is not complete solution. You will need to look into understand how the full text search works. Imagine you have a search field and the user types 2 words to hit search. The above code will throw an exception. You need to do pre-processing on the search phrase first to pass it to the query by using logical AND or OR.
for example your search phrase is "blah blah2" then you need to convert this into:
var searchTerm = #"\"blah\" AND/OR \"blah2\" ";
Complete solution would be:
value = Regex.Replace(value, #"\s+", " "); //replace multiplespaces
value = Regex.Replace(value, #"[^a-zA-Z0-9 -]", "").Trim();//remove non-alphanumeric characters and trim spaces
if (value.Any(Char.IsWhiteSpace))
{
value = PreProcessSearchKey(value);
}
public static string PreProcessSearchKey(string searchKey)
{
var splitedKeyWords = searchKey.Split(null); //split from whitespaces
// string[] addDoubleQuotes = new string[splitedKeyWords.Length];
for (int j = 0; j < splitedKeyWords.Length; j++)
{
splitedKeyWords[j] = $"\"{splitedKeyWords[j]}\"";
}
return string.Join(" AND ", splitedKeyWords);
}
this methods uses AND logic operator. You might pass that as an argument and use the method for both AND or OR operators.
You must escape none-alphanumeric characters otherwise it would throw exception when a user enters alpha numeric characters and you have no server site model level validation in place.
I recently had a similar requirement and ended up writing an IQueryable extension specifically for Microsoft full text index access, its available here IQueryableFreeTextExtensions